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