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