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