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