1use crate::{NetworkMessage, fuchsia_network_monitor_fs};
6use bstr::BString;
7use fuchsia_component::client::connect_to_protocol_sync;
8use fuchsia_inspect_derive::{IValue, Inspect, Unit, WithInspect};
9use starnix_core::task::Kernel;
10use starnix_core::vfs::fs_registry::FsRegistry;
11use starnix_logging::{log_error, log_info};
12use starnix_sync::{LockDepGuard, LockDepMutex, NmfsNetworkManagerLock};
13use starnix_uapi::error;
14use starnix_uapi::errors::Errno;
15use starnix_uapi::fs_type::FileSystemTypeFlags;
16use std::collections::HashMap;
17use std::collections::hash_map::Entry;
18use thiserror::Error;
19
20use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
21
22#[derive(Inspect)]
24pub(crate) struct NetworkManager {
25 network_registry: fnp_socketproxy::NetworkRegistrySynchronousProxy,
26 #[inspect(forward)]
27 inner: LockDepMutex<IValue<NetworkManagerInner>, NmfsNetworkManagerLock>,
28}
29
30#[derive(Unit, Default)]
31struct NetworkManagerInner {
32 #[inspect(skip)]
34 default_id: Option<u32>,
35 #[inspect(skip)]
36 networks: HashMap<u32, Option<NetworkMessage>>,
37
38 default_ids_set: SeenSentData,
39 added_networks: SeenSentData,
40 updated_networks: SeenSentData,
41 removed_networks: SeenSentData,
42}
43
44#[derive(Unit, Default)]
45struct SeenSentData {
46 seen: u64,
49 sent: u64,
52}
53
54pub fn nmfs_init(kernel: &Kernel) -> Result<(), anyhow::Error> {
56 let registry = kernel.expando.get::<FsRegistry>();
58 registry.register(
59 b"fuchsia_network_monitor_fs".into(),
60 FileSystemTypeFlags::empty(),
61 fuchsia_network_monitor_fs,
62 );
63
64 let network_registry = connect_to_protocol_sync::<fnp_socketproxy::NetworkRegistryMarker>()?;
66 kernel
67 .expando
68 .get_or_init(|| NetworkManager::new_with_proxy(network_registry, &kernel.inspect_node));
69 Ok(())
70}
71
72impl NetworkManager {
76 pub(crate) fn new_with_proxy(
78 proxy: fnp_socketproxy::NetworkRegistrySynchronousProxy,
79 node: &fuchsia_inspect::Node,
80 ) -> Self {
81 Self { network_registry: proxy, inner: Default::default() }
82 .with_inspect(node, "nmfs")
83 .expect("Failed to attach 'nmfs' node")
84 }
85
86 fn lock(&self) -> LockDepGuard<'_, IValue<NetworkManagerInner>> {
88 self.inner.lock()
89 }
90
91 pub(crate) fn get_default_network_id(&self) -> Option<u32> {
92 self.lock().default_id
93 }
94
95 pub(crate) fn get_network(&self, network_id: &u32) -> Option<Option<NetworkMessage>> {
96 self.lock().networks.get(network_id).cloned()
97 }
98
99 pub(crate) fn get_default_id_as_bytes(&self) -> BString {
100 let default_id = match self.get_default_network_id() {
101 Some(id) => id.to_string(),
102 None => "".to_string(),
103 };
104 default_id.into_bytes().into()
105 }
106
107 pub(crate) fn get_network_by_id_as_bytes(&self, network_id: u32) -> BString {
108 let network_info = match self.get_network(&network_id) {
109 Some(Some(network)) => {
110 serde_json::to_string(&network).unwrap_or_else(|_| "{}".to_string())
111 }
112 Some(None) | None => "{}".to_string(),
115 };
116 network_info.into_bytes().into()
117 }
118
119 pub(crate) fn set_default_network_id(&self, network_id: Option<u32>) {
122 {
123 let mut inner_guard = self.lock();
124 let mut inner = inner_guard.as_mut();
125 inner.default_id = network_id;
126 inner.default_ids_set.seen += 1;
127 }
128
129 match self.fidl_set_default_network_id(network_id) {
131 Ok(()) => {
132 log_info!("Successfully set network with id {network_id:?} as default in netcfg",);
133 let mut inner_guard = self.lock();
134 inner_guard.as_mut().default_ids_set.sent += 1;
135 }
136 Err(e) => {
137 log_error!(
138 "Failed to set network with id {network_id:?} as default in netcfg; {e:?}"
139 );
140 }
141 }
142 }
143
144 pub(crate) fn add_empty_network(&self, network_id: u32) -> Result<(), Errno> {
150 let mut inner_guard = self.lock();
151 match inner_guard.as_mut().networks.entry(network_id) {
152 Entry::Occupied(_) => {
153 log_error!(
154 "Failed to add empty network to HashMap, was present for id: {}",
155 network_id
156 );
157 return error!(EEXIST);
158 }
159 Entry::Vacant(entry) => entry.insert(None),
160 };
161 Ok(())
162 }
163
164 pub(crate) fn add_network(&self, network: NetworkMessage) -> Result<(), Errno> {
169 {
170 let mut inner_guard = self.lock();
171 let mut inner = inner_guard.as_mut();
172 match inner.networks.entry(network.netid) {
173 Entry::Occupied(mut entry) => {
174 if let Some(network) = entry.get() {
177 log_error!(
178 "Failed to add network with id {} to HashMap, already existed",
179 network.netid
180 );
181 return error!(EEXIST);
182 }
183 let _ = entry.insert(Some(network.clone()));
184 }
185 Entry::Vacant(entry) => {
186 let _ = entry.insert(Some(network.clone()));
187 }
188 }
189 inner.added_networks.seen += 1;
190 }
191
192 match self.fidl_add_network(&fnp_socketproxy::Network::from(&network)) {
194 Ok(()) => {
195 log_info!("Successfully added network with id {} to netcfg", network.netid);
196 let mut inner_guard = self.lock();
197 inner_guard.as_mut().added_networks.sent += 1;
198 }
199 Err(e) => {
200 log_error!("Failed to add network with id {:?} to netcfg; {e:?}", network.netid);
201 }
202 }
203
204 Ok(())
205 }
206
207 pub(crate) fn update_network(&self, network: NetworkMessage) -> Result<(), Errno> {
212 {
213 let mut inner_guard = self.lock();
214 let mut inner = inner_guard.as_mut();
215 let _old_network = match inner.networks.entry(network.netid) {
218 Entry::Occupied(mut entry) => {
219 if let None = entry.get() {
220 return error!(ENOENT);
221 }
222 entry.insert(Some(network.clone()))
223 }
224 Entry::Vacant(_) => {
225 return error!(ENOENT);
226 }
227 };
228 inner.updated_networks.seen += 1;
229 };
230
231 match self.fidl_update_network(&fnp_socketproxy::Network::from(&network)) {
233 Ok(()) => {
234 log_info!("Successfully updated network with id {} in netcfg", network.netid);
235 let mut inner_guard = self.lock();
236 inner_guard.as_mut().updated_networks.sent += 1;
237 }
238 Err(e) => {
239 log_error!("Failed to update network with id {} in netcfg; {e:?}", network.netid);
240 }
241 }
242
243 Ok(())
244 }
245
246 pub(crate) fn remove_network(&self, network_id: u32) -> Result<(), Errno> {
251 let default_network_id = self.get_default_network_id();
254 if let Some(id) = default_network_id {
255 if id == network_id {
256 return error!(EPERM);
257 }
258 }
259 {
260 let mut inner_guard = self.lock();
261 let mut inner = inner_guard.as_mut();
262 if let None = inner.networks.remove(&network_id) {
263 return error!(ENOENT);
264 }
265 inner.removed_networks.seen += 1;
266 }
267
268 match self.fidl_remove_network(&network_id) {
270 Ok(()) => {
271 log_info!("Successfully removed network with id {network_id} from netcfg",);
272 let mut inner_guard = self.lock();
273 inner_guard.as_mut().removed_networks.sent += 1;
274 }
275 Err(e) => {
276 log_error!("Failed to remove network with id {network_id} in netcfg; {e:?}");
277 }
278 }
279 Ok(())
280 }
281
282 fn fidl_set_default_network_id(
284 &self,
285 network_id: Option<u32>,
286 ) -> Result<(), NetworkManagerError> {
287 let network_id = match network_id {
288 Some(id) => fidl_fuchsia_posix_socket::OptionalUint32::Value(id),
289 None => {
290 fidl_fuchsia_posix_socket::OptionalUint32::Unset(fidl_fuchsia_posix_socket::Empty)
291 }
292 };
293 Ok(self.network_registry.set_default(&network_id, zx::MonotonicInstant::INFINITE)??)
294 }
295
296 fn fidl_add_network(
298 &self,
299 network: &fnp_socketproxy::Network,
300 ) -> Result<(), NetworkManagerError> {
301 Ok(self.network_registry.add(&network, zx::MonotonicInstant::INFINITE)??)
302 }
303
304 fn fidl_update_network(
306 &self,
307 network: &fnp_socketproxy::Network,
308 ) -> Result<(), NetworkManagerError> {
309 Ok(self.network_registry.update(&network, zx::MonotonicInstant::INFINITE)??)
310 }
311
312 fn fidl_remove_network(&self, network_id: &u32) -> Result<(), NetworkManagerError> {
314 Ok(self.network_registry.remove(*network_id, zx::MonotonicInstant::INFINITE)??)
315 }
316}
317
318#[derive(Clone, Debug, Error)]
321pub(crate) enum NetworkManagerError {
322 #[error("Error during netcfg Add: {0:?}")]
323 Add(fnp_socketproxy::NetworkRegistryAddError),
324 #[error("Error calling FIDL on netcfg: {0:?}")]
325 Fidl(#[from] fidl::Error),
326 #[error("Error during netcfg Remove: {0:?}")]
327 Remove(fnp_socketproxy::NetworkRegistryRemoveError),
328 #[error("Error during netcfg SetDefault: {0:?}")]
329 SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError),
330 #[error("Error during netcfg Update: {0:?}")]
331 Update(fnp_socketproxy::NetworkRegistryUpdateError),
332}
333
334impl From<fnp_socketproxy::NetworkRegistryAddError> for NetworkManagerError {
335 fn from(error: fnp_socketproxy::NetworkRegistryAddError) -> Self {
336 NetworkManagerError::Add(error)
337 }
338}
339
340impl From<fnp_socketproxy::NetworkRegistryRemoveError> for NetworkManagerError {
341 fn from(error: fnp_socketproxy::NetworkRegistryRemoveError) -> Self {
342 NetworkManagerError::Remove(error)
343 }
344}
345
346impl From<fnp_socketproxy::NetworkRegistrySetDefaultError> for NetworkManagerError {
347 fn from(error: fnp_socketproxy::NetworkRegistrySetDefaultError) -> Self {
348 NetworkManagerError::SetDefault(error)
349 }
350}
351
352impl From<fnp_socketproxy::NetworkRegistryUpdateError> for NetworkManagerError {
353 fn from(error: fnp_socketproxy::NetworkRegistryUpdateError) -> Self {
354 NetworkManagerError::Update(error)
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 use assert_matches::assert_matches;
363 use diagnostics_assertions::assert_data_tree;
364 use futures::StreamExt as _;
365 use starnix_uapi::{EEXIST, ENOENT};
366 use test_case::test_case;
367
368 fn test_network_message_from_id(netid: u32) -> NetworkMessage {
369 NetworkMessage { netid, ..Default::default() }
370 }
371
372 #[::fuchsia::test]
373 async fn test_add_empty_network() {
374 let inspector = fuchsia_inspect::Inspector::default();
375 let manager = &setup_proxy(inspector.root(), vec![]);
376
377 let network_id = 1;
378 assert_matches!(manager.add_empty_network(network_id), Ok(()));
379
380 assert_matches!(
382 manager.add_empty_network(network_id),
383 Err(errno) if errno.code.error_code() == EEXIST
384 );
385 assert_matches!(manager.get_network(&network_id), Some(None));
386 assert_data_tree!(inspector, root: {
389 nmfs: contains {
390 added_networks: {
391 seen: 0u64,
392 sent: 0u64,
393 },
394 },
395 });
396 }
397
398 fn setup_proxy(
401 inspect_node: &fuchsia_inspect::Node,
402 results: Vec<Result<(), NetworkManagerError>>,
403 ) -> NetworkManager {
404 let (proxy, mut stream) = fidl::endpoints::create_sync_proxy_and_stream::<
405 fnp_socketproxy::NetworkRegistryMarker,
406 >();
407 let manager = NetworkManager::new_with_proxy(proxy, inspect_node);
408
409 let mut results = results.into_iter();
410 fuchsia_async::Task::spawn(async move {
411 while let Some(item) = stream.next().await {
412 let result = results
413 .next()
414 .expect("there should be an equivalent # of results and requests");
415 match item.expect("receive request") {
416 fnp_socketproxy::NetworkRegistryRequest::SetDefault {
417 network_id: _,
418 responder,
419 } => {
420 let res = result.map_err(|e| match e {
421 NetworkManagerError::SetDefault(err) => err,
422 _ => unreachable!("should have been SetDefault error variant"),
423 });
424 responder.send(res).expect("respond to SetDefault");
425 }
426 fnp_socketproxy::NetworkRegistryRequest::Add { network: _, responder } => {
427 let res = result.map_err(|e| match e {
428 NetworkManagerError::Add(err) => err,
429 _ => unreachable!("should have been Add error variant"),
430 });
431 responder.send(res).expect("respond to Add");
432 }
433 fnp_socketproxy::NetworkRegistryRequest::Update { network: _, responder } => {
434 let res = result.map_err(|e| match e {
435 NetworkManagerError::Update(err) => err,
436 _ => unreachable!("should have been Update error variant"),
437 });
438 responder.send(res).expect("respond to Update");
439 }
440 fnp_socketproxy::NetworkRegistryRequest::Remove {
441 network_id: _,
442 responder,
443 } => {
444 let res = result.map_err(|e| match e {
445 NetworkManagerError::Remove(err) => err,
446 _ => unreachable!("should have been Remove error variant"),
447 });
448 responder.send(res).expect("respond to Remove");
449 }
450 }
451 }
452 })
453 .detach();
454 manager
455 }
456
457 struct SeenSentData {
460 seen: u64,
461 sent: u64,
462 }
463
464 #[test_case(vec![Ok(()), Ok(())], SeenSentData { seen: 2, sent: 2 }; "all_success")]
465 #[test_case(
466 vec![
467 Ok(()),
468 Err(NetworkManagerError::SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound)),
469 ],
470 SeenSentData { seen: 2, sent: 1 };
471 "one_error"
472 )]
473 #[test_case(
474 vec![
475 Err(NetworkManagerError::SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound)),
476 Err(NetworkManagerError::SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound)),
477 ],
478 SeenSentData { seen: 2, sent: 0 };
479 "all_errors"
480 )]
481 #[::fuchsia::test(threads = 2)]
482 async fn test_set_default_network_id_with_proxy(
483 results: Vec<Result<(), NetworkManagerError>>,
484 expected_data: SeenSentData,
485 ) {
486 let inspector = fuchsia_inspect::Inspector::default();
487 let manager = &setup_proxy(inspector.root(), results);
488
489 manager.set_default_network_id(Some(1));
490 assert_eq!(manager.get_default_network_id(), Some(1));
491
492 manager.set_default_network_id(Some(2));
493 assert_eq!(manager.get_default_network_id(), Some(2));
494
495 assert_data_tree!(inspector, root: {
496 nmfs: contains {
497 default_ids_set: {
498 seen: expected_data.seen,
499 sent: expected_data.sent,
500 },
501 added_networks: {
502 seen: 0u64,
503 sent: 0u64,
504 },
505 updated_networks: {
506 seen: 0u64,
507 sent: 0u64,
508 },
509 removed_networks: {
510 seen: 0u64,
511 sent: 0u64,
512 },
513 },
514 });
515 }
516
517 #[test_case(vec![Ok(()), Ok(())], SeenSentData { seen: 2, sent: 2 }; "all_success")]
518 #[test_case(
519 vec![
520 Ok(()),
521 Err(NetworkManagerError::Add(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId)),
522 ],
523 SeenSentData { seen: 2, sent: 1 };
524 "one_error"
525 )]
526 #[test_case(
527 vec![
528 Err(NetworkManagerError::Add(fnp_socketproxy::NetworkRegistryAddError::MissingNetworkId)),
529 Err(NetworkManagerError::Add(fnp_socketproxy::NetworkRegistryAddError::MissingNetworkInfo)),
530 ],
531 SeenSentData { seen: 2, sent: 0 };
532 "all_errors"
533 )]
534 #[::fuchsia::test(threads = 2)]
535 async fn test_add_network_with_proxy(
536 results: Vec<Result<(), NetworkManagerError>>,
537 expected_data: SeenSentData,
538 ) {
539 let inspector = fuchsia_inspect::Inspector::default();
540 let manager = &setup_proxy(inspector.root(), results);
541
542 let network1 = test_network_message_from_id(1);
546 assert_matches!(manager.add_network(network1.clone()), Ok(()));
547
548 let network2 = test_network_message_from_id(2);
549 assert_matches!(manager.add_network(network2.clone()), Ok(()));
550
551 assert_matches!(
554 manager.add_network(network2.clone()),
555 Err(errno) if errno.code.error_code() == EEXIST
556 );
557
558 assert_data_tree!(inspector, root: {
559 nmfs: contains {
560 default_ids_set: {
561 seen: 0u64,
562 sent: 0u64,
563 },
564 added_networks: {
565 seen: expected_data.seen,
566 sent: expected_data.sent,
567 },
568 updated_networks: {
569 seen: 0u64,
570 sent: 0u64,
571 },
572 removed_networks: {
573 seen: 0u64,
574 sent: 0u64,
575 },
576 },
577 });
578 }
579
580 #[test_case(vec![Ok(()), Ok(())], SeenSentData { seen: 2, sent: 2 }; "all_success")]
581 #[test_case(
582 vec![
583 Ok(()),
584 Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::MissingNetworkId)),
585 ],
586 SeenSentData { seen: 2, sent: 1 };
587 "one_error"
588 )]
589 #[test_case(
590 vec![
591 Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)),
592 Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::MissingNetworkInfo)),
593 ],
594 SeenSentData { seen: 2, sent: 0 };
595 "all_errors"
596 )]
597 #[::fuchsia::test(threads = 2)]
598 async fn test_update_network_with_proxy(
599 results: Vec<Result<(), NetworkManagerError>>,
600 expected_data: SeenSentData,
601 ) {
602 let inspector = fuchsia_inspect::Inspector::default();
603 let manager = &setup_proxy(inspector.root(), results);
604
605 let network_id = 1;
606 let network = test_network_message_from_id(network_id);
607
608 assert_matches!(
610 manager.update_network(network.clone()),
611 Err(errno) if errno.code.error_code() == ENOENT
612 );
613
614 {
616 let mut inner_guard = manager.lock();
617 let _ = inner_guard.as_mut().networks.insert(network_id, Some(network.clone()));
618 }
619
620 assert_matches!(manager.update_network(network.clone()), Ok(()));
626 assert_matches!(manager.update_network(network), Ok(()));
627
628 assert_data_tree!(inspector, root: {
629 nmfs: contains {
630 default_ids_set: {
631 seen: 0u64,
632 sent: 0u64,
633 },
634 added_networks: {
635 seen: 0u64,
636 sent: 0u64,
637 },
638 updated_networks: {
639 seen: expected_data.seen,
640 sent: expected_data.sent,
641 },
642 removed_networks: {
643 seen: 0u64,
644 sent: 0u64,
645 },
646 },
647 });
648 }
649
650 #[test_case(vec![Ok(())], SeenSentData { seen: 1, sent: 1 }; "success")]
651 #[test_case(
652 vec![
653 Err(NetworkManagerError::Remove(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)),
654 ],
655 SeenSentData { seen: 1, sent: 0 };
656 "error"
657 )]
658 #[::fuchsia::test(threads = 2)]
659 async fn test_remove_network_with_proxy(
660 results: Vec<Result<(), NetworkManagerError>>,
661 expected_data: SeenSentData,
662 ) {
663 let inspector = fuchsia_inspect::Inspector::default();
664 let manager = &setup_proxy(inspector.root(), results);
665
666 let network_id = 1;
667 let network = test_network_message_from_id(network_id);
668
669 assert_matches!(
671 manager.remove_network(network_id),
672 Err(errno) if errno.code.error_code() == ENOENT
673 );
674
675 {
677 let mut inner_guard = manager.lock();
678 let _ = inner_guard.as_mut().networks.insert(network_id, Some(network.clone()));
679 }
680
681 assert_matches!(manager.remove_network(network_id), Ok(()));
685
686 assert_data_tree!(inspector, root: {
687 nmfs: contains {
688 default_ids_set: {
689 seen: 0u64,
690 sent: 0u64,
691 },
692 added_networks: {
693 seen: 0u64,
694 sent: 0u64,
695 },
696 updated_networks: {
697 seen: 0u64,
698 sent: 0u64,
699 },
700 removed_networks: {
701 seen: expected_data.seen,
702 sent: expected_data.sent,
703 },
704 },
705 });
706 }
707
708 #[::fuchsia::test(threads = 2)]
709 async fn test_multiple_operations_with_proxy() {
710 let inspector = fuchsia_inspect::Inspector::default();
711 let results = vec![
712 Err(NetworkManagerError::Add(
714 fnp_socketproxy::NetworkRegistryAddError::MissingNetworkId,
715 )),
716 Ok(()),
718 Err(NetworkManagerError::SetDefault(
720 fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound,
721 )),
722 Ok(()),
724 Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)),
726 Ok(()),
728 Ok(()),
730 Err(NetworkManagerError::Remove(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)),
732 Ok(()),
734 ];
735 let manager = &setup_proxy(inspector.root(), results);
736
737 let network1 = test_network_message_from_id(1);
739 assert_matches!(manager.add_network(network1.clone()), Ok(()));
740 assert_data_tree!(inspector, root: {
741 nmfs: contains {
742 added_networks: {
743 seen: 1u64,
744 sent: 0u64,
745 },
746 },
747 });
748
749 let network2 = test_network_message_from_id(2);
751 assert_matches!(manager.add_network(network2.clone()), Ok(()));
752 assert_data_tree!(inspector, root: {
753 nmfs: contains {
754 added_networks: {
755 seen: 2u64,
756 sent: 1u64,
757 },
758 },
759 });
760
761 manager.set_default_network_id(Some(1));
763 assert_eq!(manager.get_default_network_id(), Some(1));
764 assert_data_tree!(inspector, root: {
765 nmfs: contains {
766 default_ids_set: {
767 seen: 1u64,
768 sent: 0u64,
769 },
770 },
771 });
772
773 manager.set_default_network_id(Some(2));
775 assert_eq!(manager.get_default_network_id(), Some(2));
776 assert_data_tree!(inspector, root: {
777 nmfs: contains {
778 default_ids_set: {
779 seen: 2u64,
780 sent: 1u64,
781 },
782 },
783 });
784
785 let mut network1_updated = network1.clone();
787 network1_updated.mark = 1;
788 assert_matches!(manager.update_network(network1_updated.clone()), Ok(()));
789 assert_data_tree!(inspector, root: {
790 nmfs: contains {
791 updated_networks: {
792 seen: 1u64,
793 sent: 0u64,
794 },
795 },
796 });
797
798 let mut network2_updated = network2.clone();
800 network2_updated.mark = 2;
801 assert_matches!(manager.update_network(network2_updated.clone()), Ok(()));
802 assert_data_tree!(inspector, root: {
803 nmfs: contains {
804 updated_networks: {
805 seen: 2u64,
806 sent: 1u64,
807 },
808 },
809 });
810
811 manager.set_default_network_id(None);
813 assert_data_tree!(inspector, root: {
814 nmfs: contains {
815 default_ids_set: {
816 seen: 3u64,
817 sent: 2u64,
818 },
819 },
820 });
821
822 assert_matches!(manager.remove_network(1), Ok(()));
824 assert_data_tree!(inspector, root: {
825 nmfs: contains {
826 removed_networks: {
827 seen: 1u64,
828 sent: 0u64,
829 },
830 },
831 });
832
833 assert_matches!(manager.remove_network(2), Ok(()));
835 assert_data_tree!(inspector, root: {
836 nmfs: contains {
837 default_ids_set: {
838 seen: 3u64,
839 sent: 2u64,
840 },
841 added_networks: {
842 seen: 2u64,
843 sent: 1u64,
844 },
845 updated_networks: {
846 seen: 2u64,
847 sent: 1u64,
848 },
849 removed_networks: {
850 seen: 2u64,
851 sent: 1u64,
852 },
853 },
854 });
855 }
856}