sl4f_lib/netstack/
facade.rs

1// Copyright 2019 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
5use anyhow::{Context as _, Error};
6use component_debug::dirs::{connect_to_instance_protocol_at_dir_root, OpenDirType};
7use fidl_fuchsia_net_stackmigrationdeprecated as fnet_stack_migration;
8use once_cell::sync::OnceCell;
9use serde::Serialize;
10use serde_json::Value;
11
12fn serialize_ipv4<S: serde::Serializer>(
13    addresses: &Vec<std::net::Ipv4Addr>,
14    serializer: S,
15) -> Result<S::Ok, S::Error> {
16    serializer.collect_seq(addresses.iter().map(|address| address.octets()))
17}
18
19fn serialize_ipv6<S: serde::Serializer>(
20    addresses: &Vec<std::net::Ipv6Addr>,
21    serializer: S,
22) -> Result<S::Ok, S::Error> {
23    serializer.collect_seq(addresses.iter().map(|address| address.octets()))
24}
25
26fn serialize_mac<S: serde::Serializer>(
27    mac: &Option<fidl_fuchsia_net_ext::MacAddress>,
28    serializer: S,
29) -> Result<S::Ok, S::Error> {
30    match mac {
31        None => serializer.serialize_none(),
32        Some(fidl_fuchsia_net_ext::MacAddress { octets }) => serializer.collect_seq(octets.iter()),
33    }
34}
35
36#[derive(Serialize)]
37enum DeviceClass {
38    Loopback,
39    Blackhole,
40    Virtual,
41    Ethernet,
42    WlanClient,
43    Ppp,
44    Bridge,
45    WlanAp,
46    Lowpan,
47}
48
49#[derive(Serialize)]
50pub struct Properties {
51    id: u64,
52    name: String,
53    device_class: DeviceClass,
54    online: bool,
55    #[serde(serialize_with = "serialize_ipv4")]
56    ipv4_addresses: Vec<std::net::Ipv4Addr>,
57    #[serde(serialize_with = "serialize_ipv6")]
58    ipv6_addresses: Vec<std::net::Ipv6Addr>,
59    #[serde(serialize_with = "serialize_mac")]
60    mac: Option<fidl_fuchsia_net_ext::MacAddress>,
61}
62
63impl
64    From<(
65        fidl_fuchsia_net_interfaces_ext::Properties<fidl_fuchsia_net_interfaces_ext::AllInterest>,
66        Option<fidl_fuchsia_net::MacAddress>,
67    )> for Properties
68{
69    fn from(
70        t: (
71            fidl_fuchsia_net_interfaces_ext::Properties<
72                fidl_fuchsia_net_interfaces_ext::AllInterest,
73            >,
74            Option<fidl_fuchsia_net::MacAddress>,
75        ),
76    ) -> Self {
77        use itertools::Itertools as _;
78
79        let (
80            fidl_fuchsia_net_interfaces_ext::Properties {
81                id,
82                name,
83                port_class,
84                online,
85                addresses,
86                has_default_ipv4_route: _,
87                has_default_ipv6_route: _,
88            },
89            mac,
90        ) = t;
91        let device_class = match port_class {
92            fidl_fuchsia_net_interfaces_ext::PortClass::Loopback => DeviceClass::Loopback,
93            fidl_fuchsia_net_interfaces_ext::PortClass::Blackhole => DeviceClass::Blackhole,
94            fidl_fuchsia_net_interfaces_ext::PortClass::Virtual => DeviceClass::Virtual,
95            fidl_fuchsia_net_interfaces_ext::PortClass::Ethernet => DeviceClass::Ethernet,
96            fidl_fuchsia_net_interfaces_ext::PortClass::WlanClient => DeviceClass::WlanClient,
97            fidl_fuchsia_net_interfaces_ext::PortClass::WlanAp => DeviceClass::WlanAp,
98            fidl_fuchsia_net_interfaces_ext::PortClass::Ppp => DeviceClass::Ppp,
99            fidl_fuchsia_net_interfaces_ext::PortClass::Bridge => DeviceClass::Bridge,
100            fidl_fuchsia_net_interfaces_ext::PortClass::Lowpan => DeviceClass::Lowpan,
101        };
102        let (ipv4_addresses, ipv6_addresses) =
103            addresses.into_iter().partition_map::<_, _, _, std::net::Ipv4Addr, std::net::Ipv6Addr>(
104                |fidl_fuchsia_net_interfaces_ext::Address {
105                     addr,
106                     valid_until: _,
107                     preferred_lifetime_info: _,
108                     assignment_state,
109                 }| {
110                    // Event stream is created with `IncludedAddresses::OnlyAssigned`.
111                    assert_eq!(
112                        assignment_state,
113                        fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned,
114                        "Support for unassigned addresses have not been implemented",
115                    );
116                    let fidl_fuchsia_net_ext::Subnet { addr, prefix_len: _ } = addr.into();
117                    let fidl_fuchsia_net_ext::IpAddress(addr) = addr;
118                    match addr {
119                        std::net::IpAddr::V4(addr) => itertools::Either::Left(addr),
120                        std::net::IpAddr::V6(addr) => itertools::Either::Right(addr),
121                    }
122                },
123            );
124        Self {
125            id: id.get(),
126            name,
127            device_class,
128            online,
129            ipv4_addresses,
130            ipv6_addresses,
131            mac: mac.map(Into::into),
132        }
133    }
134}
135
136#[derive(Debug, PartialEq, Serialize)]
137/// A serializable alternative to [`fnet_stack_migration::NetstackVersion`].
138pub enum NetstackVersion {
139    Netstack2,
140    Netstack3,
141}
142
143impl From<fnet_stack_migration::NetstackVersion> for NetstackVersion {
144    fn from(version: fnet_stack_migration::NetstackVersion) -> NetstackVersion {
145        match version {
146            fnet_stack_migration::NetstackVersion::Netstack2 => NetstackVersion::Netstack2,
147            fnet_stack_migration::NetstackVersion::Netstack3 => NetstackVersion::Netstack3,
148        }
149    }
150}
151impl From<NetstackVersion> for fnet_stack_migration::NetstackVersion {
152    fn from(version: NetstackVersion) -> fnet_stack_migration::NetstackVersion {
153        match version {
154            NetstackVersion::Netstack2 => fnet_stack_migration::NetstackVersion::Netstack2,
155            NetstackVersion::Netstack3 => fnet_stack_migration::NetstackVersion::Netstack3,
156        }
157    }
158}
159
160impl From<fnet_stack_migration::VersionSetting> for NetstackVersion {
161    fn from(version: fnet_stack_migration::VersionSetting) -> NetstackVersion {
162        let fnet_stack_migration::VersionSetting { version } = version;
163        version.into()
164    }
165}
166
167impl From<NetstackVersion> for fnet_stack_migration::VersionSetting {
168    fn from(version: NetstackVersion) -> fnet_stack_migration::VersionSetting {
169        fnet_stack_migration::VersionSetting { version: version.into() }
170    }
171}
172
173impl TryFrom<Value> for NetstackVersion {
174    type Error = Error;
175    fn try_from(value: Value) -> Result<NetstackVersion, Error> {
176        match value {
177            Value::String(value) => match value.to_lowercase().as_str() {
178                "ns2" | "netstack2" => Ok(NetstackVersion::Netstack2),
179                "ns3" | "netstack3" => Ok(NetstackVersion::Netstack3),
180                _ => Err(anyhow!("unrecognized netstack version: {}", value)),
181            },
182            _ => Err(anyhow!("unrecognized netstack version: {:?}", value)),
183        }
184    }
185}
186
187#[derive(Serialize)]
188/// A serializable alternative to [`fnet_stack_migration::InEffectVersion`].
189pub struct InEffectNetstackVersion {
190    current_boot: NetstackVersion,
191    automated_selection: Option<NetstackVersion>,
192    user_selection: Option<NetstackVersion>,
193}
194
195impl From<fnet_stack_migration::InEffectVersion> for InEffectNetstackVersion {
196    fn from(in_effect: fnet_stack_migration::InEffectVersion) -> InEffectNetstackVersion {
197        let fnet_stack_migration::InEffectVersion { current_boot, automated, user } = in_effect;
198        InEffectNetstackVersion {
199            current_boot: current_boot.into(),
200            automated_selection: automated.map(|selection| (*selection).into()),
201            user_selection: user.map(|selection| (*selection).into()),
202        }
203    }
204}
205
206/// Network stack operations.
207#[derive(Debug, Default)]
208pub struct NetstackFacade {
209    interfaces_state: OnceCell<fidl_fuchsia_net_interfaces::StateProxy>,
210    root_interfaces: OnceCell<fidl_fuchsia_net_root::InterfacesProxy>,
211    netstack_migration_state: OnceCell<fnet_stack_migration::StateProxy>,
212    netstack_migration_control: OnceCell<fnet_stack_migration::ControlProxy>,
213}
214
215async fn get_netstack_proxy<P: fidl::endpoints::DiscoverableProtocolMarker>(
216) -> Result<P::Proxy, Error> {
217    let query =
218        fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_sys2::RealmQueryMarker>()?;
219    let moniker = "./core/network/netstack".try_into()?;
220    let proxy =
221        connect_to_instance_protocol_at_dir_root::<P>(&moniker, OpenDirType::Exposed, &query)
222            .await?;
223    Ok(proxy)
224}
225
226async fn get_netstack_migration_proxy<P: fidl::endpoints::DiscoverableProtocolMarker>(
227) -> Result<P::Proxy, Error> {
228    let query =
229        fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_sys2::RealmQueryMarker>()?;
230    let moniker = "./core/network/netstack-migration".try_into()?;
231    let proxy =
232        connect_to_instance_protocol_at_dir_root::<P>(&moniker, OpenDirType::Exposed, &query)
233            .await?;
234    Ok(proxy)
235}
236
237impl NetstackFacade {
238    async fn get_interfaces_state(
239        &self,
240    ) -> Result<&fidl_fuchsia_net_interfaces::StateProxy, Error> {
241        let Self {
242            interfaces_state,
243            root_interfaces: _,
244            netstack_migration_state: _,
245            netstack_migration_control: _,
246        } = self;
247        if let Some(state_proxy) = interfaces_state.get() {
248            Ok(state_proxy)
249        } else {
250            let state_proxy =
251                get_netstack_proxy::<fidl_fuchsia_net_interfaces::StateMarker>().await?;
252            interfaces_state.set(state_proxy).unwrap();
253            let state_proxy = interfaces_state.get().unwrap();
254            Ok(state_proxy)
255        }
256    }
257
258    async fn get_root_interfaces(&self) -> Result<&fidl_fuchsia_net_root::InterfacesProxy, Error> {
259        let Self {
260            interfaces_state: _,
261            root_interfaces,
262            netstack_migration_state: _,
263            netstack_migration_control: _,
264        } = self;
265        if let Some(interfaces_proxy) = root_interfaces.get() {
266            Ok(interfaces_proxy)
267        } else {
268            let interfaces_proxy =
269                get_netstack_proxy::<fidl_fuchsia_net_root::InterfacesMarker>().await?;
270            root_interfaces.set(interfaces_proxy).unwrap();
271            let interfaces_proxy = root_interfaces.get().unwrap();
272            Ok(interfaces_proxy)
273        }
274    }
275
276    async fn get_netstack_migration_state(
277        &self,
278    ) -> Result<&fnet_stack_migration::StateProxy, Error> {
279        let Self {
280            interfaces_state: _,
281            root_interfaces: _,
282            netstack_migration_state,
283            netstack_migration_control: _,
284        } = self;
285        if let Some(state_proxy) = netstack_migration_state.get() {
286            Ok(state_proxy)
287        } else {
288            let state_proxy =
289                get_netstack_migration_proxy::<fnet_stack_migration::StateMarker>().await?;
290            netstack_migration_state.set(state_proxy).unwrap();
291            let state_proxy = netstack_migration_state.get().unwrap();
292            Ok(state_proxy)
293        }
294    }
295
296    async fn get_netstack_migration_control(
297        &self,
298    ) -> Result<&fnet_stack_migration::ControlProxy, Error> {
299        let Self {
300            interfaces_state: _,
301            root_interfaces: _,
302            netstack_migration_state: _,
303            netstack_migration_control,
304        } = self;
305        if let Some(control_proxy) = netstack_migration_control.get() {
306            Ok(control_proxy)
307        } else {
308            let control_proxy =
309                get_netstack_migration_proxy::<fnet_stack_migration::ControlMarker>().await?;
310            netstack_migration_control.set(control_proxy).unwrap();
311            let control_proxy = netstack_migration_control.get().unwrap();
312            Ok(control_proxy)
313        }
314    }
315
316    async fn get_control(
317        &self,
318        id: u64,
319    ) -> Result<fidl_fuchsia_net_interfaces_ext::admin::Control, Error> {
320        let root_interfaces = self.get_root_interfaces().await?;
321        let (control, server_end) =
322            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
323                .context("create admin control endpoints")?;
324        let () = root_interfaces.get_admin(id, server_end).context("send get admin request")?;
325        Ok(control)
326    }
327
328    pub async fn enable_interface(&self, id: u64) -> Result<(), Error> {
329        let control = self.get_control(id).await?;
330        let _did_enable: bool = control
331            .enable()
332            .await
333            .map_err(anyhow::Error::new)
334            .and_then(|res| {
335                res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlEnableError| {
336                    anyhow::anyhow!("{:?}", e)
337                })
338            })
339            .with_context(|| format!("failed to enable interface {}", id))?;
340        Ok(())
341    }
342
343    pub async fn disable_interface(&self, id: u64) -> Result<(), Error> {
344        let control = self.get_control(id).await?;
345        let _did_disable: bool = control
346            .disable()
347            .await
348            .map_err(anyhow::Error::new)
349            .and_then(|res| {
350                res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlDisableError| {
351                    anyhow::anyhow!("{:?}", e)
352                })
353            })
354            .with_context(|| format!("failed to disable interface {}", id))?;
355        Ok(())
356    }
357
358    pub async fn list_interfaces(&self) -> Result<Vec<Properties>, Error> {
359        let interfaces_state = self.get_interfaces_state().await?;
360        let root_interfaces = self.get_root_interfaces().await?;
361        // Only `OnlyAssigned` is implemented; additional work is required to
362        // support unassigned addresses.
363        let stream = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(
364            interfaces_state,
365            fidl_fuchsia_net_interfaces_ext::IncludedAddresses::OnlyAssigned,
366        )?;
367        let response = fidl_fuchsia_net_interfaces_ext::existing(
368            stream,
369            std::collections::HashMap::<u64, _>::new(),
370        )
371        .await?;
372        let response = response.into_values().map(
373            |fidl_fuchsia_net_interfaces_ext::PropertiesAndState { properties, state: () }| async {
374                match root_interfaces.get_mac(properties.id.get()).await? {
375                    Ok(mac) => {
376                        let mac = mac.map(|boxed_mac| *boxed_mac);
377                        let view: Properties = (properties, mac).into();
378                        Ok::<_, Error>(Some(view))
379                    }
380                    Err(fidl_fuchsia_net_root::InterfacesGetMacError::NotFound) => {
381                        // Interface with given id not found; this occurs when state
382                        // is reported for an interface that has since been removed.
383                        Ok::<_, Error>(None)
384                    }
385                }
386            },
387        );
388        let mut response: Vec<Properties> =
389            futures::future::try_join_all(response).await?.into_iter().filter_map(|r| r).collect();
390        let () = response.sort_by_key(|&Properties { id, .. }| id);
391        Ok(response)
392    }
393
394    async fn get_addresses<T, F: Copy + FnMut(fidl_fuchsia_net::Subnet) -> Option<T>>(
395        &self,
396        f: F,
397    ) -> Result<Vec<T>, Error> {
398        let mut output = Vec::new();
399
400        let interfaces_state = self.get_interfaces_state().await?;
401        let (watcher, server) =
402            fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces::WatcherMarker>();
403        let () = interfaces_state
404            .get_watcher(&fidl_fuchsia_net_interfaces::WatcherOptions::default(), server)?;
405
406        loop {
407            match watcher.watch().await? {
408                fidl_fuchsia_net_interfaces::Event::Existing(
409                    fidl_fuchsia_net_interfaces::Properties { addresses, .. },
410                ) => {
411                    let addresses = addresses.unwrap();
412                    let () = output.extend(
413                        addresses
414                            .into_iter()
415                            .map(
416                                |fidl_fuchsia_net_interfaces::Address {
417                                     addr,
418                                     valid_until: _,
419                                     ..
420                                 }| addr.unwrap(),
421                            )
422                            .filter_map(f),
423                    );
424                }
425                fidl_fuchsia_net_interfaces::Event::Idle(fidl_fuchsia_net_interfaces::Empty {}) => {
426                    break
427                }
428                event => unreachable!("{:?}", event),
429            }
430        }
431
432        Ok(output)
433    }
434
435    pub fn get_ipv6_addresses(
436        &self,
437    ) -> impl std::future::Future<Output = Result<Vec<std::net::Ipv6Addr>, Error>> + '_ {
438        self.get_addresses(|addr| {
439            let fidl_fuchsia_net_ext::Subnet { addr, prefix_len: _ } = addr.into();
440            let fidl_fuchsia_net_ext::IpAddress(addr) = addr;
441            match addr {
442                std::net::IpAddr::V4(_) => None,
443                std::net::IpAddr::V6(addr) => Some(addr),
444            }
445        })
446    }
447
448    pub fn get_link_local_ipv6_addresses(
449        &self,
450    ) -> impl std::future::Future<Output = Result<Vec<std::net::Ipv6Addr>, Error>> + '_ {
451        use futures::TryFutureExt as _;
452
453        self.get_ipv6_addresses().map_ok(|addresses| {
454            addresses.into_iter().filter(|address| address.octets()[..2] == [0xfe, 0x80]).collect()
455        })
456    }
457
458    /// Gets the current netstack version settings.
459    ///
460    /// See [`fnet_stack_migration::StateProxy::get_netstack_version`] for more
461    /// details.
462    pub async fn get_netstack_version(&self) -> Result<InEffectNetstackVersion, Error> {
463        let netstack_migration_state = self.get_netstack_migration_state().await?;
464        Ok(netstack_migration_state.get_netstack_version().await?.into())
465    }
466
467    /// Sets the user specified netstack version. takes effect on the next boot.
468    ///
469    /// See [`fnet_stack_migration::ControlProxy::set_user_netstack_version`]
470    /// for more details.
471    pub async fn set_user_netstack_version(&self, version: NetstackVersion) -> Result<(), Error> {
472        let netstack_migration_control = self.get_netstack_migration_control().await?;
473        Ok(netstack_migration_control.set_user_netstack_version(Some(&version.into())).await?)
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480    use assert_matches::assert_matches;
481    use futures::StreamExt as _;
482    use test_case::test_case;
483    use {
484        fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as finterfaces,
485        fuchsia_async as fasync,
486    };
487
488    struct MockStateTester {
489        expected_state: Vec<Box<dyn FnOnce(finterfaces::WatcherRequest) + Send + 'static>>,
490    }
491
492    impl MockStateTester {
493        fn new() -> Self {
494            Self { expected_state: vec![] }
495        }
496
497        pub fn create_facade_and_serve_state(
498            self,
499        ) -> (NetstackFacade, impl std::future::Future<Output = ()>) {
500            let (interfaces_state, stream_future) = self.build_state_and_watcher();
501            (
502                NetstackFacade { interfaces_state: interfaces_state.into(), ..Default::default() },
503                stream_future,
504            )
505        }
506
507        fn push_state(
508            mut self,
509            request: impl FnOnce(finterfaces::WatcherRequest) + Send + 'static,
510        ) -> Self {
511            self.expected_state.push(Box::new(request));
512            self
513        }
514
515        fn build_state_and_watcher(
516            self,
517        ) -> (finterfaces::StateProxy, impl std::future::Future<Output = ()>) {
518            let (proxy, mut stream) =
519                fidl::endpoints::create_proxy_and_stream::<finterfaces::StateMarker>();
520            let stream_fut = async move {
521                match stream.next().await {
522                    Some(Ok(finterfaces::StateRequest::GetWatcher { watcher, .. })) => {
523                        let mut into_stream = watcher.into_stream();
524                        for expected in self.expected_state {
525                            let () = expected(into_stream.next().await.unwrap().unwrap());
526                        }
527                        let finterfaces::WatcherRequest::Watch { responder } =
528                            into_stream.next().await.unwrap().unwrap();
529                        let () = responder
530                            .send(&finterfaces::Event::Idle(finterfaces::Empty {}))
531                            .unwrap();
532                    }
533                    err => panic!("Error in request handler: {:?}", err),
534                }
535            };
536            (proxy, stream_fut)
537        }
538
539        fn expect_get_ipv6_addresses(self, result: Vec<fnet::Subnet>) -> Self {
540            let addresses = result
541                .into_iter()
542                .map(|addr| finterfaces::Address { addr: Some(addr), ..Default::default() })
543                .collect();
544            self.push_state(move |req| match req {
545                finterfaces::WatcherRequest::Watch { responder } => responder
546                    .send(&finterfaces::Event::Existing(finterfaces::Properties {
547                        addresses: Some(addresses),
548                        ..Default::default()
549                    }))
550                    .unwrap(),
551            })
552        }
553    }
554
555    #[fasync::run_singlethreaded(test)]
556    async fn test_get_ipv6_addresses() {
557        let ipv6_octets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
558
559        let ipv6_address = fnet::Subnet {
560            addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: ipv6_octets }),
561            // NB: prefix length is ignored, use invalid value to prove it.
562            prefix_len: 137,
563        };
564        let ipv4_address = fnet::Subnet {
565            addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: [0, 1, 2, 3] }),
566            // NB: prefix length is ignored, use invalid value to prove it.
567            prefix_len: 139,
568        };
569        let all_addresses = [ipv6_address.clone(), ipv4_address.clone()];
570        let (facade, stream_fut) = MockStateTester::new()
571            .expect_get_ipv6_addresses(all_addresses.to_vec())
572            .create_facade_and_serve_state();
573        let facade_fut = async move {
574            let result_address: Vec<_> = facade.get_ipv6_addresses().await.unwrap();
575            assert_eq!(result_address, [std::net::Ipv6Addr::from(ipv6_octets)]);
576        };
577        futures::future::join(facade_fut, stream_fut).await;
578    }
579
580    #[fasync::run_singlethreaded(test)]
581    async fn test_get_link_local_ipv6_addresses() {
582        let ipv6_address = fnet::Subnet {
583            addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address {
584                addr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
585            }),
586            // NB: prefix length is ignored, use invalid value to prove it.
587            prefix_len: 137,
588        };
589        let link_local_ipv6_octets = [0xfe, 0x80, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
590        let link_local_ipv6_address = fnet::Subnet {
591            addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: link_local_ipv6_octets }),
592            // NB: prefix length is ignored, use invalid value to prove it.
593            prefix_len: 139,
594        };
595        let ipv4_address = fnet::Subnet {
596            addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: [0, 1, 2, 3] }),
597            // NB: prefix length is ignored, use invalid value to prove it.
598            prefix_len: 141,
599        };
600        let all_addresses =
601            [ipv6_address.clone(), link_local_ipv6_address.clone(), ipv4_address.clone()];
602        let (facade, stream_fut) = MockStateTester::new()
603            .expect_get_ipv6_addresses(all_addresses.to_vec())
604            .create_facade_and_serve_state();
605        let facade_fut = async move {
606            let result_address: Vec<_> = facade.get_link_local_ipv6_addresses().await.unwrap();
607            assert_eq!(result_address, [std::net::Ipv6Addr::from(link_local_ipv6_octets)]);
608        };
609        futures::future::join(facade_fut, stream_fut).await;
610    }
611
612    #[test_case(Value::String("ns2".to_string()), Some(NetstackVersion::Netstack2); "ns2")]
613    #[test_case(Value::String("ns3".to_string()), Some(NetstackVersion::Netstack3); "ns3")]
614    #[test_case(Value::String("netstack2".to_string()), Some(NetstackVersion::Netstack2);
615        "netstack2")]
616    #[test_case(Value::String("netstack3".to_string()), Some(NetstackVersion::Netstack3);
617        "netstack3")]
618    #[test_case(Value::String("invalid".to_string()), None; "invalid_string")]
619    #[test_case(Value::Bool(false), None; "invalid_value")]
620    fn test_convert_netstack_version_from_json_value(
621        json_value: Value,
622        expected_version: Option<NetstackVersion>,
623    ) {
624        let version: Result<NetstackVersion, Error> = json_value.try_into();
625        match expected_version {
626            Some(expected_version) => assert_eq!(version.expect("parse version"), expected_version),
627            None => assert_matches!(version, Err(_)),
628        }
629    }
630
631    #[test_case(fnet_stack_migration::NetstackVersion::Netstack2, NetstackVersion::Netstack2;
632        "netstack2")]
633    #[test_case(fnet_stack_migration::NetstackVersion::Netstack3, NetstackVersion::Netstack3;
634        "netstack3")]
635    fn test_convert_netstack_version_from_fidl(
636        fidl_version: fnet_stack_migration::NetstackVersion,
637        expected_version: NetstackVersion,
638    ) {
639        assert_eq!(NetstackVersion::from(fidl_version), expected_version);
640        assert_eq!(
641            NetstackVersion::from(fnet_stack_migration::VersionSetting { version: fidl_version }),
642            expected_version
643        );
644    }
645}