Skip to main content

starnix_modules_nmfs/
manager.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
5use 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/// Manager for communicating network properties.
22#[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    // Keeps track of networks and their [`NetworkMessage`].
32    #[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    // The number of event occurrences witnessed
46    // by the NetworkManager.
47    seen: u64,
48    // The number of event occurrences that have been
49    // sent successfully to the socketproxy.
50    sent: u64,
51}
52
53/// Initialize the connection to the socketproxy.
54pub fn nmfs_init(current_task: &CurrentTask) -> Result<(), anyhow::Error> {
55    let kernel = current_task.kernel();
56
57    // Register the fuchsia_network_monitor_fs in the FsRegistry.
58    let registry = kernel.expando.get::<FsRegistry>();
59    registry.register(b"fuchsia_network_monitor_fs".into(), fuchsia_network_monitor_fs);
60
61    // Register the NetworkManager.
62    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
69// The functions that propagate calls to the socketproxy prioritize maintaining
70// a correct version of local state and logging an error if the socketproxy
71// state is not aligned.
72impl NetworkManager {
73    // Create a NetworkManager with a StarnixNetworks protocol connection  and `nmfs` inspect node.
74    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    // Locks and returns the inner state of the manager.
84    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            // A network with that was created but hasn't yet
110            // been populated with network properties.
111            Some(None) | None => "{}".to_string(),
112        };
113        network_info.into_bytes().into()
114    }
115
116    // Set the default network identifier. Propagate the change
117    // to the socketproxy.
118    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        // Only log when there is an internal proxy error.
127        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    // Populate a new element in the Map. This does not
144    // propagate to the socketproxy.
145    //
146    // An error will be returned if a network with the id
147    // exists in the local state.
148    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    // Add a new network. Propagate the change to the socketproxy.
164    //
165    // An error will be returned if a network with the id
166    // exists in the local state.
167    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                    // This is deliberately before any Map manipulation because we
174                    // should not modify the Map state if we return an error.
175                    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        // Only log when there is an internal proxy error.
192        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    // Update an existing network. Propagate the change to the socketproxy
210    //
211    // An error will be returned if a network with the id does not
212    // exist in the local state.
213    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            // Ensure that there is a network already present at that netid
218            // prior to modifying the map.
219            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        // Only log when there is an internal proxy error.
234        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    // Remove an existing network. Propagate the change to the socketproxy.
252    //
253    // An error will be returned if a network with the id does not
254    // exist in the local state.
255    pub(crate) fn remove_network(&self, network_id: u32) -> Result<(), Errno> {
256        // Surface an error if the network is the current default
257        // network or if the network is not found.
258        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        // Only log when there is an internal proxy error.
274        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    // Call `set_default` on `StarnixNetworks`.
288    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    // Call `add` on `StarnixNetworks`.
302    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    // Call `update` on `StarnixNetworks`.
310    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    // Call `remove` on `StarnixNetworks`.
318    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// Errors produced when communicating updates to
324// the socket proxy.
325#[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        // Ensure we cannot add an empty network with the same id twice.
386        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        // Empty networks don't get sent to the socketproxy, so they are
392        // ignored in SeenSentData.
393        assert_data_tree!(inspector, root: {
394            nmfs: contains {
395                added_networks: {
396                    seen: 0u64,
397                    sent: 0u64,
398                },
399            },
400        });
401    }
402
403    // Set the `StarnixNetworksMarker` in the NetworkManager and mock out
404    // the responses to `StarnixNetworksRequest`s with provided results.
405    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    // Mock of private `manager::SeenSentData` to use for
463    // improved test case readability.
464    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        // `add_network` returns Ok(()) as long as the network
548        // addition is applied locally, regardless of if the call
549        // is sent to the socketproxy successfully.
550        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        // Ensure we cannot add a network with the same id twice. This is
557        // observed fully within the NetworkManager and not the socketproxy.
558        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        // Ensure we cannot update a network that doesn't exist.
614        assert_matches!(
615            manager.update_network(network.clone()),
616            Err(errno) if errno.code.error_code() == ENOENT
617        );
618
619        // Insert the network manually and then update the network.
620        {
621            let mut inner_guard = manager.lock();
622            let _ = inner_guard.as_mut().networks.insert(network_id, Some(network.clone()));
623        }
624
625        // `update_network` returns Ok(()) as long as the network
626        // update is applied locally, regardless of if the call
627        // is sent to the socketproxy successfully. Use the same
628        // network information -- another test verifies that the
629        // change is applied successfully.
630        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        // Ensure we cannot remove a network that doesn't exist.
675        assert_matches!(
676            manager.remove_network(network_id),
677            Err(errno) if errno.code.error_code() == ENOENT
678        );
679
680        // Insert the network manually and then remove it.
681        {
682            let mut inner_guard = manager.lock();
683            let _ = inner_guard.as_mut().networks.insert(network_id, Some(network.clone()));
684        }
685
686        // `remove_network` returns Ok(()) as long as the network
687        // removal is applied locally, regardless of if the call
688        // is sent to the socketproxy successfully.
689        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            // Network added to Manager, but not added to socketproxy.
718            Err(NetworkManagerError::Add(
719                fnp_socketproxy::NetworkRegistryAddError::MissingNetworkId,
720            )),
721            // Network added to Manager and to socketproxy.
722            Ok(()),
723            // Network set as default in Manager, but not in socketproxy.
724            Err(NetworkManagerError::SetDefault(
725                fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound,
726            )),
727            // Network set as default in Manager and in socketproxy.
728            Ok(()),
729            // Network updated in Manager, but not in socketproxy.
730            Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)),
731            // Network updated in Manager and in socketproxy.
732            Ok(()),
733            // Unset the default network so the network is eligible to be removed.
734            Ok(()),
735            // Network removed from Manager, but not from socketproxy.
736            Err(NetworkManagerError::Remove(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)),
737            // Network removed from Manager and from socketproxy.
738            Ok(()),
739        ];
740        let manager = &setup_proxy(inspector.root(), results);
741
742        // Add a network that doesn't get sent to the socketproxy.
743        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        // Add a network that gets sent to the socketproxy.
755        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        // Set the default network that isn't known to the socketproxy.
767        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        // Set the default network that is known to the socketproxy.
779        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        // Update a network not known to the socketproxy.
791        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        // Update a network that is known to the socketproxy.
804        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        // The default network must be unset first to remove this network.
817        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        // Remove a network that doesn't get sent to the socketproxy.
828        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        // Remove a network that gets sent to the socketproxy.
839        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}