blob: 8e2d6350327c5eb78e3f8f7c208ffedb5bd24521 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{NetworkMessage, fuchsia_network_monitor_fs};
use bstr::BString;
use fuchsia_component::client::connect_to_protocol_sync;
use fuchsia_inspect_derive::{IValue, Inspect, Unit, WithInspect};
use starnix_core::task::CurrentTask;
use starnix_core::vfs::fs_registry::FsRegistry;
use starnix_logging::{log_error, log_info};
use starnix_sync::{Mutex, MutexGuard};
use starnix_uapi::error;
use starnix_uapi::errors::Errno;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use thiserror::Error;
use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
/// Manager for communicating network properties.
#[derive(Inspect)]
pub(crate) struct NetworkManager {
starnix_networks: fnp_socketproxy::StarnixNetworksSynchronousProxy,
#[inspect(forward)]
inner: Mutex<IValue<NetworkManagerInner>>,
}
#[derive(Unit, Default)]
struct NetworkManagerInner {
// Keeps track of networks and their [`NetworkMessage`].
#[inspect(skip)]
default_id: Option<u32>,
#[inspect(skip)]
networks: HashMap<u32, Option<NetworkMessage>>,
default_ids_set: SeenSentData,
added_networks: SeenSentData,
updated_networks: SeenSentData,
removed_networks: SeenSentData,
}
#[derive(Unit, Default)]
struct SeenSentData {
// The number of event occurrences witnessed
// by the NetworkManager.
seen: u64,
// The number of event occurrences that have been
// sent successfully to the socketproxy.
sent: u64,
}
/// Initialize the connection to the socketproxy.
pub fn nmfs_init(current_task: &CurrentTask) -> Result<(), anyhow::Error> {
let kernel = current_task.kernel();
// Register the fuchsia_network_monitor_fs in the FsRegistry.
let registry = kernel.expando.get::<FsRegistry>();
registry.register(b"fuchsia_network_monitor_fs".into(), fuchsia_network_monitor_fs);
// Register the NetworkManager.
let starnix_networks = connect_to_protocol_sync::<fnp_socketproxy::StarnixNetworksMarker>()?;
kernel
.expando
.get_or_init(|| NetworkManager::new_with_proxy(starnix_networks, &kernel.inspect_node));
Ok(())
}
// The functions that propagate calls to the socketproxy prioritize maintaining
// a correct version of local state and logging an error if the socketproxy
// state is not aligned.
impl NetworkManager {
// Create a NetworkManager with a StarnixNetworks protocol connection and `nmfs` inspect node.
pub(crate) fn new_with_proxy(
proxy: fnp_socketproxy::StarnixNetworksSynchronousProxy,
node: &fuchsia_inspect::Node,
) -> Self {
Self { starnix_networks: proxy, inner: Default::default() }
.with_inspect(node, "nmfs")
.expect("Failed to attach 'nmfs' node")
}
// Locks and returns the inner state of the manager.
fn lock(&self) -> MutexGuard<'_, IValue<NetworkManagerInner>> {
self.inner.lock()
}
pub(crate) fn get_default_network_id(&self) -> Option<u32> {
self.lock().default_id
}
pub(crate) fn get_network(&self, network_id: &u32) -> Option<Option<NetworkMessage>> {
self.lock().networks.get(network_id).cloned()
}
pub(crate) fn get_default_id_as_bytes(&self) -> BString {
let default_id = match self.get_default_network_id() {
Some(id) => id.to_string(),
None => "".to_string(),
};
default_id.into_bytes().into()
}
pub(crate) fn get_network_by_id_as_bytes(&self, network_id: u32) -> BString {
let network_info = match self.get_network(&network_id) {
Some(Some(network)) => {
serde_json::to_string(&network).unwrap_or_else(|_| "{}".to_string())
}
// A network with that was created but hasn't yet
// been populated with network properties.
Some(None) | None => "{}".to_string(),
};
network_info.into_bytes().into()
}
// Set the default network identifier. Propagate the change
// to the socketproxy.
pub(crate) fn set_default_network_id(&self, network_id: Option<u32>) {
{
let mut inner_guard = self.lock();
let mut inner = inner_guard.as_mut();
inner.default_id = network_id;
inner.default_ids_set.seen += 1;
}
// Only log when there is an internal proxy error.
match self.fidl_set_default_network_id(network_id) {
Ok(()) => {
log_info!(
"Successfully set network with id {network_id:?} as default in socketproxy",
);
let mut inner_guard = self.lock();
inner_guard.as_mut().default_ids_set.sent += 1;
}
Err(e) => {
log_error!(
"Failed to set network with id {network_id:?} as default in socketproxy; {e:?}"
);
}
}
}
// Populate a new element in the Map. This does not
// propagate to the socketproxy.
//
// An error will be returned if a network with the id
// exists in the local state.
pub(crate) fn add_empty_network(&self, network_id: u32) -> Result<(), Errno> {
let mut inner_guard = self.lock();
match inner_guard.as_mut().networks.entry(network_id) {
Entry::Occupied(_) => {
log_error!(
"Failed to add empty network to HashMap, was present for id: {}",
network_id
);
return error!(EEXIST);
}
Entry::Vacant(entry) => entry.insert(None),
};
Ok(())
}
// Add a new network. Propagate the change to the socketproxy.
//
// An error will be returned if a network with the id
// exists in the local state.
pub(crate) fn add_network(&self, network: NetworkMessage) -> Result<(), Errno> {
{
let mut inner_guard = self.lock();
let mut inner = inner_guard.as_mut();
match inner.networks.entry(network.netid) {
Entry::Occupied(mut entry) => {
// This is deliberately before any Map manipulation because we
// should not modify the Map state if we return an error.
if let Some(network) = entry.get() {
log_error!(
"Failed to add network with id {} to HashMap, already existed",
network.netid
);
return error!(EEXIST);
}
let _ = entry.insert(Some(network.clone()));
}
Entry::Vacant(entry) => {
let _ = entry.insert(Some(network.clone()));
}
}
inner.added_networks.seen += 1;
}
// Only log when there is an internal proxy error.
match self.fidl_add_network(&fnp_socketproxy::Network::from(&network)) {
Ok(()) => {
log_info!("Successfully added network with id {} to socketproxy", network.netid);
let mut inner_guard = self.lock();
inner_guard.as_mut().added_networks.sent += 1;
}
Err(e) => {
log_error!(
"Failed to add network with id {:?} to socketproxy; {e:?}",
network.netid
);
}
}
Ok(())
}
// Update an existing network. Propagate the change to the socketproxy
//
// An error will be returned if a network with the id does not
// exist in the local state.
pub(crate) fn update_network(&self, network: NetworkMessage) -> Result<(), Errno> {
{
let mut inner_guard = self.lock();
let mut inner = inner_guard.as_mut();
// Ensure that there is a network already present at that netid
// prior to modifying the map.
let _old_network = match inner.networks.entry(network.netid) {
Entry::Occupied(mut entry) => {
if let None = entry.get() {
return error!(ENOENT);
}
entry.insert(Some(network.clone()))
}
Entry::Vacant(_) => {
return error!(ENOENT);
}
};
inner.updated_networks.seen += 1;
};
// Only log when there is an internal proxy error.
match self.fidl_update_network(&fnp_socketproxy::Network::from(&network)) {
Ok(()) => {
log_info!("Successfully updated network with id {} in socketproxy", network.netid);
let mut inner_guard = self.lock();
inner_guard.as_mut().updated_networks.sent += 1;
}
Err(e) => {
log_error!(
"Failed to update network with id {} in socketproxy; {e:?}",
network.netid
);
}
}
Ok(())
}
// Remove an existing network. Propagate the change to the socketproxy.
//
// An error will be returned if a network with the id does not
// exist in the local state.
pub(crate) fn remove_network(&self, network_id: u32) -> Result<(), Errno> {
// Surface an error if the network is the current default
// network or if the network is not found.
let default_network_id = self.get_default_network_id();
if let Some(id) = default_network_id {
if id == network_id {
return error!(EPERM);
}
}
{
let mut inner_guard = self.lock();
let mut inner = inner_guard.as_mut();
if let None = inner.networks.remove(&network_id) {
return error!(ENOENT);
}
inner.removed_networks.seen += 1;
}
// Only log when there is an internal proxy error.
match self.fidl_remove_network(&network_id) {
Ok(()) => {
log_info!("Successfully removed network with id {network_id} from socketproxy",);
let mut inner_guard = self.lock();
inner_guard.as_mut().removed_networks.sent += 1;
}
Err(e) => {
log_error!("Failed to remove network with id {network_id} in socketproxy; {e:?}");
}
}
Ok(())
}
// Call `set_default` on `StarnixNetworks`.
fn fidl_set_default_network_id(
&self,
network_id: Option<u32>,
) -> Result<(), NetworkManagerError> {
let network_id = match network_id {
Some(id) => fidl_fuchsia_posix_socket::OptionalUint32::Value(id),
None => {
fidl_fuchsia_posix_socket::OptionalUint32::Unset(fidl_fuchsia_posix_socket::Empty)
}
};
Ok(self.starnix_networks.set_default(&network_id, zx::MonotonicInstant::INFINITE)??)
}
// Call `add` on `StarnixNetworks`.
fn fidl_add_network(
&self,
network: &fnp_socketproxy::Network,
) -> Result<(), NetworkManagerError> {
Ok(self.starnix_networks.add(&network, zx::MonotonicInstant::INFINITE)??)
}
// Call `update` on `StarnixNetworks`.
fn fidl_update_network(
&self,
network: &fnp_socketproxy::Network,
) -> Result<(), NetworkManagerError> {
Ok(self.starnix_networks.update(&network, zx::MonotonicInstant::INFINITE)??)
}
// Call `remove` on `StarnixNetworks`.
fn fidl_remove_network(&self, network_id: &u32) -> Result<(), NetworkManagerError> {
Ok(self.starnix_networks.remove(*network_id, zx::MonotonicInstant::INFINITE)??)
}
}
// Errors produced when communicating updates to
// the socket proxy.
#[derive(Clone, Debug, Error)]
pub(crate) enum NetworkManagerError {
#[error("Error during socketproxy Add: {0:?}")]
Add(fnp_socketproxy::NetworkRegistryAddError),
#[error("Error calling FIDL on socketproxy: {0:?}")]
Fidl(#[from] fidl::Error),
#[error("Error during socketproxy Remove: {0:?}")]
Remove(fnp_socketproxy::NetworkRegistryRemoveError),
#[error("Error during socketproxy SetDefault: {0:?}")]
SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError),
#[error("Error during socketproxy Update: {0:?}")]
Update(fnp_socketproxy::NetworkRegistryUpdateError),
}
impl From<fnp_socketproxy::NetworkRegistryAddError> for NetworkManagerError {
fn from(error: fnp_socketproxy::NetworkRegistryAddError) -> Self {
NetworkManagerError::Add(error)
}
}
impl From<fnp_socketproxy::NetworkRegistryRemoveError> for NetworkManagerError {
fn from(error: fnp_socketproxy::NetworkRegistryRemoveError) -> Self {
NetworkManagerError::Remove(error)
}
}
impl From<fnp_socketproxy::NetworkRegistrySetDefaultError> for NetworkManagerError {
fn from(error: fnp_socketproxy::NetworkRegistrySetDefaultError) -> Self {
NetworkManagerError::SetDefault(error)
}
}
impl From<fnp_socketproxy::NetworkRegistryUpdateError> for NetworkManagerError {
fn from(error: fnp_socketproxy::NetworkRegistryUpdateError) -> Self {
NetworkManagerError::Update(error)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use diagnostics_assertions::assert_data_tree;
use futures::StreamExt as _;
use starnix_uapi::{EEXIST, ENOENT};
use test_case::test_case;
fn test_network_message_from_id(netid: u32) -> NetworkMessage {
NetworkMessage { netid, ..Default::default() }
}
#[::fuchsia::test]
async fn test_add_empty_network() {
let inspector = fuchsia_inspect::Inspector::default();
let manager = &setup_proxy(inspector.root(), vec![]);
let network_id = 1;
assert_matches!(manager.add_empty_network(network_id), Ok(()));
// Ensure we cannot add an empty network with the same id twice.
assert_matches!(
manager.add_empty_network(network_id),
Err(errno) if errno.code.error_code() == EEXIST
);
assert_matches!(manager.get_network(&network_id), Some(None));
// Empty networks don't get sent to the socketproxy, so they are
// ignored in SeenSentData.
assert_data_tree!(inspector, root: {
nmfs: contains {
added_networks: {
seen: 0u64,
sent: 0u64,
},
},
});
}
// Set the `StarnixNetworksMarker` in the NetworkManager and mock out
// the responses to `StarnixNetworksRequest`s with provided results.
fn setup_proxy(
inspect_node: &fuchsia_inspect::Node,
results: Vec<Result<(), NetworkManagerError>>,
) -> NetworkManager {
let (proxy, mut stream) = fidl::endpoints::create_sync_proxy_and_stream::<
fnp_socketproxy::StarnixNetworksMarker,
>();
let manager = NetworkManager::new_with_proxy(proxy, inspect_node);
let mut results = results.into_iter();
fuchsia_async::Task::spawn(async move {
while let Some(item) = stream.next().await {
let result = results
.next()
.expect("there should be an equivalent # of results and requests");
match item.expect("receive request") {
fnp_socketproxy::StarnixNetworksRequest::SetDefault {
network_id: _,
responder,
} => {
let res = result.map_err(|e| match e {
NetworkManagerError::SetDefault(err) => err,
_ => unreachable!("should have been SetDefault error variant"),
});
responder.send(res).expect("respond to SetDefault");
}
fnp_socketproxy::StarnixNetworksRequest::Add { network: _, responder } => {
let res = result.map_err(|e| match e {
NetworkManagerError::Add(err) => err,
_ => unreachable!("should have been Add error variant"),
});
responder.send(res).expect("respond to Add");
}
fnp_socketproxy::StarnixNetworksRequest::Update { network: _, responder } => {
let res = result.map_err(|e| match e {
NetworkManagerError::Update(err) => err,
_ => unreachable!("should have been Update error variant"),
});
responder.send(res).expect("respond to Update");
}
fnp_socketproxy::StarnixNetworksRequest::Remove {
network_id: _,
responder,
} => {
let res = result.map_err(|e| match e {
NetworkManagerError::Remove(err) => err,
_ => unreachable!("should have been Remove error variant"),
});
responder.send(res).expect("respond to Remove");
}
}
}
})
.detach();
manager
}
// Mock of private `manager::SeenSentData` to use for
// improved test case readability.
struct SeenSentData {
seen: u64,
sent: u64,
}
#[test_case(vec![Ok(()), Ok(())], SeenSentData { seen: 2, sent: 2 }; "all_success")]
#[test_case(
vec![
Ok(()),
Err(NetworkManagerError::SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound)),
],
SeenSentData { seen: 2, sent: 1 };
"one_error"
)]
#[test_case(
vec![
Err(NetworkManagerError::SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound)),
Err(NetworkManagerError::SetDefault(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound)),
],
SeenSentData { seen: 2, sent: 0 };
"all_errors"
)]
#[::fuchsia::test(threads = 2)]
async fn test_set_default_network_id_with_proxy(
results: Vec<Result<(), NetworkManagerError>>,
expected_data: SeenSentData,
) {
let inspector = fuchsia_inspect::Inspector::default();
let manager = &setup_proxy(inspector.root(), results);
manager.set_default_network_id(Some(1));
assert_eq!(manager.get_default_network_id(), Some(1));
manager.set_default_network_id(Some(2));
assert_eq!(manager.get_default_network_id(), Some(2));
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: expected_data.seen,
sent: expected_data.sent,
},
added_networks: {
seen: 0u64,
sent: 0u64,
},
updated_networks: {
seen: 0u64,
sent: 0u64,
},
removed_networks: {
seen: 0u64,
sent: 0u64,
},
},
});
}
#[test_case(vec![Ok(()), Ok(())], SeenSentData { seen: 2, sent: 2 }; "all_success")]
#[test_case(
vec![
Ok(()),
Err(NetworkManagerError::Add(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId)),
],
SeenSentData { seen: 2, sent: 1 };
"one_error"
)]
#[test_case(
vec![
Err(NetworkManagerError::Add(fnp_socketproxy::NetworkRegistryAddError::MissingNetworkId)),
Err(NetworkManagerError::Add(fnp_socketproxy::NetworkRegistryAddError::MissingNetworkInfo)),
],
SeenSentData { seen: 2, sent: 0 };
"all_errors"
)]
#[::fuchsia::test(threads = 2)]
async fn test_add_network_with_proxy(
results: Vec<Result<(), NetworkManagerError>>,
expected_data: SeenSentData,
) {
let inspector = fuchsia_inspect::Inspector::default();
let manager = &setup_proxy(inspector.root(), results);
// `add_network` returns Ok(()) as long as the network
// addition is applied locally, regardless of if the call
// is sent to the socketproxy successfully.
let network1 = test_network_message_from_id(1);
assert_matches!(manager.add_network(network1.clone()), Ok(()));
let network2 = test_network_message_from_id(2);
assert_matches!(manager.add_network(network2.clone()), Ok(()));
// Ensure we cannot add a network with the same id twice. This is
// observed fully within the NetworkManager and not the socketproxy.
assert_matches!(
manager.add_network(network2.clone()),
Err(errno) if errno.code.error_code() == EEXIST
);
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 0u64,
sent: 0u64,
},
added_networks: {
seen: expected_data.seen,
sent: expected_data.sent,
},
updated_networks: {
seen: 0u64,
sent: 0u64,
},
removed_networks: {
seen: 0u64,
sent: 0u64,
},
},
});
}
#[test_case(vec![Ok(()), Ok(())], SeenSentData { seen: 2, sent: 2 }; "all_success")]
#[test_case(
vec![
Ok(()),
Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::MissingNetworkId)),
],
SeenSentData { seen: 2, sent: 1 };
"one_error"
)]
#[test_case(
vec![
Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)),
Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::MissingNetworkInfo)),
],
SeenSentData { seen: 2, sent: 0 };
"all_errors"
)]
#[::fuchsia::test(threads = 2)]
async fn test_update_network_with_proxy(
results: Vec<Result<(), NetworkManagerError>>,
expected_data: SeenSentData,
) {
let inspector = fuchsia_inspect::Inspector::default();
let manager = &setup_proxy(inspector.root(), results);
let network_id = 1;
let network = test_network_message_from_id(network_id);
// Ensure we cannot update a network that doesn't exist.
assert_matches!(
manager.update_network(network.clone()),
Err(errno) if errno.code.error_code() == ENOENT
);
// Insert the network manually and then update the network.
{
let mut inner_guard = manager.lock();
let _ = inner_guard.as_mut().networks.insert(network_id, Some(network.clone()));
}
// `update_network` returns Ok(()) as long as the network
// update is applied locally, regardless of if the call
// is sent to the socketproxy successfully. Use the same
// network information -- another test verifies that the
// change is applied successfully.
assert_matches!(manager.update_network(network.clone()), Ok(()));
assert_matches!(manager.update_network(network), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 0u64,
sent: 0u64,
},
added_networks: {
seen: 0u64,
sent: 0u64,
},
updated_networks: {
seen: expected_data.seen,
sent: expected_data.sent,
},
removed_networks: {
seen: 0u64,
sent: 0u64,
},
},
});
}
#[test_case(vec![Ok(())], SeenSentData { seen: 1, sent: 1 }; "success")]
#[test_case(
vec![
Err(NetworkManagerError::Remove(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)),
],
SeenSentData { seen: 1, sent: 0 };
"error"
)]
#[::fuchsia::test(threads = 2)]
async fn test_remove_network_with_proxy(
results: Vec<Result<(), NetworkManagerError>>,
expected_data: SeenSentData,
) {
let inspector = fuchsia_inspect::Inspector::default();
let manager = &setup_proxy(inspector.root(), results);
let network_id = 1;
let network = test_network_message_from_id(network_id);
// Ensure we cannot remove a network that doesn't exist.
assert_matches!(
manager.remove_network(network_id),
Err(errno) if errno.code.error_code() == ENOENT
);
// Insert the network manually and then remove it.
{
let mut inner_guard = manager.lock();
let _ = inner_guard.as_mut().networks.insert(network_id, Some(network.clone()));
}
// `remove_network` returns Ok(()) as long as the network
// removal is applied locally, regardless of if the call
// is sent to the socketproxy successfully.
assert_matches!(manager.remove_network(network_id), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 0u64,
sent: 0u64,
},
added_networks: {
seen: 0u64,
sent: 0u64,
},
updated_networks: {
seen: 0u64,
sent: 0u64,
},
removed_networks: {
seen: expected_data.seen,
sent: expected_data.sent,
},
},
});
}
#[::fuchsia::test(threads = 2)]
async fn test_multiple_operations_with_proxy() {
let inspector = fuchsia_inspect::Inspector::default();
let results = vec![
// Network added to Manager, but not added to socketproxy.
Err(NetworkManagerError::Add(
fnp_socketproxy::NetworkRegistryAddError::MissingNetworkId,
)),
// Network added to Manager and to socketproxy.
Ok(()),
// Network set as default in Manager, but not in socketproxy.
Err(NetworkManagerError::SetDefault(
fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound,
)),
// Network set as default in Manager and in socketproxy.
Ok(()),
// Network updated in Manager, but not in socketproxy.
Err(NetworkManagerError::Update(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)),
// Network updated in Manager and in socketproxy.
Ok(()),
// Unset the default network so the network is eligible to be removed.
Ok(()),
// Network removed from Manager, but not from socketproxy.
Err(NetworkManagerError::Remove(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)),
// Network removed from Manager and from socketproxy.
Ok(()),
];
let manager = &setup_proxy(inspector.root(), results);
// Add a network that doesn't get sent to the socketproxy.
let network1 = test_network_message_from_id(1);
assert_matches!(manager.add_network(network1.clone()), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
added_networks: {
seen: 1u64,
sent: 0u64,
},
},
});
// Add a network that gets sent to the socketproxy.
let network2 = test_network_message_from_id(2);
assert_matches!(manager.add_network(network2.clone()), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
added_networks: {
seen: 2u64,
sent: 1u64,
},
},
});
// Set the default network that isn't known to the socketproxy.
manager.set_default_network_id(Some(1));
assert_eq!(manager.get_default_network_id(), Some(1));
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 1u64,
sent: 0u64,
},
},
});
// Set the default network that is known to the socketproxy.
manager.set_default_network_id(Some(2));
assert_eq!(manager.get_default_network_id(), Some(2));
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 2u64,
sent: 1u64,
},
},
});
// Update a network not known to the socketproxy.
let mut network1_updated = network1.clone();
network1_updated.mark = 1;
assert_matches!(manager.update_network(network1_updated.clone()), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
updated_networks: {
seen: 1u64,
sent: 0u64,
},
},
});
// Update a network that is known to the socketproxy.
let mut network2_updated = network2.clone();
network2_updated.mark = 2;
assert_matches!(manager.update_network(network2_updated.clone()), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
updated_networks: {
seen: 2u64,
sent: 1u64,
},
},
});
// The default network must be unset first to remove this network.
manager.set_default_network_id(None);
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 3u64,
sent: 2u64,
},
},
});
// Remove a network that doesn't get sent to the socketproxy.
assert_matches!(manager.remove_network(1), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
removed_networks: {
seen: 1u64,
sent: 0u64,
},
},
});
// Remove a network that gets sent to the socketproxy.
assert_matches!(manager.remove_network(2), Ok(()));
assert_data_tree!(inspector, root: {
nmfs: contains {
default_ids_set: {
seen: 3u64,
sent: 2u64,
},
added_networks: {
seen: 2u64,
sent: 1u64,
},
updated_networks: {
seen: 2u64,
sent: 1u64,
},
removed_networks: {
seen: 2u64,
sent: 1u64,
},
},
});
}
}