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