[wlan][policy] Create network config stash
Add a stash implementation that will be used to save network
configs persistently using the stash service. The stash
implementation will replace KnownEssStore as a mechanism for
persistent storage. This stash implementation translates network
configs into strings to store using the stash node abstraction,
and loads information from stash back into network configs.
Bug: 35910
Tests: Added unit tests:
fx run-test wlancfg-tests
Change-Id: I93a4b079adc8900ebe4598bd0159b913c997c92e
diff --git a/src/connectivity/wlan/lib/stash/src/lib.rs b/src/connectivity/wlan/lib/stash/src/lib.rs
index bf8ccb4..3afae91 100644
--- a/src/connectivity/wlan/lib/stash/src/lib.rs
+++ b/src/connectivity/wlan/lib/stash/src/lib.rs
@@ -14,7 +14,7 @@
std::collections::{HashMap, HashSet},
};
-const NODE_SEPARATOR: &'static str = "#/@";
+pub const NODE_SEPARATOR: &'static str = "#/@";
pub struct StashNode {
// Always terminated with `NODE_SEPARATOR`
diff --git a/src/connectivity/wlan/wlancfg/BUILD.gn b/src/connectivity/wlan/wlancfg/BUILD.gn
index e2b472a..1b4208b 100644
--- a/src/connectivity/wlan/wlancfg/BUILD.gn
+++ b/src/connectivity/wlan/wlancfg/BUILD.gn
@@ -22,6 +22,7 @@
edition = "2018"
deps = [
+ "//sdk/fidl/fuchsia.stash:fuchsia.stash-rustc",
"//sdk/fidl/fuchsia.wlan.ap:fuchsia.wlan.ap.policy-rustc",
"//sdk/fidl/fuchsia.wlan.common:fuchsia.wlan.common-rustc",
"//sdk/fidl/fuchsia.wlan.device:fuchsia.wlan.device-rustc",
@@ -31,11 +32,12 @@
"//sdk/fidl/fuchsia.wlan.sme:fuchsia.wlan.sme-rustc",
"//sdk/fidl/fuchsia.wlan.stats:fuchsia.wlan.stats-rustc",
"//src/connectivity/wlan/lib/common/rust/:wlan-common",
+ "//src/connectivity/wlan/lib/stash/:wlan-stash",
"//src/lib/fidl/rust/fidl",
"//src/lib/fuchsia-async",
"//src/lib/fuchsia-component",
- "//src/lib/zircon/rust:fuchsia-zircon",
"//third_party/rust_crates:anyhow",
+ "//third_party/rust_crates:base64",
"//third_party/rust_crates:futures",
"//third_party/rust_crates:itertools",
"//third_party/rust_crates:log",
@@ -81,7 +83,10 @@
}
test_package("wlancfg-tests") {
- deps = [ ":bin_test" ]
+ deps = [
+ ":bin_test",
+ "//sdk/fidl/fuchsia.stash:fuchsia.stash-rustc",
+ ]
tests = [
{
name = "wlancfg_bin_test"
diff --git a/src/connectivity/wlan/wlancfg/meta/wlancfg.cmx b/src/connectivity/wlan/wlancfg/meta/wlancfg.cmx
index 52fcec08..9e85390 100644
--- a/src/connectivity/wlan/wlancfg/meta/wlancfg.cmx
+++ b/src/connectivity/wlan/wlancfg/meta/wlancfg.cmx
@@ -8,7 +8,8 @@
"isolated-persistent-storage"
],
"services": [
- "fuchsia.wlan.device.service.DeviceService"
+ "fuchsia.wlan.device.service.DeviceService",
+ "fuchsia.stash.SecureStore"
]
}
}
diff --git a/src/connectivity/wlan/wlancfg/meta/wlancfg_bin_test.cmx b/src/connectivity/wlan/wlancfg/meta/wlancfg_bin_test.cmx
index ffa5364..17d7ef8 100644
--- a/src/connectivity/wlan/wlancfg/meta/wlancfg_bin_test.cmx
+++ b/src/connectivity/wlan/wlancfg/meta/wlancfg_bin_test.cmx
@@ -1,10 +1,20 @@
{
+ "facets": {
+ "fuchsia.test": {
+ "injected-services": {
+ "fuchsia.stash.SecureStore": "fuchsia-pkg://fuchsia.com/stash#meta/stash_secure.cmx"
+ }
+ }
+ },
"program": {
"binary": "test/wlancfg_bin_test"
},
"sandbox": {
"features": [
"isolated-temp"
+ ],
+ "services": [
+ "fuchsia.stash.SecureStore"
]
}
}
diff --git a/src/connectivity/wlan/wlancfg/src/main.rs b/src/connectivity/wlan/wlancfg/src/main.rs
index fd1967e..7757ac4 100644
--- a/src/connectivity/wlan/wlancfg/src/main.rs
+++ b/src/connectivity/wlan/wlancfg/src/main.rs
@@ -13,6 +13,7 @@
mod network_config;
mod policy;
mod shim;
+mod stash;
mod state_machine;
use {
diff --git a/src/connectivity/wlan/wlancfg/src/network_config.rs b/src/connectivity/wlan/wlancfg/src/network_config.rs
index e2982fd..ee5e3f2 100644
--- a/src/connectivity/wlan/wlancfg/src/network_config.rs
+++ b/src/connectivity/wlan/wlancfg/src/network_config.rs
@@ -4,6 +4,7 @@
use {
fidl_fuchsia_wlan_policy as fidl_policy, fidl_fuchsia_wlan_sme as fidl_sme,
+ serde_derive::{Deserialize, Serialize},
std::{collections::VecDeque, time::SystemTime},
wlan_common::mac::Bssid,
};
@@ -109,7 +110,7 @@
}
/// The credential of a network connection. It mirrors the fidl_fuchsia_wlan_policy Credential
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Credential {
None,
Password(Vec<u8>),
@@ -143,7 +144,7 @@
}
}
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum SecurityType {
None,
Wep,
@@ -178,7 +179,7 @@
/// The network identifier is the SSID and security policy of the network, and it is used to
/// distinguish networks. It mirrors the NetworkIdentifier in fidl_fuchsia_wlan_policy.
-#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct NetworkIdentifier {
pub ssid: Vec<u8>,
pub security_type: SecurityType,
diff --git a/src/connectivity/wlan/wlancfg/src/stash.rs b/src/connectivity/wlan/wlancfg/src/stash.rs
new file mode 100644
index 0000000..7d1c601
--- /dev/null
+++ b/src/connectivity/wlan/wlancfg/src/stash.rs
@@ -0,0 +1,402 @@
+// Copyright 2020 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::network_config::{Credential, NetworkConfig, NetworkIdentifier},
+ anyhow::{bail, format_err, Context, Error},
+ fidl::endpoints::create_proxy,
+ fidl_fuchsia_stash as fidl_stash,
+ fuchsia_component::client::connect_to_service,
+ serde_derive::{Deserialize, Serialize},
+ std::collections::HashMap,
+ wlan_stash::{StashNode, NODE_SEPARATOR},
+};
+
+const STASH_PREFIX: &str = "config";
+/// The name we store the persistent data of a network config under. The StashNode abstraction
+/// requires that writing to a StashNode is done as a named field, so we will store the network
+/// config's data under this name.
+const DATA: &str = "data";
+
+/// Manages access to the persistent storage or saved network configs through Stash
+pub struct Stash {
+ root: StashNode,
+}
+
+/// TODO(nmccracken) Remove this attribute when the stash is used by SavedNetworksManager.
+#[allow(dead_code)]
+impl Stash {
+ /// Initialize new Stash with the ID provided by the Saved Networks Manager. The ID will
+ /// identify stored values as being part of the same persistent storage.
+ pub fn new_with_id(id: &str) -> Result<Self, Error> {
+ let store_client = connect_to_service::<fidl_stash::SecureStoreMarker>()
+ .context("failed to connect to store")?;
+ store_client.identify(id).context("failed to identify client to store")?;
+ let (store, accessor_server) = create_proxy().context("failed to create accessor proxy")?;
+ store_client
+ .create_accessor(false, accessor_server)
+ .context("failed to create accessor")?;
+ let root = StashNode::root(store).child(STASH_PREFIX);
+ Ok(Stash { root })
+ }
+
+ /// Add or update network configs of a given network identifier to persistent storage.
+ pub fn write(
+ &self,
+ id: &NetworkIdentifier,
+ network_configs: &[NetworkConfig],
+ ) -> Result<(), Error> {
+ // write each config to a StashNode under the network identifier. The key of the StashNode
+ // will be STASH_PREFIX#<net_id>#<index>
+ let id_key = Self::serialize_key(id)
+ .map_err(|_| format_err!("failed to serialize network identifier"))?;
+
+ // use a different number to separate each child network config
+ let mut config_index = 0;
+ let mut id_node = self.root.child(&id_key);
+ for network_config in network_configs {
+ let mut config_node = id_node.child(&config_index.to_string());
+ write_config(&mut config_node, network_config)?;
+ config_index += 1;
+ }
+ id_node.commit()
+ }
+
+ /// Make string value of NetworkIdentifier that will be the key for a config in the stash.
+ fn serialize_key(id: &NetworkIdentifier) -> Result<String, serde_json::error::Error> {
+ serde_json::to_string(id)
+ }
+
+ /// Create the NetworkIdentifier described by the StashNode's key. The key must be in the
+ /// format of the root's key followed by a JSON representation of a NetworkIdentifier and then
+ /// a node separator. Everything after in the key will be ignored.
+ fn id_from_key(&self, stash_node: &StashNode) -> Result<NetworkIdentifier, Error> {
+ let key = stash_node.key();
+ // Verify that the key begins with the root node's key and remove it.
+ if !key.starts_with(&self.root.key()) {
+ bail!("key is missing the beginning node separator");
+ }
+ let prefix_len = self.root.key().len();
+ let mut key_after_root = key[prefix_len..].to_string();
+ if let Some(index) = key_after_root.find(NODE_SEPARATOR) {
+ key_after_root.truncate(index);
+ } else {
+ bail!("key is missing node separator after network identifier");
+ }
+ // key_after_root should now just be the serialization of the NetworkIdentifier
+ serde_json::from_str(&key_after_root).map_err(|e| format_err!("{}", e))
+ }
+
+ /// Read persisting data of a given StashNode and use it to build a network config.
+ async fn read_config(
+ net_id: NetworkIdentifier,
+ stash_node: &StashNode,
+ ) -> Result<NetworkConfig, Error> {
+ let fields = stash_node.fields().await?;
+ let data = fields.get_str(DATA).ok_or_else(|| format_err!("failed to config's data"))?;
+ let data: PersistentData = serde_json::from_str(data).map_err(|e| format_err!("{}", e))?;
+ data.into_config_with_id(net_id)
+ }
+
+ /// Load all saved network configs from stash. Will create HashMap of network configs by SSID
+ /// as saved in the stash
+ pub async fn load(&self) -> Result<HashMap<NetworkIdentifier, Vec<NetworkConfig>>, Error> {
+ // get all the children nodes of root, which represent the unique identifiers,
+ let id_nodes = self.root.children().await?;
+ let mut network_configs = HashMap::new();
+
+ // for each child representing a network config, read in values
+ for id_node in id_nodes {
+ let mut config_list = vec![];
+ let net_id = self.id_from_key(&id_node)?;
+ for config_node in id_node.children().await? {
+ let network_config = Self::read_config(net_id.clone(), &config_node).await?;
+ config_list.push(network_config);
+ }
+ network_configs.insert(net_id, config_list);
+ }
+
+ Ok(network_configs)
+ }
+
+ /// Remove all saved values from the stash. It will delete everything under the root node,
+ /// and anything else in the same stash but not under the root node would be ignored.
+ pub fn clear(&mut self) -> Result<(), Error> {
+ self.root.delete();
+ self.root.commit()?;
+ Ok(())
+ }
+}
+
+/// Write the persisting values (not including network ID) of a network config to the provided
+/// stash node.
+fn write_config(stash_node: &mut StashNode, config: &NetworkConfig) -> Result<(), Error> {
+ let data = PersistentData::new(config.credential.clone(), config.has_ever_connected);
+ let data_str = serde_json::to_string(&data).map_err(|e| format_err!("{}", e))?;
+ stash_node.write_str(DATA, data_str)
+}
+
+/// The data that will be stored between reboots of a device. Used to convert the data between JSON
+/// and network config
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct PersistentData {
+ credential: Credential,
+ has_ever_connected: bool,
+}
+
+impl PersistentData {
+ fn new(credential: Credential, has_ever_connected: bool) -> Self {
+ Self { credential, has_ever_connected }
+ }
+
+ /// Since Network Identifier is stored in the stash key and not in the stash value, we need
+ /// to combine network identifier with persistent data in order to make the network config.
+ fn into_config_with_id(self, network_id: NetworkIdentifier) -> Result<NetworkConfig, Error> {
+ let seen_in_passive = false;
+ NetworkConfig::new(network_id, self.credential, self.has_ever_connected, seen_in_passive)
+ .map_err(|_| format_err!("error creating network config from persistent data"))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use {
+ super::*,
+ crate::network_config::{Credential, NetworkIdentifier, SecurityType},
+ fuchsia_async as fasync,
+ };
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn write_and_read() {
+ let stash = new_stash("write_and_read");
+ let cfg_id = NetworkIdentifier::new(b"foo".to_vec(), SecurityType::Wpa2);
+ let cfg = NetworkConfig::new(
+ cfg_id.clone(),
+ Credential::Password(b"password".to_vec()),
+ true,
+ false,
+ )
+ .expect("Failed to create network config");
+
+ // Save a network config to the stash
+ stash.write(&cfg_id, &vec![cfg.clone()]).expect("Failed writing to stash");
+
+ // Expect to read the same value back with the same key
+ let cfgs_from_stash = stash.load().await.expect("Failed reading from stash");
+ assert_eq!(1, cfgs_from_stash.len());
+ assert_eq!(Some(&vec![cfg.clone()]), cfgs_from_stash.get(&cfg_id));
+
+ // Overwrite the list of configs saved in stash
+ let cfg_2 = NetworkConfig::new(
+ NetworkIdentifier::new(b"foo".to_vec(), SecurityType::Wpa2),
+ Credential::Password(b"other-password".to_vec()),
+ false,
+ false,
+ )
+ .expect("Failed to create network config");
+ stash.write(&cfg_id, &vec![cfg.clone(), cfg_2.clone()]).expect("Failed writing to stash");
+
+ // Expect to read the saved value back with the same key
+ let cfgs_from_stash = stash.load().await.expect("Failed reading from stash");
+ assert_eq!(1, cfgs_from_stash.len());
+ let actual_configs = cfgs_from_stash.get(&cfg_id).unwrap();
+ assert_eq!(2, actual_configs.len());
+ assert!(actual_configs.contains(&cfg));
+ assert!(actual_configs.contains(&cfg_2));
+ }
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn write_read_security_types() {
+ let stash = new_stash("write_read_security_types");
+ let password = Credential::Password(b"config-password".to_vec());
+
+ // create and write configs with each security type
+ let net_id_open = network_id("foo", SecurityType::None);
+ let net_id_wep = network_id("foo", SecurityType::Wep);
+ let net_id_wpa = network_id("foo", SecurityType::Wpa);
+ let net_id_wpa2 = network_id("foo", SecurityType::Wpa2);
+ let net_id_wpa3 = network_id("foo", SecurityType::Wpa3);
+ let cfg_open = new_config(net_id_open.clone(), Credential::None);
+ let cfg_wep = new_config(net_id_wep.clone(), password.clone());
+ let cfg_wpa = new_config(net_id_wpa.clone(), password.clone());
+ let cfg_wpa2 = new_config(net_id_wpa2.clone(), password.clone());
+ let cfg_wpa3 = new_config(net_id_wpa3.clone(), password.clone());
+
+ stash.write(&net_id_open, &vec![cfg_open.clone()]).expect("failed to write config");
+ stash.write(&net_id_wep, &vec![cfg_wep.clone()]).expect("failed to write config");
+ stash.write(&net_id_wpa, &vec![cfg_wpa.clone()]).expect("failed to write config");
+ stash.write(&net_id_wpa2, &vec![cfg_wpa2.clone()]).expect("failed to write config");
+ stash.write(&net_id_wpa3, &vec![cfg_wpa3.clone()]).expect("failed to write config");
+
+ // load stash and expect each config that we wrote
+ let configs = stash.load().await.expect("failed loading from stash");
+ assert_eq!(Some(&vec![cfg_open]), configs.get(&net_id_open));
+ assert_eq!(Some(&vec![cfg_wep]), configs.get(&net_id_wep));
+ assert_eq!(Some(&vec![cfg_wpa]), configs.get(&net_id_wpa));
+ assert_eq!(Some(&vec![cfg_wpa2]), configs.get(&net_id_wpa2));
+ assert_eq!(Some(&vec![cfg_wpa3]), configs.get(&net_id_wpa3));
+ }
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn write_read_credentials() {
+ let stash = new_stash("write_read_credentials");
+
+ let net_id_none = network_id("bar-none", SecurityType::None);
+ let net_id_password = network_id("bar-password", SecurityType::Wpa2);
+ let net_id_psk = network_id("bar-psk", SecurityType::Wpa2);
+
+ // create and write configs with each type credential
+ let password = Credential::Password(b"config-password".to_vec());
+ let psk = Credential::Psk([65; 64].to_vec());
+
+ let cfg_none = new_config(net_id_none.clone(), Credential::None);
+ let cfg_password = new_config(net_id_password.clone(), password);
+ let cfg_psk = new_config(net_id_psk.clone(), psk);
+
+ // write each config to stash, then check that we see them when we load
+ stash.write(&net_id_none, &vec![cfg_none.clone()]).expect("failed to write");
+ stash.write(&net_id_password, &vec![cfg_password.clone()]).expect("failed to write");
+ stash.write(&net_id_psk, &vec![cfg_psk.clone()]).expect("failed to write");
+
+ let configs = stash.load().await.expect("failed loading from stash");
+ assert_eq!(Some(&vec![cfg_none]), configs.get(&net_id_none));
+ assert_eq!(Some(&vec![cfg_password]), configs.get(&net_id_password));
+ assert_eq!(Some(&vec![cfg_psk]), configs.get(&net_id_psk));
+ }
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn write_persists() {
+ let stash_id = "write_persists";
+ let stash = new_stash(stash_id);
+ let cfg_id = NetworkIdentifier::new(b"foo".to_vec(), SecurityType::Wpa2);
+ let cfg = NetworkConfig::new(
+ cfg_id.clone(),
+ Credential::Password(b"password".to_vec()),
+ true,
+ false,
+ )
+ .expect("Failed to create network config");
+
+ // Save a network config to the stash
+ stash.write(&cfg_id, &vec![cfg.clone()]).expect("Failed writing to stash");
+
+ //create the stash again with same id
+ let stash = Stash::new_with_id(stash_id).expect("Failed to create new stash");
+
+ // Expect to read the same value back with the same key, should exist in new stash
+ let cfgs_from_stash = stash.load().await.expect("Failed reading from stash");
+ assert_eq!(1, cfgs_from_stash.len());
+ assert_eq!(Some(&vec![cfg.clone()]), cfgs_from_stash.get(&cfg_id));
+ }
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn load_stash() {
+ let store = new_stash("load_stash");
+ let foo_net_id = NetworkIdentifier::new(b"foo".to_vec(), SecurityType::Wpa2);
+ let cfg_foo = NetworkConfig::new(
+ foo_net_id.clone(),
+ Credential::Password(b"12345678".to_vec()),
+ true,
+ false,
+ )
+ .expect("Failed to create network config");
+ let bar_net_id = NetworkIdentifier::new(b"bar".to_vec(), SecurityType::Wpa2);
+ let cfg_bar = NetworkConfig::new(
+ bar_net_id.clone(),
+ Credential::Password(b"qwertyuiop".to_vec()),
+ true,
+ false,
+ )
+ .expect("Failed to create network config");
+
+ // Store two networks in our stash.
+ store.write(&foo_net_id, &vec![cfg_foo.clone()]).expect("Failed to save config to stash");
+ store.write(&bar_net_id, &vec![cfg_bar.clone()]).expect("Failed to save config to stash");
+
+ // load should give us a hashmap with the two networks we saved
+ let mut expected_cfgs = HashMap::new();
+ expected_cfgs.insert(foo_net_id.clone(), vec![cfg_foo]);
+ expected_cfgs.insert(bar_net_id.clone(), vec![cfg_bar]);
+ assert_eq!(expected_cfgs, store.load().await.expect("Failed to load configs from stash"));
+ }
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn clear_stash() {
+ let stash_id = "clear_stash";
+ let mut stash = new_stash(stash_id);
+
+ // add some configs to the stash
+ let net_id_foo = NetworkIdentifier::new(b"foo".to_vec(), SecurityType::Wpa2);
+ let cfg_foo = NetworkConfig::new(
+ net_id_foo.clone(),
+ Credential::Password(b"qwertyuio".to_vec()),
+ true,
+ false,
+ )
+ .expect("Failed to create network config");
+ let net_id_bar = NetworkIdentifier::new(b"bar".to_vec(), SecurityType::Wpa2);
+ let cfg_bar = NetworkConfig::new(
+ net_id_bar.clone(),
+ Credential::Password(b"12345678".to_vec()),
+ false,
+ false,
+ )
+ .expect("Failed to create network config");
+ stash.write(&net_id_foo, &vec![cfg_foo.clone()]).expect("Failed to write to stash");
+ stash.write(&net_id_bar, &vec![cfg_bar.clone()]).expect("Failed to write to stash");
+
+ // verify that the configs are found in stash
+ let configs_from_stash = stash.load().await.expect("Failed to read");
+ assert_eq!(2, configs_from_stash.len());
+ assert_eq!(Some(&vec![cfg_foo.clone()]), configs_from_stash.get(&net_id_foo));
+ assert_eq!(Some(&vec![cfg_bar.clone()]), configs_from_stash.get(&net_id_bar));
+
+ // clear the stash
+ stash.clear().expect("Failed to clear stash");
+ // verify that the configs are no longer in the stash
+ let configs_from_stash = stash.load().await.expect("Failed to read");
+ assert_eq!(0, configs_from_stash.len());
+
+ // recreate stash and verify that clearing the stash persists
+ let stash = Stash::new_with_id(stash_id).expect("Failed to create new stash");
+ let configs_from_stash = stash.load().await.expect("Failed to read");
+ assert_eq!(0, configs_from_stash.len());
+ }
+
+ // creates a new stash with the given ID and clears the values saved in the stash
+ fn new_stash(stash_id: &str) -> Stash {
+ let mut stash = Stash::new_with_id(stash_id).expect("Failed to create new stash");
+ stash.root.delete();
+ stash.root.commit().expect("Failed to commit clearing stash");
+ stash
+ }
+
+ #[fasync::run_singlethreaded(test)]
+ async fn write_to_correct_stash_node() {
+ let stash = new_stash("write_to_correct_stash_node");
+
+ let net_id = network_id("foo", SecurityType::Wpa2);
+ let credential = Credential::Password(b"password".to_vec());
+ let network_config = new_config(net_id.clone(), credential.clone());
+
+ // write to stash and check that the right thing is written under the right StashNode
+ stash.write(&net_id, &vec![network_config]).expect("failed to write to stash");
+ let net_id_str =
+ Stash::serialize_key(&net_id).expect("failed to serialize network identifier");
+ let expected_node = stash.root.child(&net_id_str).child(&format!("{}", 0));
+ let fields = expected_node.fields().await.expect("failed to get fields");
+ let data_actual = fields.get_str(&format!("{}", DATA));
+ let data_expected = serde_json::to_string(&PersistentData::new(credential, false))
+ .expect("failed to serialize data");
+ assert_eq!(data_actual, Some(&data_expected));
+ }
+
+ fn network_id(ssid: impl Into<Vec<u8>>, security_type: SecurityType) -> NetworkIdentifier {
+ NetworkIdentifier::new(ssid.into(), security_type)
+ }
+
+ fn new_config(network_id: NetworkIdentifier, credential: Credential) -> NetworkConfig {
+ NetworkConfig::new(network_id, credential, false, false).expect("failed to create config")
+ }
+}