Skip to main content

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 flex_fuchsia_net_interfaces as fnet_interfaces;
8use flex_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
9use flex_fuchsia_net_resources as fnet_resources;
10use futures::{Future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
11use thiserror::Error;
12
13/// Error type when using a [`fnet_interfaces_admin::AddressStateProviderProxy`].
14#[derive(Error, Debug)]
15pub enum AddressStateProviderError {
16    /// Address removed error.
17    #[error("address removed: {0:?}")]
18    AddressRemoved(fnet_interfaces_admin::AddressRemovalReason),
19    /// FIDL error.
20    #[error("fidl error")]
21    Fidl(#[from] fidl::Error),
22    /// Channel closed.
23    #[error("AddressStateProvider channel closed")]
24    ChannelClosed,
25}
26
27impl From<TerminalError<fnet_interfaces_admin::AddressRemovalReason>>
28    for AddressStateProviderError
29{
30    fn from(e: TerminalError<fnet_interfaces_admin::AddressRemovalReason>) -> Self {
31        match e {
32            TerminalError::Fidl(e) => AddressStateProviderError::Fidl(e),
33            TerminalError::Terminal(r) => AddressStateProviderError::AddressRemoved(r),
34        }
35    }
36}
37
38/// Waits for the `OnAddressAdded` event to be received on the event stream.
39///
40/// Returns an error if an address removed event is received instead.
41pub async fn wait_for_address_added_event(
42    event_stream: &mut fnet_interfaces_admin::AddressStateProviderEventStream,
43) -> Result<(), AddressStateProviderError> {
44    let event = event_stream
45        .next()
46        .await
47        .ok_or(AddressStateProviderError::ChannelClosed)?
48        .map_err(AddressStateProviderError::Fidl)?;
49    match event {
50        fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => Ok(()),
51        fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved { error } => {
52            Err(AddressStateProviderError::AddressRemoved(error))
53        }
54    }
55}
56
57// TODO(https://fxbug.dev/42162477): Introduce type with better concurrency safety
58// for hanging gets.
59/// Returns a stream of assignment states obtained by watching on `address_state_provider`.
60///
61/// Note that this function calls the hanging get FIDL method
62/// [`AddressStateProviderProxy::watch_address_assignment_state`] internally,
63/// which means that this stream should not be polled concurrently with any
64/// logic which calls the same hanging get. This also means that callers should
65/// be careful not to drop the returned stream when it has been polled but yet
66/// to yield an item, e.g. due to a timeout or if using select with another
67/// stream, as doing so causes a pending hanging get to get lost, and may cause
68/// future hanging get calls to fail or the channel to be closed.
69pub fn assignment_state_stream(
70    address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
71) -> impl Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
72{
73    let event_fut = address_state_provider
74        .take_event_stream()
75        .filter_map(|event| {
76            futures::future::ready(match event {
77                Ok(event) => match event {
78                    fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => None,
79                    fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
80                        error,
81                    } => Some(AddressStateProviderError::AddressRemoved(error)),
82                },
83                Err(e) => Some(AddressStateProviderError::Fidl(e)),
84            })
85        })
86        .into_future()
87        .map(|(event, _stream)| event.unwrap_or(AddressStateProviderError::ChannelClosed));
88    futures::stream::try_unfold(
89        (address_state_provider, event_fut),
90        |(address_state_provider, event_fut)| {
91            // NB: Rely on the fact that select always polls the left future
92            // first to guarantee that if a terminal event was yielded by the
93            // right future, then we don't have an assignment state to emit to
94            // clients.
95            futures::future::select(
96                address_state_provider.watch_address_assignment_state(),
97                event_fut,
98            )
99            .then(|s| match s {
100                futures::future::Either::Left((state_result, event_fut)) => match state_result {
101                    Ok(state) => {
102                        futures::future::ok(Some((state, (address_state_provider, event_fut))))
103                            .left_future()
104                    }
105                    Err(e) if e.is_closed() => event_fut.map(Result::Err).right_future(),
106                    Err(e) => {
107                        futures::future::err(AddressStateProviderError::Fidl(e)).left_future()
108                    }
109                },
110                futures::future::Either::Right((error, _state_fut)) => {
111                    futures::future::err(error).left_future()
112                }
113            })
114        },
115    )
116}
117
118// TODO(https://fxbug.dev/42162477): Introduce type with better concurrency safety
119// for hanging gets.
120/// Wait until the Assigned state is observed on `stream`.
121///
122/// After this async function resolves successfully, the underlying
123/// `AddressStateProvider` may be used as usual. If an error is returned, a
124/// terminal error has occurred on the underlying channel.
125pub async fn wait_assignment_state<S>(
126    stream: S,
127    want: fnet_interfaces::AddressAssignmentState,
128) -> Result<(), AddressStateProviderError>
129where
130    S: Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
131        + Unpin,
132{
133    stream
134        .try_filter_map(|state| futures::future::ok((state == want).then_some(())))
135        .try_next()
136        .await
137        .and_then(|opt| opt.ok_or_else(|| AddressStateProviderError::ChannelClosed))
138}
139
140type ControlEventStreamFutureToReason =
141    fn(
142        (
143            Option<Result<fnet_interfaces_admin::ControlEvent, fidl::Error>>,
144            fnet_interfaces_admin::ControlEventStream,
145        ),
146    ) -> Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>;
147
148/// Convert [`fnet_resources::GrantForInterfaceAuthorization`] to
149/// [`fnet_resources::ProofOfInterfaceAuthorization`] with fewer
150/// permissions.
151///
152/// # Panics
153///
154/// Panics when the Event handle does not have the DUPLICATE right. Callers
155/// need not worry about this if providing a grant received from
156/// [`GetAuthorizationForInterface`].
157#[cfg(not(feature = "fdomain"))]
158pub fn proof_from_grant(
159    grant: &fnet_resources::GrantForInterfaceAuthorization,
160) -> fnet_resources::ProofOfInterfaceAuthorization {
161    let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } = grant;
162
163    // The handle duplication is expected to succeed since the input
164    // `GrantFromInterfaceAuthorization` is retrieved directly from FIDL and has
165    // `zx::Rights::DUPLICATE`. Failure may occur if memory is limited, but this
166    // problem cannot be easily resolved via userspace.
167    fnet_resources::ProofOfInterfaceAuthorization {
168        interface_id: *interface_id,
169        token: token.duplicate_handle(fidl::Rights::TRANSFER).unwrap(),
170    }
171}
172
173/// A wrapper for fuchsia.net.interfaces.admin/Control that observes terminal
174/// events.
175#[derive(Clone)]
176pub struct Control {
177    proxy: fnet_interfaces_admin::ControlProxy,
178    // Keeps a shared future that will resolve when the first event is seen on a
179    // ControlEventStream. The shared future makes the observed terminal event
180    // "sticky" for as long as we clone the future before polling it. Note that
181    // we don't drive the event stream to completion, the future is resolved
182    // when the first event is seen. That means this relies on the terminal
183    // event contract but does *not* enforce that the channel is closed
184    // immediately after or that no other events are issued.
185    terminal_event_fut: futures::future::Shared<
186        futures::future::Map<
187            futures::stream::StreamFuture<fnet_interfaces_admin::ControlEventStream>,
188            ControlEventStreamFutureToReason,
189        >,
190    >,
191}
192
193/// Waits for response on query result and terminal event. If the query has a
194/// result, returns that. Otherwise, returns the terminal event.
195async fn or_terminal_event<QR, QF, TR>(
196    query_fut: QF,
197    terminal_event_fut: TR,
198) -> Result<QR, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>
199where
200    QR: Unpin,
201    QF: Unpin + Future<Output = Result<QR, fidl::Error>>,
202    TR: Unpin
203        + Future<Output = Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>>,
204{
205    match futures::future::select(query_fut, terminal_event_fut).await {
206        futures::future::Either::Left((query_result, terminal_event_fut)) => match query_result {
207            Ok(ok) => Ok(ok),
208            Err(e) if e.is_closed() => match terminal_event_fut.await {
209                Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
210                Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
211            },
212            Err(e) => Err(TerminalError::Fidl(e)),
213        },
214        futures::future::Either::Right((event, query_fut)) => {
215            // We need to poll the query response future one more time,
216            // because of the following scenario:
217            //
218            // 1. select() polls the query response future, which returns
219            //    pending.
220            // 2. The server sends the query response and terminal event in
221            //    that order.
222            // 3. The FIDL client library dequeues both of these and wakes
223            //    the respective futures.
224            // 4. select() polls the terminal event future, which is now
225            //    ready.
226            //
227            // In that case, both futures will be ready, so we can use
228            // now_or_never() to check whether the query result future has a
229            // result, since we always want to process that result first.
230            if let Some(query_result) = query_fut.now_or_never() {
231                match query_result {
232                    Ok(ok) => Ok(ok),
233                    Err(e) if e.is_closed() => match event {
234                        Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
235                        Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
236                    },
237                    Err(e) => Err(TerminalError::Fidl(e)),
238                }
239            } else {
240                match event.map_err(|e| TerminalError::Fidl(e))? {
241                    Some(removal_reason) => Err(TerminalError::Terminal(removal_reason)),
242                    None => Err(TerminalError::Fidl(fidl::Error::ClientChannelClosed {
243                        epitaph: fidl::Epitaph::PeerClosed,
244                        protocol_name: <fnet_interfaces_admin::ControlMarker as flex_client::fidl::ProtocolMarker>::DEBUG_NAME,
245                        #[cfg(not(target_os = "fuchsia"))]
246                        reason: None,
247                    })),
248                }
249            }
250        }
251    }
252}
253
254impl Control {
255    /// Calls `AddAddress` on the proxy.
256    pub fn add_address(
257        &self,
258        address: &flex_fuchsia_net::Subnet,
259        parameters: &fnet_interfaces_admin::AddressParameters,
260        address_state_provider: flex_client::fidl::ServerEnd<
261            fnet_interfaces_admin::AddressStateProviderMarker,
262        >,
263    ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
264        self.or_terminal_event_no_return(self.proxy.add_address(
265            address,
266            parameters,
267            address_state_provider,
268        ))
269    }
270
271    /// Calls `GetId` on the proxy.
272    pub async fn get_id(
273        &self,
274    ) -> Result<u64, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
275        self.or_terminal_event(self.proxy.get_id()).await
276    }
277
278    /// Calls `RemoveAddress` on the proxy.
279    pub async fn remove_address(
280        &self,
281        address: &flex_fuchsia_net::Subnet,
282    ) -> Result<
283        fnet_interfaces_admin::ControlRemoveAddressResult,
284        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
285    > {
286        self.or_terminal_event(self.proxy.remove_address(address)).await
287    }
288
289    /// Calls `SetConfiguration` on the proxy.
290    pub async fn set_configuration(
291        &self,
292        config: &fnet_interfaces_admin::Configuration,
293    ) -> Result<
294        fnet_interfaces_admin::ControlSetConfigurationResult,
295        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
296    > {
297        self.or_terminal_event(self.proxy.set_configuration(config)).await
298    }
299
300    /// Calls `GetConfiguration` on the proxy.
301    pub async fn get_configuration(
302        &self,
303    ) -> Result<
304        fnet_interfaces_admin::ControlGetConfigurationResult,
305        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
306    > {
307        self.or_terminal_event(self.proxy.get_configuration()).await
308    }
309
310    /// Calls `GetAuthorizationForInterface` on the proxy.
311    pub async fn get_authorization_for_interface(
312        &self,
313    ) -> Result<
314        fnet_resources::GrantForInterfaceAuthorization,
315        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
316    > {
317        self.or_terminal_event(self.proxy.get_authorization_for_interface()).await
318    }
319
320    /// Calls `Enable` on the proxy.
321    pub async fn enable(
322        &self,
323    ) -> Result<
324        fnet_interfaces_admin::ControlEnableResult,
325        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
326    > {
327        self.or_terminal_event(self.proxy.enable()).await
328    }
329
330    /// Calls `Remove` on the proxy.
331    pub async fn remove(
332        &self,
333    ) -> Result<
334        fnet_interfaces_admin::ControlRemoveResult,
335        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
336    > {
337        self.or_terminal_event(self.proxy.remove()).await
338    }
339
340    /// Calls `Disable` on the proxy.
341    pub async fn disable(
342        &self,
343    ) -> Result<
344        fnet_interfaces_admin::ControlDisableResult,
345        TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
346    > {
347        self.or_terminal_event(self.proxy.disable()).await
348    }
349
350    /// Calls `Detach` on the proxy.
351    pub fn detach(
352        &self,
353    ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
354        self.or_terminal_event_no_return(self.proxy.detach())
355    }
356
357    /// Creates a new `Control` wrapper from `proxy`.
358    pub fn new(proxy: fnet_interfaces_admin::ControlProxy) -> Self {
359        let terminal_event_fut = proxy
360            .take_event_stream()
361            .into_future()
362            .map::<_, ControlEventStreamFutureToReason>(|(event, _stream)| {
363                event
364                    .map(|r| {
365                        r.map(
366                            |fnet_interfaces_admin::ControlEvent::OnInterfaceRemoved { reason }| {
367                                reason
368                            },
369                        )
370                    })
371                    .transpose()
372            })
373            .shared();
374        Self { proxy, terminal_event_fut }
375    }
376
377    /// Waits for interface removal.
378    pub async fn wait_termination(
379        self,
380    ) -> TerminalError<fnet_interfaces_admin::InterfaceRemovedReason> {
381        let Self { proxy: _, terminal_event_fut } = self;
382        match terminal_event_fut.await {
383            Ok(Some(event)) => TerminalError::Terminal(event),
384            Ok(None) => TerminalError::Fidl(fidl::Error::ClientChannelClosed {
385                epitaph: fidl::Epitaph::PeerClosed,
386                protocol_name: <fnet_interfaces_admin::ControlMarker as flex_client::fidl::ProtocolMarker>::DEBUG_NAME,
387                #[cfg(not(target_os = "fuchsia"))]
388                reason: None,
389            }),
390            Err(e) => TerminalError::Fidl(e),
391        }
392    }
393
394    /// Creates a new `Control` and its `ServerEnd`.
395    #[cfg(not(feature = "fdomain"))]
396    pub fn create_endpoints()
397    -> Result<(Self, fidl::endpoints::ServerEnd<fnet_interfaces_admin::ControlMarker>), fidl::Error>
398    {
399        let (proxy, server_end) = fidl::endpoints::create_proxy();
400        Ok((Self::new(proxy), server_end))
401    }
402
403    async fn or_terminal_event<R: Unpin, F: Unpin + Future<Output = Result<R, fidl::Error>>>(
404        &self,
405        fut: F,
406    ) -> Result<R, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
407        or_terminal_event(fut, self.terminal_event_fut.clone()).await
408    }
409
410    fn or_terminal_event_no_return(
411        &self,
412        r: Result<(), fidl::Error>,
413    ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
414        r.map_err(|err| {
415            if !err.is_closed() {
416                return TerminalError::Fidl(err);
417            }
418            // TODO(https://fxbug.dev/42178907): The terminal event may have been
419            // sent by the server but the future may not resolve immediately,
420            // resulting in the terminal event being missed and a FIDL error
421            // being returned to the user.
422            //
423            // Poll event stream to see if we have a terminal event to return
424            // instead of a FIDL closed error.
425            match self.terminal_event_fut.clone().now_or_never() {
426                Some(Ok(Some(terminal_event))) => TerminalError::Terminal(terminal_event),
427                Some(Err(e)) => {
428                    // Prefer the error observed by the proxy.
429                    let _: fidl::Error = e;
430                    TerminalError::Fidl(err)
431                }
432                None | Some(Ok(None)) => TerminalError::Fidl(err),
433            }
434        })
435    }
436}
437
438impl std::fmt::Debug for Control {
439    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440        let Self { proxy, terminal_event_fut: _ } = self;
441        fmt.debug_struct("Control").field("proxy", proxy).finish()
442    }
443}
444
445/// Errors observed from wrapped terminal events.
446#[derive(Debug)]
447pub enum TerminalError<E> {
448    /// Terminal event was observed.
449    Terminal(E),
450    /// A FIDL error occurred.
451    Fidl(fidl::Error),
452}
453
454impl<E> std::fmt::Display for TerminalError<E>
455where
456    E: std::fmt::Debug,
457{
458    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459        match self {
460            TerminalError::Terminal(e) => write!(f, "terminal event: {:?}", e),
461            TerminalError::Fidl(e) => write!(f, "fidl error: {}", e),
462        }
463    }
464}
465
466impl<E: std::fmt::Debug> std::error::Error for TerminalError<E> {}
467
468/// This can be provided on interface creation to appoint a route table into
469/// which netstack managed routes are installed.
470#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
471pub enum NetstackManagedRoutesDesignation {
472    /// The netstack managed routes are installed in the main table.
473    #[default]
474    Main,
475    /// The netstack managed routes are installed in an interface-local table.
476    ///
477    /// The interface creates local tables (one for each IP version). When the
478    /// interface is removed and all the outstanding `RouteTableV{4,6}` protocol
479    /// channels are closed, the local table is removed.
480    InterfaceLocal,
481}
482
483/// Unknown FIDL value for NetstackManagedRoutesDesignation.
484#[derive(Error, Debug)]
485#[error("unknown designation for netsack managed routes: {0}")]
486pub struct UnknownNetstackManagedRoutesDesignation(pub u64);
487
488impl TryFrom<fnet_interfaces_admin::NetstackManagedRoutesDesignation>
489    for NetstackManagedRoutesDesignation
490{
491    type Error = UnknownNetstackManagedRoutesDesignation;
492
493    fn try_from(
494        value: fnet_interfaces_admin::NetstackManagedRoutesDesignation,
495    ) -> Result<Self, Self::Error> {
496        match value {
497            fnet_interfaces_admin::NetstackManagedRoutesDesignation::Main(
498                fnet_interfaces_admin::Empty,
499            ) => Ok(Self::Main),
500            fnet_interfaces_admin::NetstackManagedRoutesDesignation::InterfaceLocal(
501                fnet_interfaces_admin::Empty,
502            ) => Ok(Self::InterfaceLocal),
503            fnet_interfaces_admin::NetstackManagedRoutesDesignation::__SourceBreaking {
504                unknown_ordinal,
505            } => Err(UnknownNetstackManagedRoutesDesignation(unknown_ordinal)),
506        }
507    }
508}
509
510impl From<NetstackManagedRoutesDesignation>
511    for fnet_interfaces_admin::NetstackManagedRoutesDesignation
512{
513    fn from(value: NetstackManagedRoutesDesignation) -> Self {
514        match value {
515            NetstackManagedRoutesDesignation::Main => Self::Main(fnet_interfaces_admin::Empty),
516            NetstackManagedRoutesDesignation::InterfaceLocal => {
517                Self::InterfaceLocal(fnet_interfaces_admin::Empty)
518            }
519        }
520    }
521}
522
523#[cfg(test)]
524mod test {
525    use std::task::Poll;
526
527    use flex_client::fidl::{ProtocolMarker, RequestStream};
528    use flex_fuchsia_net_interfaces as fnet_interfaces;
529    use flex_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
530    use fnet_interfaces_admin::InterfaceRemovedReason;
531    use test_case::test_case;
532
533    use super::*;
534
535    // Test that the terminal event is observed when the server closes its end.
536    #[fuchsia_async::run_singlethreaded(test)]
537    async fn test_assignment_state_stream() {
538        let client = flex_local::local_client_empty();
539        let (address_state_provider, server_end) =
540            client.create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
541        let state_stream = assignment_state_stream(address_state_provider);
542        futures::pin_mut!(state_stream);
543
544        const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
545            fnet_interfaces_admin::AddressRemovalReason::Invalid;
546        {
547            let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
548
549            const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
550                fnet_interfaces::AddressAssignmentState::Assigned;
551            let state_fut = state_stream.try_next().map(|r| {
552                assert_eq!(
553                    r.expect("state stream error").expect("state stream ended"),
554                    ASSIGNMENT_STATE_ASSIGNED
555                )
556            });
557            let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
558                fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
559                    responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
560                }
561                req => panic!("unexpected method called: {:?}", req),
562            });
563            let ((), ()) = futures::join!(state_fut, handle_fut);
564
565            control_handle
566                .send_on_address_removed(REMOVAL_REASON_INVALID)
567                .expect("failed to send fake INVALID address removal reason event");
568        }
569
570        assert_matches::assert_matches!(
571            state_stream.try_collect::<Vec<_>>().await,
572            Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
573        );
574    }
575
576    // Test that only one error is returned on the assignment state stream when
577    // an error observable on both the client proxy and the event stream occurs.
578    #[fuchsia_async::run_singlethreaded(test)]
579    async fn test_assignment_state_stream_single_error() {
580        let client = flex_local::local_client_empty();
581        let (address_state_provider, server_end) =
582            client.create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
583        let state_stream = assignment_state_stream(address_state_provider);
584
585        server_end
586            .close_with_epitaph(fidl::Status::INTERNAL)
587            .expect("failed to send INTERNAL epitaph");
588
589        let states_fut = state_stream.collect::<Vec<_>>();
590
591        #[cfg(not(feature = "fdomain"))]
592        let states = states_fut.now_or_never().expect("state stream not immediately ready");
593
594        // FDomain needs to pump requests through its bowels a bit so the stream
595        // won't be immediately ready.
596        #[cfg(feature = "fdomain")]
597        let states = states_fut.await;
598
599        // Use collect rather than try_collect to ensure that we don't observe
600        // multiple errors on this stream.
601        assert_matches::assert_matches!(
602            states.as_slice(),
603            [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
604                epitaph: fidl::Epitaph::Explicit(Err(fidl::Status::INTERNAL)),
605                #[cfg(not(target_os = "fuchsia"))]
606                reason: None,
607                ..
608            }))]
609        );
610    }
611
612    // Test that if an assignment state and a terminal event is available at
613    // the same time, the state is yielded first.
614    #[fuchsia_async::run_singlethreaded(test)]
615    async fn assignment_state_stream_state_before_event() {
616        let client = flex_local::local_client_empty();
617        let (address_state_provider, mut request_stream) =
618            client.create_proxy_and_stream::<fnet_interfaces_admin::AddressStateProviderMarker>();
619
620        const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
621            fnet_interfaces::AddressAssignmentState::Assigned;
622        const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
623            fnet_interfaces_admin::AddressRemovalReason::Invalid;
624
625        let ((), ()) = futures::future::join(
626            async move {
627                request_stream
628                    .try_next()
629                    .await
630                    .expect("request stream error")
631                    .expect("request stream ended")
632                    .into_watch_address_assignment_state()
633                    .expect("unexpected request")
634                    .send(ASSIGNMENT_STATE_ASSIGNED)
635                    .expect("failed to send stubbed assignment state");
636                request_stream
637                    .control_handle()
638                    .send_on_address_removed(REMOVAL_REASON_INVALID)
639                    .expect("failed to send fake INVALID address removal reason event");
640            },
641            async move {
642                let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
643                assert_matches::assert_matches!(
644                    got.as_slice(),
645                    &[
646                        Ok(got_state),
647                        Err(AddressStateProviderError::AddressRemoved(got_reason)),
648                    ] => {
649                        assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
650                        assert_eq!(got_reason, REMOVAL_REASON_INVALID);
651                    }
652                );
653            },
654        )
655        .await;
656    }
657
658    // Tests that terminal event is observed when using ControlWrapper.
659    #[fuchsia_async::run_singlethreaded(test)]
660    async fn control_terminal_event() {
661        let client = flex_local::local_client_empty();
662        let (control, mut request_stream) =
663            client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
664        let control = super::Control::new(control);
665        const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
666            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
667        const ID: u64 = 15;
668        let ((), ()) = futures::future::join(
669            async move {
670                assert_matches::assert_matches!(control.get_id().await, Ok(ID));
671                assert_matches::assert_matches!(
672                    control.get_id().await,
673                    Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
674                );
675            },
676            async move {
677                let responder = request_stream
678                    .try_next()
679                    .await
680                    .expect("operating request stream")
681                    .expect("stream ended unexpectedly")
682                    .into_get_id()
683                    .expect("unexpected request");
684                responder.send(ID).expect("failed to send response");
685                request_stream
686                    .control_handle()
687                    .send_on_interface_removed(EXPECTED_EVENT)
688                    .expect("sending terminal event");
689            },
690        )
691        .await;
692    }
693
694    // Tests that terminal error is observed when using ControlWrapper if no
695    // event is issued.
696    #[fuchsia_async::run_singlethreaded(test)]
697    async fn control_missing_terminal_event() {
698        let client = flex_local::local_client_empty();
699        let (control, mut request_stream) =
700            client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
701        let control = super::Control::new(control);
702        let ((), ()) = futures::future::join(
703            async move {
704                assert_matches::assert_matches!(
705                    control.get_id().await,
706                    Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
707                        epitaph: fidl::Epitaph::PeerClosed,
708                        protocol_name: flex_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
709                        #[cfg(not(target_os = "fuchsia"))]
710                        reason: None,
711                        ..
712                    }))
713                );
714            },
715            async move {
716                match request_stream
717                    .try_next()
718                    .await
719                    .expect("operating request stream")
720                    .expect("stream ended unexpectedly")
721                {
722                    fnet_interfaces_admin::ControlRequest::GetId { responder } => {
723                        // 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 client = flex_local::local_client_empty();
736        let (control, request_stream) =
737            client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
738        let control = super::Control::new(control);
739        const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
740            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
741        request_stream
742            .control_handle()
743            .send_on_interface_removed(CLOSE_REASON)
744            .expect("send terminal event");
745        std::mem::drop(request_stream);
746        #[cfg(feature = "fdomain")]
747        {
748            let control_clone = control.clone();
749            let _ = control_clone.wait_termination().await;
750        }
751        assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
752        assert_matches::assert_matches!(
753            control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
754                zx_status::Status::INTERNAL.into()
755            ))),
756            Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
757                fidl::TransportError::Status(zx_status::Status::INTERNAL)
758            )))
759        );
760        #[cfg(target_os = "fuchsia")]
761        assert_matches::assert_matches!(
762            control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
763                epitaph: fidl::Epitaph::PeerClosed,
764                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
765            })),
766            Err(super::TerminalError::Terminal(CLOSE_REASON))
767        );
768    }
769
770    #[fuchsia_async::run_singlethreaded(test)]
771    async fn control_wait_termination() {
772        let client = flex_local::local_client_empty();
773        let (control, request_stream) =
774            client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
775        let control = super::Control::new(control);
776        const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
777            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
778        request_stream
779            .control_handle()
780            .send_on_interface_removed(CLOSE_REASON)
781            .expect("send terminal event");
782        std::mem::drop(request_stream);
783        assert_matches::assert_matches!(
784            control.wait_termination().await,
785            super::TerminalError::Terminal(CLOSE_REASON)
786        );
787    }
788
789    #[fuchsia_async::run_singlethreaded(test)]
790    async fn control_respond_and_drop() {
791        const ID: u64 = 15;
792        let client = flex_local::local_client_empty();
793        let (control, mut request_stream) =
794            client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
795        let control = super::Control::new(control);
796        let ((), ()) = futures::future::join(
797            async move {
798                assert_matches::assert_matches!(control.get_id().await, Ok(ID));
799            },
800            async move {
801                let responder = request_stream
802                    .try_next()
803                    .await
804                    .expect("operating request stream")
805                    .expect("stream ended unexpectedly")
806                    .into_get_id()
807                    .expect("unexpected request");
808                responder.send(ID).expect("failed to send response");
809            },
810        )
811        .await;
812    }
813
814    // This test is for the case found in https://fxbug.dev/328297563.  The
815    // query result and terminal event futures both become ready after the query
816    // result is polled and returns pending. This test does not handle the case
817    // for when there is no query result.
818    #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
819    #[test_case(
820        Err(fidl::Error::InvalidHeader),
821        Ok(Some(InterfaceRemovedReason::User)),
822        Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
823        "returns query error when not closed"
824    )]
825    #[test_case(
826        Err(fidl::Error::ClientChannelClosed {
827            epitaph: fidl::Epitaph::PeerClosed,
828            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
829            #[cfg(not(target_os = "fuchsia"))]
830            reason: None,
831        }),
832        Ok(Some(InterfaceRemovedReason::User)),
833        Err(TerminalError::Terminal(InterfaceRemovedReason::User));
834        "returns terminal error when channel closed"
835    )]
836    #[test_case(
837        Err(fidl::Error::ClientChannelClosed {
838            epitaph: fidl::Epitaph::PeerClosed,
839            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
840            #[cfg(not(target_os = "fuchsia"))]
841            reason: None,
842        }),
843        Ok(None),
844        Err(TerminalError::Fidl(
845            fidl::Error::ClientChannelClosed {
846                epitaph: fidl::Epitaph::PeerClosed,
847                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
848                #[cfg(not(target_os = "fuchsia"))]
849                reason: None,
850            }
851        ));
852        "returns query error when no terminal error"
853    )]
854    #[test_case(
855        Err(fidl::Error::ClientChannelClosed {
856            epitaph: fidl::Epitaph::PeerClosed,
857            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
858            #[cfg(not(target_os = "fuchsia"))]
859            reason: None,
860        }),
861        Err(fidl::Error::InvalidHeader),
862        Err(TerminalError::Fidl(
863            fidl::Error::ClientChannelClosed {
864                epitaph: fidl::Epitaph::PeerClosed,
865                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
866                #[cfg(not(target_os = "fuchsia"))]
867                reason: None,
868            }
869        ));
870        "returns query error when terminal event returns a fidl error"
871    )]
872    #[fuchsia_async::run_singlethreaded(test)]
873    async fn control_polling_race(
874        left_future_result: Result<(), fidl::Error>,
875        right_future_result: Result<
876            Option<fnet_interfaces_admin::InterfaceRemovedReason>,
877            fidl::Error,
878        >,
879        expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
880    ) {
881        let mut polled = false;
882        let first_future = std::future::poll_fn(|_cx| {
883            if polled {
884                Poll::Ready(left_future_result.clone())
885            } else {
886                polled = true;
887                Poll::Pending
888            }
889        })
890        .fuse();
891
892        let second_future =
893            std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
894
895        let res = or_terminal_event(first_future, second_future).await;
896        match (res, expected) {
897            (Ok(()), Ok(())) => (),
898            (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
899                if res == expected => {}
900            // fidl::Error doesn't implement Eq, but this lack of an actual
901            // equality check does not matter for this test.
902            (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
903            (res, expected) => panic!("expected {:?} got {:?}", expected, res),
904        }
905    }
906
907    #[cfg(not(feature = "fdomain"))]
908    #[test]
909    fn convert_proof_to_grant() {
910        use assert_matches::assert_matches;
911        // I don't know why we need this unused. The trait is definitely used
912        // and this function is only in the non-FDomain variant so we shouldn't
913        // be building an FDomain version.
914        #[allow(unused)]
915        use fidl::{AsHandleRef, Rights};
916        // The default Event has more Rights than the token within the Grant returned from
917        // [`GetAuthorizationForInterface`], but can still be converted to be used in the
918        // [`ProofOfInterfaceAuthorization`], since only `zx::Rights::DUPLICATE` and
919        // `zx::Rights::TRANSFER` is required.
920        let event = fidl::Event::create();
921        let grant = fnet_resources::GrantForInterfaceAuthorization {
922            interface_id: Default::default(),
923            token: event,
924        };
925
926        let fnet_resources::ProofOfInterfaceAuthorization { interface_id, token } =
927            proof_from_grant(&grant);
928        assert_eq!(interface_id, Default::default());
929        assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
930    }
931}