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/// This can be provided on interface creation to appoint a route table into
472/// which netstack managed routes are installed.
473#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
474pub enum NetstackManagedRoutesDesignation {
475    /// The netstack managed routes are installed in the main table.
476    #[default]
477    Main,
478    /// The netstack managed routes are installed in an interface-local table.
479    ///
480    /// The interface creates local tables (one for each IP version). When the
481    /// interface is removed and all the outstanding `RouteTableV{4,6}` protocol
482    /// channels are closed, the local table is removed.
483    InterfaceLocal,
484}
485
486/// Unknown FIDL value for NetstackManagedRoutesDesignation.
487#[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    // Test that the terminal event is observed when the server closes its end.
546    #[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    // Test that only one error is returned on the assignment state stream when
586    // an error observable on both the client proxy and the event stream occurs.
587    #[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        // Use collect rather than try_collect to ensure that we don't observe
598        // multiple errors on this stream.
599        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    // Test that if an assignment state and a terminal event is available at
615    // the same time, the state is yielded first.
616    #[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    // Tests that terminal event is observed when using ControlWrapper.
661    #[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    // Tests that terminal error is observed when using ControlWrapper if no
696    // event is issued.
697    #[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                        // Just close the channel without issuing a response.
724                        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    // This test is for the case found in https://fxbug.dev/328297563.  The
808    // query result and terminal event futures both become ready after the query
809    // result is polled and returns pending. This test does not handle the case
810    // for when there is no query result.
811    #[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            // fidl::Error doesn't implement Eq, but this lack of an actual
899            // equality check does not matter for this test.
900            (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        // The default Event has more Rights than the token within the Grant returned from
908        // [`GetAuthorizationForInterface`], but can still be converted to be used in the
909        // [`ProofOfInterfaceAuthorization`], since only `zx::Rights::DUPLICATE` and
910        // `zx::Rights::TRANSFER` is required.
911        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}