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