Skip to main content

starnix_modules_nmfs/
fs.rs

1// Copyright 2024 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//! This file contains an implementation for storing network information in a file system.
6//!
7//! Each file within `/sys/fs/fuchsia_network_monitor_fs` represents a network and its properties.
8
9use crate::NetworkManager;
10use serde::{Deserialize, Serialize};
11use serde_repr::{Deserialize_repr, Serialize_repr};
12use starnix_core::task::{CurrentTask, Kernel};
13use starnix_core::vfs::fs_args::parse;
14use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
15use starnix_core::vfs::{
16    CacheMode, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNode,
17    FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, MemoryDirectoryFile,
18};
19use starnix_logging::{log_error, log_warn};
20use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
21use starnix_types::vfs::default_statfs;
22use starnix_uapi::auth::FsCred;
23use starnix_uapi::device_type::DeviceType;
24use starnix_uapi::errors::Errno;
25use starnix_uapi::file_mode::FileMode;
26use starnix_uapi::open_flags::OpenFlags;
27use starnix_uapi::{errno, error, statfs};
28use std::borrow::Cow;
29use std::net::{Ipv4Addr, Ipv6Addr};
30use std::sync::Arc;
31
32use fidl_fuchsia_net as fnet;
33use fidl_fuchsia_net_policy_socketproxy::{self as fnp_socketproxy, ProtoProperties};
34
35const DEFAULT_NETWORK_FILE_NAME: &str = "default";
36
37#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
38pub(crate) struct NetworkMessage {
39    pub(crate) netid: u32,
40    pub(crate) mark: u32,
41    pub(crate) handle: u64,
42    #[serde(with = "addr_list")]
43    pub(crate) dnsv4: Vec<Ipv4Addr>,
44    #[serde(with = "addr_list")]
45    pub(crate) dnsv6: Vec<Ipv6Addr>,
46    #[serde(flatten)]
47    pub(crate) versioned_properties: VersionedProperties,
48}
49
50#[derive(Clone, Copy, Debug, Deserialize_repr, PartialEq, Serialize_repr)]
51#[repr(u8)]
52pub(crate) enum TransportType {
53    Cellular = 0,
54    Wifi = 1,
55    Bluetooth = 2,
56    Ethernet = 3,
57    Vpn = 4,
58    WifiAware = 5,
59    Lowpan = 6,
60}
61
62#[derive(Clone, Copy, Debug, Deserialize_repr, PartialEq, Serialize_repr)]
63#[repr(u8)]
64pub(crate) enum NetworkCapability {
65    Mms = 0,
66    Supl = 1,
67    Dun = 2,
68    Fota = 3,
69    Ims = 4,
70    Cbs = 5,
71    WifiP2p = 6,
72    Ia = 7,
73    Rcs = 8,
74    Xcap = 9,
75    Eims = 10,
76    NotMetered = 11,
77    Internet = 12,
78    NotRestricted = 13,
79    Trusted = 14,
80    NotVpn = 15,
81    Validated = 16,
82    CaptivePortal = 17,
83    NotRoaming = 18,
84    Foreground = 19,
85    NotCongested = 20,
86    NotSuspended = 21,
87    OemPaid = 22,
88    Mcs = 23,
89    PartialConnectivity = 24,
90    TemporarilyNotMetered = 25,
91    OemPrivate = 26,
92    VehicleInternal = 27,
93    NotVcnManaged = 28,
94    Enterprise = 29,
95    Vsim = 30,
96    Bip = 31,
97    HeadUnit = 32,
98    Mmtel = 33,
99    PrioritizeLatency = 34,
100    PrioritizeBandwidth = 35,
101    LocalNetwork = 36,
102    NotBandwidthConstrained = 37,
103    PrioritizeUnifiedCommunications = 38,
104}
105
106#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
107#[serde(tag = "version")]
108pub(crate) enum VersionedProperties {
109    #[default]
110    V1,
111    V2 {
112        #[serde(with = "transport_list")]
113        transports: Vec<TransportType>,
114        #[serde(with = "capability_list")]
115        capabilities: Vec<NetworkCapability>,
116        name: String,
117        // Whether or not there is a v4/v6 address assigned for
118        // the underlying link properties.
119        addrv4: bool,
120        addrv6: bool,
121        // Whether or not there is a v4/v6 default route for
122        // the underlying link properties.
123        defaultv4: bool,
124        defaultv6: bool,
125    },
126}
127
128pub struct FuchsiaNetworkMonitorFs;
129impl FuchsiaNetworkMonitorFs {
130    pub fn new_fs<L>(
131        locked: &mut Locked<L>,
132        kernel: &Kernel,
133        options: FileSystemOptions,
134    ) -> Result<FileSystemHandle, Errno>
135    where
136        L: LockEqualOrBefore<FileOpsCore>,
137    {
138        let fs = FileSystem::new(
139            locked,
140            kernel,
141            CacheMode::Permanent,
142            FuchsiaNetworkMonitorFs,
143            options,
144        )?;
145        let root_ino = fs.allocate_ino();
146        fs.create_root(root_ino, NetworkDirectoryNode::new());
147        Ok(fs)
148    }
149}
150
151const FUCHSIA_NETWORK_MONITOR_FS_NAME: &[u8; 26] = b"fuchsia_network_monitor_fs";
152const FUCHSIA_NETWORK_MONITOR_FS_MAGIC: u32 = u32::from_be_bytes(*b"nmfs");
153
154impl FileSystemOps for FuchsiaNetworkMonitorFs {
155    fn statfs(
156        &self,
157        _locked: &mut Locked<FileOpsCore>,
158        _fs: &FileSystem,
159        _current_task: &CurrentTask,
160    ) -> Result<statfs, Errno> {
161        Ok(default_statfs(FUCHSIA_NETWORK_MONITOR_FS_MAGIC))
162    }
163
164    fn name(&self) -> &'static FsStr {
165        FUCHSIA_NETWORK_MONITOR_FS_NAME.into()
166    }
167}
168
169pub fn fuchsia_network_monitor_fs(
170    locked: &mut Locked<Unlocked>,
171    current_task: &CurrentTask,
172    options: FileSystemOptions,
173) -> Result<FileSystemHandle, Errno> {
174    struct FuchsiaNetworkMonitorFsHandle(FileSystemHandle);
175
176    let kernel = current_task.kernel();
177    Ok(kernel
178        .expando
179        .get_or_try_init(|| {
180            FuchsiaNetworkMonitorFs::new_fs(locked, kernel, options)
181                .map(|fs| FuchsiaNetworkMonitorFsHandle(fs))
182        })?
183        .0
184        .clone())
185}
186
187// Get the NetworkManager from the Kernel's Expando.
188//
189// Returns the NetworkManager when available, or EPERM when the NetworkManager
190// is absent.
191fn try_acquire_network_manager(current_task: &CurrentTask) -> Result<Arc<NetworkManager>, Errno> {
192    let kernel = current_task.kernel();
193    kernel.expando.peek::<NetworkManager>().ok_or_else(|| errno!(EPERM))
194}
195
196pub struct NetworkDirectoryNode;
197
198impl NetworkDirectoryNode {
199    pub fn new() -> Self {
200        Self
201    }
202}
203
204impl FsNodeOps for NetworkDirectoryNode {
205    fn create_file_ops(
206        &self,
207        _locked: &mut Locked<FileOpsCore>,
208        _node: &FsNode,
209        _current_task: &CurrentTask,
210        _flags: OpenFlags,
211    ) -> Result<Box<dyn FileOps>, Errno> {
212        Ok(Box::new(MemoryDirectoryFile::new()))
213    }
214
215    fn mkdir(
216        &self,
217        _locked: &mut Locked<FileOpsCore>,
218        _node: &FsNode,
219        _current_task: &CurrentTask,
220        _name: &FsStr,
221        _mode: FileMode,
222        _owner: FsCred,
223    ) -> Result<FsNodeHandle, Errno> {
224        error!(EPERM)
225    }
226
227    fn mknod(
228        &self,
229        _locked: &mut Locked<FileOpsCore>,
230        node: &FsNode,
231        current_task: &CurrentTask,
232        name: &FsStr,
233        mode: FileMode,
234        _dev: DeviceType,
235        _owner: FsCred,
236    ) -> Result<FsNodeHandle, Errno> {
237        if !mode.is_reg() {
238            return error!(EACCES);
239        }
240
241        let ops: Box<dyn FsNodeOps> = if name == DEFAULT_NETWORK_FILE_NAME {
242            // The node with DEFAULT_NETWORK_FILE_NAME is special and can
243            // only be written to with network ids.
244            Box::new(DefaultNetworkIdFile::new_node())
245        } else {
246            let id: u32 = parse(name).map_err(|_| errno!(EINVAL))?;
247            // Insert a new network entry, but don't populate any fields.
248            let network_manager = try_acquire_network_manager(current_task)?;
249            // This call should only occur on the first node with this name,
250            // so this call isn't expected to fail.
251            network_manager.add_empty_network(id)?;
252            Box::new(NetworkFile::new_node(id))
253        };
254
255        let child = node.fs().create_node_and_allocate_node_id(
256            ops,
257            FsNodeInfo::new(mode, current_task.current_fscred()),
258        );
259
260        Ok(child)
261    }
262
263    fn unlink(
264        &self,
265        _locked: &mut Locked<FileOpsCore>,
266        _node: &FsNode,
267        current_task: &CurrentTask,
268        name: &FsStr,
269        _child: &FsNodeHandle,
270    ) -> Result<(), Errno> {
271        let network_manager = try_acquire_network_manager(current_task)?;
272        // Note: direct equality comparisons are easier using FsStr
273        // than using a match block.
274        if name == DEFAULT_NETWORK_FILE_NAME {
275            // Reset the default network when the associated
276            // network file with the same id is unlinked.
277            network_manager.set_default_network_id(None);
278        } else {
279            let id: u32 = parse(name)?;
280            network_manager.remove_network(id)?;
281        }
282
283        Ok(())
284    }
285
286    fn create_symlink(
287        &self,
288        _locked: &mut Locked<FileOpsCore>,
289        _node: &FsNode,
290        _current_task: &CurrentTask,
291        _name: &FsStr,
292        _target: &FsStr,
293        _owner: FsCred,
294    ) -> Result<FsNodeHandle, Errno> {
295        error!(EPERM)
296    }
297}
298
299pub struct NetworkFile {
300    network_id: u32,
301}
302
303impl NetworkFile {
304    pub fn new_node(network_id: u32) -> impl FsNodeOps {
305        BytesFile::new_node(Self { network_id })
306    }
307}
308
309impl BytesFileOps for NetworkFile {
310    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
311        let network: NetworkMessage = serde_json::from_slice(&data).map_err(|e| {
312            log_error!("failed to deserialize network message: {}", e);
313            errno!(EINVAL)
314        })?;
315
316        let new_netid = network.netid;
317
318        // The network id must be the same as the id listed in the JSON.
319        if new_netid != self.network_id {
320            return error!(EINVAL);
321        }
322
323        let network_manager = try_acquire_network_manager(current_task)?;
324        match network_manager.get_network(&new_netid) {
325            None | Some(None) => {
326                network_manager.add_network(network)?;
327            }
328            Some(Some(_old_network)) => {
329                network_manager.update_network(network)?;
330            }
331        }
332
333        Ok(())
334    }
335
336    fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
337        let network_manager = try_acquire_network_manager(current_task)?;
338        // Verify whether the network exists before reading.
339        if let None = network_manager.get_network(&self.network_id) {
340            return error!(ENOENT);
341        }
342
343        Ok(network_manager.get_network_by_id_as_bytes(self.network_id).to_vec().into())
344    }
345}
346
347pub struct DefaultNetworkIdFile {}
348
349impl DefaultNetworkIdFile {
350    pub fn new_node() -> impl FsNodeOps {
351        BytesFile::new_node(Self {})
352    }
353}
354
355impl BytesFileOps for DefaultNetworkIdFile {
356    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
357        let id_string = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
358        let id: u32 = id_string.parse().map_err(|_| errno!(EINVAL))?;
359
360        {
361            let network_manager = try_acquire_network_manager(current_task)?;
362            match network_manager.get_network(&id) {
363                // A network with the provided id must already
364                // exist to become the default network.
365                Some(Some(_)) => {
366                    network_manager.set_default_network_id(Some(id));
367                }
368                // The network properties must be provided for
369                // a network before it can become the default.
370                Some(None) | None => return error!(ENOENT),
371            };
372        }
373        Ok(())
374    }
375
376    fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
377        let network_manager = try_acquire_network_manager(current_task)?;
378        if let None = network_manager.get_default_network_id() {
379            return error!(ENOENT);
380        }
381
382        Ok(network_manager.get_default_id_as_bytes().to_vec().into())
383    }
384}
385
386impl From<&NetworkMessage> for fnp_socketproxy::Network {
387    fn from(message: &NetworkMessage) -> Self {
388        let (transports, capabilities, name, addrv4, addrv6, defaultv4, defaultv6) =
389            match &message.versioned_properties {
390                VersionedProperties::V1 => (None, None, None, None, None, None, None),
391                VersionedProperties::V2 {
392                    transports,
393                    capabilities,
394                    name,
395                    addrv4,
396                    addrv6,
397                    defaultv4,
398                    defaultv6,
399                } => (
400                    Some(transports),
401                    Some(capabilities),
402                    Some(name),
403                    Some(addrv4),
404                    Some(addrv6),
405                    Some(defaultv4),
406                    Some(defaultv6),
407                ),
408            };
409
410        Self {
411            network_id: Some(message.netid),
412            info: Some(fnp_socketproxy::NetworkInfo::Starnix(
413                fnp_socketproxy::StarnixNetworkInfo {
414                    mark: Some(message.mark),
415                    handle: Some(message.handle),
416                    ..Default::default()
417                },
418            )),
419            dns_servers: Some(fnp_socketproxy::NetworkDnsServers {
420                v4: Some(
421                    message
422                        .dnsv4
423                        .clone()
424                        .into_iter()
425                        .map(|a| fnet::Ipv4Address { addr: a.octets() })
426                        .collect::<Vec<_>>(),
427                ),
428                v6: Some(
429                    message
430                        .dnsv6
431                        .clone()
432                        .into_iter()
433                        .map(|a| fnet::Ipv6Address { addr: a.octets() })
434                        .collect::<Vec<_>>(),
435                ),
436                ..Default::default()
437            }),
438            network_type: transports
439                .map(|transport_list| consolidate_transport_types_to_network_type(transport_list)),
440            capabilities: capabilities.map(|capability_list| convert_capabilities(capability_list)),
441            connectivity: capabilities
442                .map(|capability_list| convert_connectivity_state(capability_list)),
443            name: name.cloned(),
444            has_address: Some(proto_properties_from_v4_v6(addrv4.copied(), addrv6.copied())),
445            has_default_route: Some(proto_properties_from_v4_v6(
446                defaultv4.copied(),
447                defaultv6.copied(),
448            )),
449            ..Default::default()
450        }
451    }
452}
453
454fn proto_properties_from_v4_v6(v4: Option<bool>, v6: Option<bool>) -> ProtoProperties {
455    let mut properties = ProtoProperties::empty();
456
457    if Some(true) == v4 {
458        properties |= ProtoProperties::V4;
459    }
460
461    if Some(true) == v6 {
462        properties |= ProtoProperties::V6;
463    }
464    properties
465}
466
467// Given a list of `TransportType`, convert it to a single `NetworkType`. Only a single
468// transport is expected, so take the first one if multiple are present. If none are
469// present, Unknown is used as a fallback.
470fn consolidate_transport_types_to_network_type(
471    value: &Vec<TransportType>,
472) -> fnp_socketproxy::NetworkType {
473    value
474        .iter()
475        .filter_map(|tt| maybe_network_type_from_transport_type(*tt))
476        .next()
477        .unwrap_or(fnp_socketproxy::NetworkType::Unknown)
478}
479
480fn maybe_network_type_from_transport_type(
481    value: TransportType,
482) -> Option<fnp_socketproxy::NetworkType> {
483    match value {
484        TransportType::Ethernet => Some(fnp_socketproxy::NetworkType::Ethernet),
485        TransportType::Wifi => Some(fnp_socketproxy::NetworkType::Wifi),
486        TransportType::Bluetooth => Some(fnp_socketproxy::NetworkType::Bluetooth),
487        TransportType::Cellular => Some(fnp_socketproxy::NetworkType::Cellular),
488        TransportType::Vpn | TransportType::WifiAware | TransportType::Lowpan => {
489            log_warn!("Known NetworkType {value:?} is not currently supported");
490            None
491        }
492    }
493}
494
495// Currently, we only check for the following capabilities: NotMetered, NotCongested,
496// NotBandwidthConstrained. If we wish to have more knowledge of the offered capabilities,
497// we can consider adding fields to the fnp_socketproxy::NetworkRegistry FIDL API.
498fn convert_capabilities(value: &Vec<NetworkCapability>) -> Vec<fnp_socketproxy::NetworkCapability> {
499    value
500        .iter()
501        .filter_map(|c| match c {
502            NetworkCapability::NotMetered => Some(fnp_socketproxy::NetworkCapability::UNMETERED),
503            NetworkCapability::NotCongested => {
504                Some(fnp_socketproxy::NetworkCapability::UNCONGESTED)
505            }
506            NetworkCapability::NotBandwidthConstrained => {
507                Some(fnp_socketproxy::NetworkCapability::NOT_BANDWIDTH_CONSTRAINED)
508            }
509            _ => None,
510        })
511        .collect::<Vec<_>>()
512}
513
514fn convert_connectivity_state(
515    value: &Vec<NetworkCapability>,
516) -> fnp_socketproxy::ConnectivityState {
517    // Validated and Internet capabilities indicate that the network has been
518    // validated to have internet connectivity (HTTP and DNS).
519    if value.contains(&NetworkCapability::Validated) && value.contains(&NetworkCapability::Internet)
520    {
521        return fnp_socketproxy::ConnectivityState::FullConnectivity;
522    }
523
524    if value.contains(&NetworkCapability::PartialConnectivity) {
525        return fnp_socketproxy::ConnectivityState::PartialConnectivity;
526    }
527
528    if value.contains(&NetworkCapability::LocalNetwork) {
529        return fnp_socketproxy::ConnectivityState::LocalConnectivity;
530    }
531
532    return fnp_socketproxy::ConnectivityState::NoConnectivity;
533}
534
535mod addr_list {
536    use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
537
538    pub fn serialize<S, Addr>(addr: &Vec<Addr>, serializer: S) -> Result<S::Ok, S::Error>
539    where
540        S: Serializer,
541        Addr: std::fmt::Display,
542    {
543        let strings = addr.iter().map(|x| x.to_string()).collect::<Vec<_>>();
544        strings.serialize(serializer)
545    }
546
547    pub fn deserialize<'de, D, Addr>(deserializer: D) -> Result<Vec<Addr>, D::Error>
548    where
549        D: Deserializer<'de>,
550        Addr: std::str::FromStr,
551        Addr::Err: std::fmt::Display,
552    {
553        let s = Vec::<String>::deserialize(deserializer)?;
554        s.into_iter().map(|s| s.parse().map_err(de::Error::custom)).collect()
555    }
556}
557
558macro_rules! tolerant_repr_serde_impl {
559    ($module_name:ident, $NewType:path) => {
560        mod $module_name {
561            use serde::{Deserialize, Deserializer, Serialize, Serializer};
562            use starnix_logging::log_error;
563
564            pub fn serialize<S>(items: &Vec<$NewType>, serializer: S) -> Result<S::Ok, S::Error>
565            where
566                S: Serializer,
567            {
568                let nums = items.iter().map(|item| *item as u8).collect::<Vec<_>>();
569                nums.serialize(serializer)
570            }
571
572            pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<$NewType>, D::Error>
573            where
574                D: Deserializer<'de>,
575            {
576                let vals = Vec::<serde_json::Value>::deserialize(deserializer)?;
577                Ok(vals
578                    .into_iter()
579                    .filter_map(|val| match serde_json::from_value::<$NewType>(val.clone()) {
580                        Ok(s) => Some(s),
581                        Err(_) => {
582                            log_error!("unknown value: {}", val);
583                            None
584                        }
585                    })
586                    .collect())
587            }
588        }
589    };
590}
591
592tolerant_repr_serde_impl!(transport_list, crate::TransportType);
593tolerant_repr_serde_impl!(capability_list, crate::NetworkCapability);
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598    use net_declare::{std_ip_v4, std_ip_v6};
599    use test_case::test_case;
600
601    #[::fuchsia::test]
602    fn network_message_serde() {
603        let network = NetworkMessage {
604            netid: 123,
605            mark: 456,
606            handle: 789,
607            dnsv4: vec![std_ip_v4!("192.168.0.1")],
608            dnsv6: vec![std_ip_v6!("2001:db8::1")],
609            versioned_properties: VersionedProperties::V1,
610        };
611        serde_helper(network);
612    }
613
614    #[::fuchsia::test]
615    fn network_message_serde_version2() {
616        let network = NetworkMessage {
617            netid: 123,
618            mark: 456,
619            handle: 789,
620            dnsv4: vec![std_ip_v4!("192.168.0.1")],
621            dnsv6: vec![std_ip_v6!("2001:db8::1")],
622            versioned_properties: VersionedProperties::V2 {
623                transports: vec![
624                    TransportType::Cellular,
625                    TransportType::Wifi,
626                    TransportType::Bluetooth,
627                    TransportType::Ethernet,
628                    TransportType::Vpn,
629                    TransportType::WifiAware,
630                    TransportType::Lowpan,
631                ],
632                capabilities: vec![
633                    NetworkCapability::Trusted,
634                    NetworkCapability::Internet,
635                    NetworkCapability::Validated,
636                ],
637                name: "test01".to_string(),
638                addrv4: false,
639                addrv6: true,
640                defaultv4: false,
641                defaultv6: true,
642            },
643        };
644        serde_helper(network);
645    }
646
647    #[::fuchsia::test]
648    fn network_message_serde_unknown_transports_and_capabilities() {
649        let json = r#"{
650            "netid": 123,
651            "mark": 456,
652            "handle": 789,
653            "dnsv4": ["192.168.0.1"],
654            "dnsv6": ["2001:db8::1"],
655            "version": "V2",
656            "transports": [1, 99, 3],
657            "capabilities": [11, 99],
658            "name": "test01",
659            "addrv4": false,
660            "addrv6": false,
661            "defaultv4": false,
662            "defaultv6": false
663        }"#;
664
665        let expected = NetworkMessage {
666            netid: 123,
667            mark: 456,
668            handle: 789,
669            dnsv4: vec![std_ip_v4!("192.168.0.1")],
670            dnsv6: vec![std_ip_v6!("2001:db8::1")],
671            versioned_properties: VersionedProperties::V2 {
672                transports: vec![TransportType::Wifi, TransportType::Ethernet],
673                capabilities: vec![NetworkCapability::NotMetered],
674                name: "test01".to_string(),
675                addrv4: false,
676                addrv6: false,
677                defaultv4: false,
678                defaultv6: false,
679            },
680        };
681
682        let deserialized: NetworkMessage = serde_json::from_str(json).unwrap();
683        assert_eq!(expected, deserialized);
684    }
685
686    // Ensure that a provided NetworkMessage can be serialized and
687    // deserialized into the original form.
688    fn serde_helper(network: NetworkMessage) {
689        let serialized = serde_json::to_string(&network).unwrap_or_else(|_| "{}".to_string());
690        let deserialized = serde_json::from_str(&serialized).unwrap();
691        assert_eq!(network, deserialized);
692    }
693
694    #[test_case(vec![TransportType::Cellular], fnp_socketproxy::NetworkType::Cellular;
695        "cellular")]
696    #[test_case(vec![TransportType::Wifi], fnp_socketproxy::NetworkType::Wifi;
697        "wifi")]
698    #[test_case(vec![TransportType::Bluetooth], fnp_socketproxy::NetworkType::Bluetooth;
699        "bluetooth")]
700    #[test_case(vec![TransportType::Ethernet], fnp_socketproxy::NetworkType::Ethernet;
701        "ethernet")]
702    #[test_case(
703        vec![
704            TransportType::Wifi,
705            TransportType::Cellular
706        ],
707        fnp_socketproxy::NetworkType::Wifi; "first_transport_prioritized")]
708    #[test_case(vec![], fnp_socketproxy::NetworkType::Unknown; "empty_fallback")]
709    #[test_case(
710        vec![
711            TransportType::Lowpan,
712            TransportType::WifiAware,
713            TransportType::Vpn
714        ],
715        fnp_socketproxy::NetworkType::Unknown; "none_applicable")]
716    #[::fuchsia::test]
717    fn test_consolidate_transport_types_to_network_type(
718        transports: Vec<TransportType>,
719        expected: fnp_socketproxy::NetworkType,
720    ) {
721        assert_eq!(consolidate_transport_types_to_network_type(&transports), expected);
722    }
723
724    #[test_case(
725        vec![
726            NetworkCapability::NotMetered,
727            NetworkCapability::NotCongested,
728            NetworkCapability::NotBandwidthConstrained,
729        ],
730        vec![
731            fnp_socketproxy::NetworkCapability::UNMETERED,
732            fnp_socketproxy::NetworkCapability::UNCONGESTED,
733            fnp_socketproxy::NetworkCapability::NOT_BANDWIDTH_CONSTRAINED,
734        ];
735        "all_capabilities"
736    )]
737    #[test_case(
738        vec![NetworkCapability::NotMetered],
739        vec![fnp_socketproxy::NetworkCapability::UNMETERED];
740        "one_capability"
741    )]
742    #[test_case(
743        vec![],
744        vec![];
745        "no_capabilities"
746    )]
747    #[test_case(
748        vec![
749            NetworkCapability::NotMetered,
750            NetworkCapability::Trusted, // This should be ignored.
751        ],
752        vec![fnp_socketproxy::NetworkCapability::UNMETERED];
753        "ignore_unrelated"
754    )]
755    #[::fuchsia::test]
756    fn test_convert_capabilities(
757        capabilities: Vec<NetworkCapability>,
758        expected: Vec<fnp_socketproxy::NetworkCapability>,
759    ) {
760        let mut result = convert_capabilities(&capabilities);
761        result.sort();
762        let mut expected_sorted = expected;
763        expected_sorted.sort();
764        assert_eq!(result, expected_sorted);
765    }
766
767    #[test_case(
768        vec![
769            NetworkCapability::Validated,
770            NetworkCapability::Internet,
771        ],
772        fnp_socketproxy::ConnectivityState::FullConnectivity;
773        "full_connectivity"
774    )]
775    #[test_case(
776        vec![
777            NetworkCapability::Validated,
778            NetworkCapability::Internet,
779            NetworkCapability::LocalNetwork,
780        ],
781        fnp_socketproxy::ConnectivityState::FullConnectivity;
782        "full_connectivity_prioritized"
783    )]
784    #[test_case(
785        vec![NetworkCapability::PartialConnectivity],
786        fnp_socketproxy::ConnectivityState::PartialConnectivity;
787        "partial_connectivity"
788    )]
789    #[test_case(
790        vec![NetworkCapability::LocalNetwork],
791        fnp_socketproxy::ConnectivityState::LocalConnectivity;
792        "local_connectivity"
793    )]
794    #[test_case(
795        vec![],
796        fnp_socketproxy::ConnectivityState::NoConnectivity;
797        "no_connectivity"
798    )]
799    #[test_case(
800        vec![NetworkCapability::Trusted], // This should be ignored.
801        fnp_socketproxy::ConnectivityState::NoConnectivity;
802        "ignore_unrelated"
803    )]
804    #[::fuchsia::test]
805    fn test_convert_connectivity_state(
806        capabilities: Vec<NetworkCapability>,
807        expected: fnp_socketproxy::ConnectivityState,
808    ) {
809        assert_eq!(convert_connectivity_state(&capabilities), expected);
810    }
811}