1use 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 addrv4: bool,
120 addrv6: bool,
121 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
187fn 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 Box::new(DefaultNetworkIdFile::new_node())
245 } else {
246 let id: u32 = parse(name).map_err(|_| errno!(EINVAL))?;
247 let network_manager = try_acquire_network_manager(current_task)?;
249 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 if name == DEFAULT_NETWORK_FILE_NAME {
275 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 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 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 Some(Some(_)) => {
366 network_manager.set_default_network_id(Some(id));
367 }
368 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
467fn 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
495fn 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 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 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, ],
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], 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}