fidl_fuchsia_net_interfaces_ext/
admin.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Extensions for fuchsia.net.interfaces.admin.
6
7use 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/// Error type when using a [`fnet_interfaces_admin::AddressStateProviderProxy`].
17#[derive(Error, Debug)]
18pub enum AddressStateProviderError {
19    /// Address removed error.
20    #[error("address removed: {0:?}")]
21    AddressRemoved(fnet_interfaces_admin::AddressRemovalReason),
22    /// FIDL error.
23    #[error("fidl error")]
24    Fidl(#[from] fidl::Error),
25    /// Channel closed.
26    #[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
41/// Waits for the `OnAddressAdded` event to be received on the event stream.
42///
43/// Returns an error if an address removed event is received instead.
44pub 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
60// TODO(https://fxbug.dev/42162477): Introduce type with better concurrency safety
61// for hanging gets.
62/// Returns a stream of assignment states obtained by watching on `address_state_provider`.
63///
64/// Note that this function calls the hanging get FIDL method
65/// [`AddressStateProviderProxy::watch_address_assignment_state`] internally,
66/// which means that this stream should not be polled concurrently with any
67/// logic which calls the same hanging get. This also means that callers should
68/// be careful not to drop the returned stream when it has been polled but yet
69/// to yield an item, e.g. due to a timeout or if using select with another
70/// stream, as doing so causes a pending hanging get to get lost, and may cause
71/// future hanging get calls to fail or the channel to be closed.
72pub 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            // NB: Rely on the fact that select always polls the left future
95            // first to guarantee that if a terminal event was yielded by the
96            // right future, then we don't have an assignment state to emit to
97            // clients.
98            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
121// TODO(https://fxbug.dev/42162477): Introduce type with better concurrency safety
122// for hanging gets.
123/// Wait until the Assigned state is observed on `stream`.
124///
125/// After this async function resolves successfully, the underlying
126/// `AddressStateProvider` may be used as usual. If an error is returned, a
127/// terminal error has occurred on the underlying channel.
128pub 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
151/// Convert [`fnet_interfaces_admin::GrantForInterfaceAuthorization`] to
152/// [`fnet_interfaces_admin::ProofOfInterfaceAuthorization`] with fewer
153/// permissions.
154///
155/// # Panics
156///
157/// Panics when the Event handle does not have the DUPLICATE right. Callers
158/// need not worry about this if providing a grant received from
159/// [`GetAuthorizationForInterface`].
160pub 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    // The handle duplication is expected to succeed since the input
166    // `GrantFromInterfaceAuthorization` is retrieved directly from FIDL and has
167    // `zx::Rights::DUPLICATE`. Failure may occur if memory is limited, but this
168    // problem cannot be easily resolved via userspace.
169    fnet_interfaces_admin::ProofOfInterfaceAuthorization {
170        interface_id: *interface_id,
171        token: token.duplicate_handle(Rights::TRANSFER).unwrap(),
172    }
173}
174
175/// A wrapper for fuchsia.net.interfaces.admin/Control that observes terminal
176/// events.
177#[derive(Clone)]
178pub struct Control {
179    proxy: fnet_interfaces_admin::ControlProxy,
180    // Keeps a shared future that will resolve when the first event is seen on a
181    // ControlEventStream. The shared future makes the observed terminal event
182    // "sticky" for as long as we clone the future before polling it. Note that
183    // we don't drive the event stream to completion, the future is resolved
184    // when the first event is seen. That means this relies on the terminal
185    // event contract but does *not* enforce that the channel is closed
186    // immediately after or that no other events are issued.
187    terminal_event_fut: futures::future::Shared<
188        futures::future::Map<
189            futures::stream::StreamFuture<fnet_interfaces_admin::ControlEventStream>,
190            ControlEventStreamFutureToReason,
191        >,
192    >,
193}
194
195/// Waits for response on query result and terminal event. If the query has a
196/// result, returns that. Otherwise, returns the terminal event.
197async 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            // We need to poll the query response future one more time,
218            // because of the following scenario:
219            //
220            // 1. select() polls the query response future, which returns
221            //    pending.
222            // 2. The server sends the query response and terminal event in
223            //    that order.
224            // 3. The FIDL client library dequeues both of these and wakes
225            //    the respective futures.
226            // 4. select() polls the terminal event future, which is now
227            //    ready.
228            //
229            // In that case, both futures will be ready, so we can use
230            // now_or_never() to check whether the query result future has a
231            // result, since we always want to process that result first.
232            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    /// Calls `AddAddress` on the proxy.
259    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    /// Calls `GetId` on the proxy.
275    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    /// Calls `RemoveAddress` on the proxy.
282    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    /// Calls `SetConfiguration` on the proxy.
293    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    /// Calls `GetConfiguration` on the proxy.
304    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    /// Calls `GetAuthorizationForInterface` on the proxy.
314    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    /// Calls `Enable` on the proxy.
324    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    /// Calls `Remove` on the proxy.
334    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    /// Calls `Disable` on the proxy.
344    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    /// Calls `Detach` on the proxy.
354    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    /// Creates a new `Control` wrapper from `proxy`.
361    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    /// Waits for interface removal.
381    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    /// Creates a new `Control` and its `ServerEnd`.
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            // TODO(https://fxbug.dev/42178907): The terminal event may have been
422            // sent by the server but the future may not resolve immediately,
423            // resulting in the terminal event being missed and a FIDL error
424            // being returned to the user.
425            //
426            // Poll event stream to see if we have a terminal event to return
427            // instead of a FIDL closed error.
428            match self.terminal_event_fut.clone().now_or_never() {
429                Some(Ok(Some(terminal_event))) => TerminalError::Terminal(terminal_event),
430                Some(Err(e)) => {
431                    // Prefer the error observed by the proxy.
432                    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/// Errors observed from wrapped terminal events.
449#[derive(Debug)]
450pub enum TerminalError<E> {
451    /// Terminal event was observed.
452    Terminal(E),
453    /// A FIDL error occurred.
454    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#[cfg(test)]
472mod test {
473    use std::task::Poll;
474
475    use super::{
476        assignment_state_stream, or_terminal_event, proof_from_grant, AddressStateProviderError,
477        TerminalError,
478    };
479    use assert_matches::assert_matches;
480    use fidl::prelude::*;
481    use fidl::Rights;
482    use fnet_interfaces_admin::InterfaceRemovedReason;
483    use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
484    use test_case::test_case;
485    use {
486        fidl_fuchsia_net_interfaces as fnet_interfaces,
487        fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin, zx_status as zx,
488    };
489
490    // Test that the terminal event is observed when the server closes its end.
491    #[fuchsia_async::run_singlethreaded(test)]
492    async fn test_assignment_state_stream() {
493        let (address_state_provider, server_end) =
494            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
495        let state_stream = assignment_state_stream(address_state_provider);
496        futures::pin_mut!(state_stream);
497
498        const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
499            fnet_interfaces_admin::AddressRemovalReason::Invalid;
500        {
501            let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
502
503            const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
504                fnet_interfaces::AddressAssignmentState::Assigned;
505            let state_fut = state_stream.try_next().map(|r| {
506                assert_eq!(
507                    r.expect("state stream error").expect("state stream ended"),
508                    ASSIGNMENT_STATE_ASSIGNED
509                )
510            });
511            let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
512                fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
513                    let () = responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
514                }
515                req => panic!("unexpected method called: {:?}", req),
516            });
517            let ((), ()) = futures::join!(state_fut, handle_fut);
518
519            let () = control_handle
520                .send_on_address_removed(REMOVAL_REASON_INVALID)
521                .expect("failed to send fake INVALID address removal reason event");
522        }
523
524        assert_matches::assert_matches!(
525            state_stream.try_collect::<Vec<_>>().await,
526            Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
527        );
528    }
529
530    // Test that only one error is returned on the assignment state stream when
531    // an error observable on both the client proxy and the event stream occurs.
532    #[fuchsia_async::run_singlethreaded(test)]
533    async fn test_assignment_state_stream_single_error() {
534        let (address_state_provider, server_end) =
535            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
536        let state_stream = assignment_state_stream(address_state_provider);
537
538        let () = server_end
539            .close_with_epitaph(fidl::Status::INTERNAL)
540            .expect("failed to send INTERNAL epitaph");
541
542        // Use collect rather than try_collect to ensure that we don't observe
543        // multiple errors on this stream.
544        assert_matches::assert_matches!(
545            state_stream
546                .collect::<Vec<_>>()
547                .now_or_never()
548                .expect("state stream not immediately ready")
549                .as_slice(),
550            [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
551                status: fidl::Status::INTERNAL,
552                #[cfg(not(target_os = "fuchsia"))]
553                reason: None,
554                ..
555            }))]
556        );
557    }
558
559    // Test that if an assignment state and a terminal event is available at
560    // the same time, the state is yielded first.
561    #[fuchsia_async::run_singlethreaded(test)]
562    async fn assignment_state_stream_state_before_event() {
563        let (address_state_provider, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
564            fnet_interfaces_admin::AddressStateProviderMarker,
565        >();
566
567        const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
568            fnet_interfaces::AddressAssignmentState::Assigned;
569        const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
570            fnet_interfaces_admin::AddressRemovalReason::Invalid;
571
572        let ((), ()) = futures::future::join(
573            async move {
574                let () = request_stream
575                    .try_next()
576                    .await
577                    .expect("request stream error")
578                    .expect("request stream ended")
579                    .into_watch_address_assignment_state()
580                    .expect("unexpected request")
581                    .send(ASSIGNMENT_STATE_ASSIGNED)
582                    .expect("failed to send stubbed assignment state");
583                let () = request_stream
584                    .control_handle()
585                    .send_on_address_removed(REMOVAL_REASON_INVALID)
586                    .expect("failed to send fake INVALID address removal reason event");
587            },
588            async move {
589                let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
590                assert_matches::assert_matches!(
591                    got.as_slice(),
592                    &[
593                        Ok(got_state),
594                        Err(AddressStateProviderError::AddressRemoved(got_reason)),
595                    ] => {
596                        assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
597                        assert_eq!(got_reason, REMOVAL_REASON_INVALID);
598                    }
599                );
600            },
601        )
602        .await;
603    }
604
605    // Tests that terminal event is observed when using ControlWrapper.
606    #[fuchsia_async::run_singlethreaded(test)]
607    async fn control_terminal_event() {
608        let (control, mut request_stream) =
609            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
610        let control = super::Control::new(control);
611        const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
612            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
613        const ID: u64 = 15;
614        let ((), ()) = futures::future::join(
615            async move {
616                assert_matches::assert_matches!(control.get_id().await, Ok(ID));
617                assert_matches::assert_matches!(
618                    control.get_id().await,
619                    Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
620                );
621            },
622            async move {
623                let responder = request_stream
624                    .try_next()
625                    .await
626                    .expect("operating request stream")
627                    .expect("stream ended unexpectedly")
628                    .into_get_id()
629                    .expect("unexpected request");
630                let () = responder.send(ID).expect("failed to send response");
631                let () = request_stream
632                    .control_handle()
633                    .send_on_interface_removed(EXPECTED_EVENT)
634                    .expect("sending terminal event");
635            },
636        )
637        .await;
638    }
639
640    // Tests that terminal error is observed when using ControlWrapper if no
641    // event is issued.
642    #[fuchsia_async::run_singlethreaded(test)]
643    async fn control_missing_terminal_event() {
644        let (control, mut request_stream) =
645            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
646        let control = super::Control::new(control);
647        let ((), ()) = futures::future::join(
648            async move {
649                assert_matches::assert_matches!(
650                    control.get_id().await,
651                    Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
652                        status: zx::Status::PEER_CLOSED,
653                        protocol_name: fidl_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
654                        #[cfg(not(target_os = "fuchsia"))]
655                        reason: None,
656                        ..
657                    }))
658                );
659            },
660            async move {
661                match request_stream
662                    .try_next()
663                    .await
664                    .expect("operating request stream")
665                    .expect("stream ended unexpectedly")
666                {
667                    fnet_interfaces_admin::ControlRequest::GetId { responder } => {
668                        // Just close the channel without issuing a response.
669                        std::mem::drop(responder);
670                    }
671                    request => panic!("unexpected request {:?}", request),
672                }
673            },
674        )
675        .await;
676    }
677
678    #[fuchsia_async::run_singlethreaded(test)]
679    async fn control_pipelined_error() {
680        let (control, request_stream) =
681            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
682        let control = super::Control::new(control);
683        const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
684            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
685        let () = request_stream
686            .control_handle()
687            .send_on_interface_removed(CLOSE_REASON)
688            .expect("send terminal event");
689        std::mem::drop(request_stream);
690        assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
691        assert_matches::assert_matches!(
692            control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
693                zx::Status::INTERNAL.into()
694            ))),
695            Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
696                fidl::TransportError::Status(zx::Status::INTERNAL)
697            )))
698        );
699        #[cfg(target_os = "fuchsia")]
700        assert_matches::assert_matches!(
701            control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
702                status: zx::Status::PEER_CLOSED,
703                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
704                epitaph: None,
705            })),
706            Err(super::TerminalError::Terminal(CLOSE_REASON))
707        );
708    }
709
710    #[fuchsia_async::run_singlethreaded(test)]
711    async fn control_wait_termination() {
712        let (control, request_stream) =
713            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
714        let control = super::Control::new(control);
715        const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
716            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
717        let () = request_stream
718            .control_handle()
719            .send_on_interface_removed(CLOSE_REASON)
720            .expect("send terminal event");
721        std::mem::drop(request_stream);
722        assert_matches::assert_matches!(
723            control.wait_termination().await,
724            super::TerminalError::Terminal(CLOSE_REASON)
725        );
726    }
727
728    #[fuchsia_async::run_singlethreaded(test)]
729    async fn control_respond_and_drop() {
730        const ID: u64 = 15;
731        let (control, mut request_stream) =
732            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
733        let control = super::Control::new(control);
734        let ((), ()) = futures::future::join(
735            async move {
736                assert_matches::assert_matches!(control.get_id().await, Ok(ID));
737            },
738            async move {
739                let responder = request_stream
740                    .try_next()
741                    .await
742                    .expect("operating request stream")
743                    .expect("stream ended unexpectedly")
744                    .into_get_id()
745                    .expect("unexpected request");
746                let () = responder.send(ID).expect("failed to send response");
747            },
748        )
749        .await;
750    }
751
752    // This test is for the case found in https://fxbug.dev/328297563.  The
753    // query result and terminal event futures both become ready after the query
754    // result is polled and returns pending. This test does not handle the case
755    // for when there is no query result.
756    #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
757    #[test_case(
758        Err(fidl::Error::InvalidHeader),
759        Ok(Some(InterfaceRemovedReason::User)),
760        Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
761        "returns query error when not closed"
762    )]
763    #[test_case(
764        Err(fidl::Error::ClientChannelClosed {
765            status: zx::Status::PEER_CLOSED,
766            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
767            #[cfg(not(target_os = "fuchsia"))]
768            reason: None,
769            epitaph: None,
770        }),
771        Ok(Some(InterfaceRemovedReason::User)),
772        Err(TerminalError::Terminal(InterfaceRemovedReason::User));
773        "returns terminal error when channel closed"
774    )]
775    #[test_case(
776        Err(fidl::Error::ClientChannelClosed {
777            status: zx::Status::PEER_CLOSED,
778            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
779            #[cfg(not(target_os = "fuchsia"))]
780            reason: None,
781            epitaph: None,
782        }),
783        Ok(None),
784        Err(TerminalError::Fidl(
785            fidl::Error::ClientChannelClosed {
786                status: zx::Status::PEER_CLOSED,
787                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
788                #[cfg(not(target_os = "fuchsia"))]
789                reason: None,
790                epitaph: None,
791            }
792        ));
793        "returns query error when no terminal error"
794    )]
795    #[test_case(
796        Err(fidl::Error::ClientChannelClosed {
797            status: zx::Status::PEER_CLOSED,
798            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
799            #[cfg(not(target_os = "fuchsia"))]
800            reason: None,
801            epitaph: None,
802        }),
803        Err(fidl::Error::InvalidHeader),
804        Err(TerminalError::Fidl(
805            fidl::Error::ClientChannelClosed {
806                status: zx::Status::PEER_CLOSED,
807                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
808                #[cfg(not(target_os = "fuchsia"))]
809                reason: None,
810                epitaph: None,
811            }
812        ));
813        "returns query error when terminal event returns a fidl error"
814    )]
815    #[fuchsia_async::run_singlethreaded(test)]
816    async fn control_polling_race(
817        left_future_result: Result<(), fidl::Error>,
818        right_future_result: Result<
819            Option<fnet_interfaces_admin::InterfaceRemovedReason>,
820            fidl::Error,
821        >,
822        expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
823    ) {
824        let mut polled = false;
825        let first_future = std::future::poll_fn(|_cx| {
826            if polled {
827                Poll::Ready(left_future_result.clone())
828            } else {
829                polled = true;
830                Poll::Pending
831            }
832        })
833        .fuse();
834
835        let second_future =
836            std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
837
838        let res = or_terminal_event(first_future, second_future).await;
839        match (res, expected) {
840            (Ok(()), Ok(())) => (),
841            (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
842                if res == expected => {}
843            // fidl::Error doesn't implement Eq, but this lack of an actual
844            // equality check does not matter for this test.
845            (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
846            (res, expected) => panic!("expected {:?} got {:?}", expected, res),
847        }
848    }
849
850    #[test]
851    fn convert_proof_to_grant() {
852        // The default Event has more Rights than the token within the Grant returned from
853        // [`GetAuthorizationForInterface`], but can still be converted to be used in the
854        // [`ProofOfInterfaceAuthorization`], since only `zx::Rights::DUPLICATE` and
855        // `zx::Rights::TRANSFER` is required.
856        let event = fidl::Event::create();
857        let grant = fnet_interfaces_admin::GrantForInterfaceAuthorization {
858            interface_id: Default::default(),
859            token: event,
860        };
861
862        let fnet_interfaces_admin::ProofOfInterfaceAuthorization { interface_id, token } =
863            proof_from_grant(&grant);
864        assert_eq!(interface_id, Default::default());
865        assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
866    }
867}