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 fidl::endpoints::ProtocolMarker as _;
8use fidl::{HandleBased, Rights};
9use fidl_fuchsia_net_interfaces as fnet_interfaces;
10use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
11use fidl_fuchsia_net_resources as fnet_resources;
12use futures::{Future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
13use thiserror::Error;
14use zx_status as zx;
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_resources::GrantForInterfaceAuthorization`] to
152/// [`fnet_resources::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_resources::GrantForInterfaceAuthorization,
162) -> fnet_resources::ProofOfInterfaceAuthorization {
163    let fnet_resources::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_resources::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_resources::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 assert_matches::assert_matches;
531    use fidl::Rights;
532    use fidl::prelude::*;
533    use fidl_fuchsia_net_interfaces as fnet_interfaces;
534    use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
535    use fnet_interfaces_admin::InterfaceRemovedReason;
536    use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
537    use test_case::test_case;
538    use zx_status as zx;
539
540    use super::*;
541
542    // Test that the terminal event is observed when the server closes its end.
543    #[fuchsia_async::run_singlethreaded(test)]
544    async fn test_assignment_state_stream() {
545        let (address_state_provider, server_end) =
546            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
547        let state_stream = assignment_state_stream(address_state_provider);
548        futures::pin_mut!(state_stream);
549
550        const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
551            fnet_interfaces_admin::AddressRemovalReason::Invalid;
552        {
553            let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
554
555            const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
556                fnet_interfaces::AddressAssignmentState::Assigned;
557            let state_fut = state_stream.try_next().map(|r| {
558                assert_eq!(
559                    r.expect("state stream error").expect("state stream ended"),
560                    ASSIGNMENT_STATE_ASSIGNED
561                )
562            });
563            let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
564                fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
565                    responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
566                }
567                req => panic!("unexpected method called: {:?}", req),
568            });
569            let ((), ()) = futures::join!(state_fut, handle_fut);
570
571            control_handle
572                .send_on_address_removed(REMOVAL_REASON_INVALID)
573                .expect("failed to send fake INVALID address removal reason event");
574        }
575
576        assert_matches::assert_matches!(
577            state_stream.try_collect::<Vec<_>>().await,
578            Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
579        );
580    }
581
582    // Test that only one error is returned on the assignment state stream when
583    // an error observable on both the client proxy and the event stream occurs.
584    #[fuchsia_async::run_singlethreaded(test)]
585    async fn test_assignment_state_stream_single_error() {
586        let (address_state_provider, server_end) =
587            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
588        let state_stream = assignment_state_stream(address_state_provider);
589
590        server_end
591            .close_with_epitaph(fidl::Status::INTERNAL)
592            .expect("failed to send INTERNAL epitaph");
593
594        // Use collect rather than try_collect to ensure that we don't observe
595        // multiple errors on this stream.
596        assert_matches::assert_matches!(
597            state_stream
598                .collect::<Vec<_>>()
599                .now_or_never()
600                .expect("state stream not immediately ready")
601                .as_slice(),
602            [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
603                status: fidl::Status::INTERNAL,
604                #[cfg(not(target_os = "fuchsia"))]
605                reason: None,
606                ..
607            }))]
608        );
609    }
610
611    // Test that if an assignment state and a terminal event is available at
612    // the same time, the state is yielded first.
613    #[fuchsia_async::run_singlethreaded(test)]
614    async fn assignment_state_stream_state_before_event() {
615        let (address_state_provider, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
616            fnet_interfaces_admin::AddressStateProviderMarker,
617        >();
618
619        const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
620            fnet_interfaces::AddressAssignmentState::Assigned;
621        const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
622            fnet_interfaces_admin::AddressRemovalReason::Invalid;
623
624        let ((), ()) = futures::future::join(
625            async move {
626                request_stream
627                    .try_next()
628                    .await
629                    .expect("request stream error")
630                    .expect("request stream ended")
631                    .into_watch_address_assignment_state()
632                    .expect("unexpected request")
633                    .send(ASSIGNMENT_STATE_ASSIGNED)
634                    .expect("failed to send stubbed assignment state");
635                request_stream
636                    .control_handle()
637                    .send_on_address_removed(REMOVAL_REASON_INVALID)
638                    .expect("failed to send fake INVALID address removal reason event");
639            },
640            async move {
641                let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
642                assert_matches::assert_matches!(
643                    got.as_slice(),
644                    &[
645                        Ok(got_state),
646                        Err(AddressStateProviderError::AddressRemoved(got_reason)),
647                    ] => {
648                        assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
649                        assert_eq!(got_reason, REMOVAL_REASON_INVALID);
650                    }
651                );
652            },
653        )
654        .await;
655    }
656
657    // Tests that terminal event is observed when using ControlWrapper.
658    #[fuchsia_async::run_singlethreaded(test)]
659    async fn control_terminal_event() {
660        let (control, mut request_stream) =
661            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
662        let control = super::Control::new(control);
663        const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
664            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
665        const ID: u64 = 15;
666        let ((), ()) = futures::future::join(
667            async move {
668                assert_matches::assert_matches!(control.get_id().await, Ok(ID));
669                assert_matches::assert_matches!(
670                    control.get_id().await,
671                    Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
672                );
673            },
674            async move {
675                let responder = request_stream
676                    .try_next()
677                    .await
678                    .expect("operating request stream")
679                    .expect("stream ended unexpectedly")
680                    .into_get_id()
681                    .expect("unexpected request");
682                responder.send(ID).expect("failed to send response");
683                request_stream
684                    .control_handle()
685                    .send_on_interface_removed(EXPECTED_EVENT)
686                    .expect("sending terminal event");
687            },
688        )
689        .await;
690    }
691
692    // Tests that terminal error is observed when using ControlWrapper if no
693    // event is issued.
694    #[fuchsia_async::run_singlethreaded(test)]
695    async fn control_missing_terminal_event() {
696        let (control, mut request_stream) =
697            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
698        let control = super::Control::new(control);
699        let ((), ()) = futures::future::join(
700            async move {
701                assert_matches::assert_matches!(
702                    control.get_id().await,
703                    Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
704                        status: zx::Status::PEER_CLOSED,
705                        protocol_name: fidl_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
706                        #[cfg(not(target_os = "fuchsia"))]
707                        reason: None,
708                        ..
709                    }))
710                );
711            },
712            async move {
713                match request_stream
714                    .try_next()
715                    .await
716                    .expect("operating request stream")
717                    .expect("stream ended unexpectedly")
718                {
719                    fnet_interfaces_admin::ControlRequest::GetId { responder } => {
720                        // Just close the channel without issuing a response.
721                        std::mem::drop(responder);
722                    }
723                    request => panic!("unexpected request {:?}", request),
724                }
725            },
726        )
727        .await;
728    }
729
730    #[fuchsia_async::run_singlethreaded(test)]
731    async fn control_pipelined_error() {
732        let (control, request_stream) =
733            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
734        let control = super::Control::new(control);
735        const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
736            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
737        request_stream
738            .control_handle()
739            .send_on_interface_removed(CLOSE_REASON)
740            .expect("send terminal event");
741        std::mem::drop(request_stream);
742        assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
743        assert_matches::assert_matches!(
744            control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
745                zx::Status::INTERNAL.into()
746            ))),
747            Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
748                fidl::TransportError::Status(zx::Status::INTERNAL)
749            )))
750        );
751        #[cfg(target_os = "fuchsia")]
752        assert_matches::assert_matches!(
753            control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
754                status: zx::Status::PEER_CLOSED,
755                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
756                epitaph: None,
757            })),
758            Err(super::TerminalError::Terminal(CLOSE_REASON))
759        );
760    }
761
762    #[fuchsia_async::run_singlethreaded(test)]
763    async fn control_wait_termination() {
764        let (control, request_stream) =
765            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
766        let control = super::Control::new(control);
767        const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
768            fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
769        request_stream
770            .control_handle()
771            .send_on_interface_removed(CLOSE_REASON)
772            .expect("send terminal event");
773        std::mem::drop(request_stream);
774        assert_matches::assert_matches!(
775            control.wait_termination().await,
776            super::TerminalError::Terminal(CLOSE_REASON)
777        );
778    }
779
780    #[fuchsia_async::run_singlethreaded(test)]
781    async fn control_respond_and_drop() {
782        const ID: u64 = 15;
783        let (control, mut request_stream) =
784            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
785        let control = super::Control::new(control);
786        let ((), ()) = futures::future::join(
787            async move {
788                assert_matches::assert_matches!(control.get_id().await, Ok(ID));
789            },
790            async move {
791                let responder = request_stream
792                    .try_next()
793                    .await
794                    .expect("operating request stream")
795                    .expect("stream ended unexpectedly")
796                    .into_get_id()
797                    .expect("unexpected request");
798                responder.send(ID).expect("failed to send response");
799            },
800        )
801        .await;
802    }
803
804    // This test is for the case found in https://fxbug.dev/328297563.  The
805    // query result and terminal event futures both become ready after the query
806    // result is polled and returns pending. This test does not handle the case
807    // for when there is no query result.
808    #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
809    #[test_case(
810        Err(fidl::Error::InvalidHeader),
811        Ok(Some(InterfaceRemovedReason::User)),
812        Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
813        "returns query error when not closed"
814    )]
815    #[test_case(
816        Err(fidl::Error::ClientChannelClosed {
817            status: zx::Status::PEER_CLOSED,
818            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
819            #[cfg(not(target_os = "fuchsia"))]
820            reason: None,
821            epitaph: None,
822        }),
823        Ok(Some(InterfaceRemovedReason::User)),
824        Err(TerminalError::Terminal(InterfaceRemovedReason::User));
825        "returns terminal error when channel closed"
826    )]
827    #[test_case(
828        Err(fidl::Error::ClientChannelClosed {
829            status: zx::Status::PEER_CLOSED,
830            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
831            #[cfg(not(target_os = "fuchsia"))]
832            reason: None,
833            epitaph: None,
834        }),
835        Ok(None),
836        Err(TerminalError::Fidl(
837            fidl::Error::ClientChannelClosed {
838                status: zx::Status::PEER_CLOSED,
839                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
840                #[cfg(not(target_os = "fuchsia"))]
841                reason: None,
842                epitaph: None,
843            }
844        ));
845        "returns query error when no terminal error"
846    )]
847    #[test_case(
848        Err(fidl::Error::ClientChannelClosed {
849            status: zx::Status::PEER_CLOSED,
850            protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
851            #[cfg(not(target_os = "fuchsia"))]
852            reason: None,
853            epitaph: None,
854        }),
855        Err(fidl::Error::InvalidHeader),
856        Err(TerminalError::Fidl(
857            fidl::Error::ClientChannelClosed {
858                status: zx::Status::PEER_CLOSED,
859                protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
860                #[cfg(not(target_os = "fuchsia"))]
861                reason: None,
862                epitaph: None,
863            }
864        ));
865        "returns query error when terminal event returns a fidl error"
866    )]
867    #[fuchsia_async::run_singlethreaded(test)]
868    async fn control_polling_race(
869        left_future_result: Result<(), fidl::Error>,
870        right_future_result: Result<
871            Option<fnet_interfaces_admin::InterfaceRemovedReason>,
872            fidl::Error,
873        >,
874        expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
875    ) {
876        let mut polled = false;
877        let first_future = std::future::poll_fn(|_cx| {
878            if polled {
879                Poll::Ready(left_future_result.clone())
880            } else {
881                polled = true;
882                Poll::Pending
883            }
884        })
885        .fuse();
886
887        let second_future =
888            std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
889
890        let res = or_terminal_event(first_future, second_future).await;
891        match (res, expected) {
892            (Ok(()), Ok(())) => (),
893            (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
894                if res == expected => {}
895            // fidl::Error doesn't implement Eq, but this lack of an actual
896            // equality check does not matter for this test.
897            (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
898            (res, expected) => panic!("expected {:?} got {:?}", expected, res),
899        }
900    }
901
902    #[test]
903    fn convert_proof_to_grant() {
904        // The default Event has more Rights than the token within the Grant returned from
905        // [`GetAuthorizationForInterface`], but can still be converted to be used in the
906        // [`ProofOfInterfaceAuthorization`], since only `zx::Rights::DUPLICATE` and
907        // `zx::Rights::TRANSFER` is required.
908        let event = fidl::Event::create();
909        let grant = fnet_resources::GrantForInterfaceAuthorization {
910            interface_id: Default::default(),
911            token: event,
912        };
913
914        let fnet_resources::ProofOfInterfaceAuthorization { interface_id, token } =
915            proof_from_grant(&grant);
916        assert_eq!(interface_id, Default::default());
917        assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
918    }
919}