From 25d6e19c11288a0ee40b0e2119b8c849e8cf0b22 Mon Sep 17 00:00:00 2001
From: Jorik Cronenberg <jcronenberg@suse.de>
Date: Wed, 11 Sep 2024 17:06:10 +0200
Subject: [PATCH] Add additional wireless settings

---
 rust/agama-lib/share/profile.schema.json |  60 ++++++++
 rust/agama-lib/src/network/settings.rs   |  33 +++-
 rust/agama-server/src/network/error.rs   |  10 ++
 rust/agama-server/src/network/model.rs   | 187 ++++++++++++++++++++++-
 rust/agama-server/src/network/nm/dbus.rs | 158 +++++++++++++++++--
 5 files changed, 435 insertions(+), 13 deletions(-)

diff --git a/rust/agama-lib/share/profile.schema.json b/rust/agama-lib/share/profile.schema.json
index c87728ab88..6a2c007fa0 100644
--- a/rust/agama-lib/share/profile.schema.json
+++ b/rust/agama-lib/share/profile.schema.json
@@ -164,6 +164,66 @@
                       "mesh",
                       "ap"
                     ]
+                  },
+                  "hidden": {
+                    "title": "Indicates that the wifi network is not broadcasting it's SSID",
+                    "type": "boolean"
+                  },
+                  "band": {
+                    "title": "Frequency band of the wifi network",
+                    "type": "string",
+                    "enum": [
+                      "a",
+                      "bg"
+                    ]
+                  },
+                  "channel": {
+                    "title": "Wireless channel of the wifi network",
+                    "type": "integer",
+                    "minimum" : 0
+                  },
+                  "bssid": {
+                    "title": "Only allow connection to this mac address",
+                    "type": "string"
+                  },
+                  "group_algorithms": {
+                    "type": "array",
+                    "items": {
+                      "title": "A list of group/broadcast encryption algorithms",
+                      "type": "string",
+                      "enum": [
+                        "wep40",
+                        "wep104",
+                        "tkip",
+                        "ccmp"
+                      ]
+                    },
+                  },
+                  "pairwise_algorithms": {
+                    "type": "array",
+                    "items": {
+                      "title": "A list of pairwise encryption algorithms",
+                      "type": "string",
+                      "enum": [
+                        "tkip",
+                        "ccmp"
+                      ]
+                    }
+                  },
+                  "wpa_protocol_versions": {
+                    "type": "array",
+                    "items": {
+                      "title": "A list of allowed WPA protocol versions",
+                      "type": "string",
+                      "enum": [
+                        "wpa",
+                        "rsn"
+                      ]
+                    }
+                  },
+                  "pmf": {
+                    "title": "Indicates whether Protected Management Frames must be enabled for the connection",
+                    "type": "integer"
                   }
                 }
               },
diff --git a/rust/agama-lib/src/network/settings.rs b/rust/agama-lib/src/network/settings.rs
index 4c9ab13cc7..d55e25688c 100644
--- a/rust/agama-lib/src/network/settings.rs
+++ b/rust/agama-lib/src/network/settings.rs
@@ -35,13 +35,42 @@ impl MatchSettings {
     }
 }
 
+/// Wireless configuration
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
 pub struct WirelessSettings {
+    /// Password of the wifi network
     #[serde(skip_serializing_if = "Option::is_none")]
     pub password: Option<String>,
+    /// Security method/key management
     pub security: String,
+    /// SSID of the wifi network
     pub ssid: String,
+    /// Wifi network mode
     pub mode: String,
+    /// Frequency band of the wifi network
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub band: Option<String>,
+    /// Wireless channel of the wifi network
+    #[serde(skip_serializing_if = "is_zero", default)]
+    pub channel: u32,
+    /// Only allow connection to this mac address
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub bssid: Option<String>,
+    /// Indicates that the wifi network is not broadcasting it's SSID
+    #[serde(skip_serializing_if = "std::ops::Not::not", default)]
+    pub hidden: bool,
+    /// A list of group/broadcast encryption algorithms
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub group_algorithms: Vec<String>,
+    /// A list of pairwise encryption algorithms
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub pairwise_algorithms: Vec<String>,
+    /// A list of allowed WPA protocol versions
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub wpa_protocol_versions: Vec<String>,
+    /// Indicates whether Protected Management Frames must be enabled for the connection
+    #[serde(skip_serializing_if = "is_zero", default)]
+    pub pmf: i32,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -154,8 +183,8 @@ pub struct NetworkConnection {
     pub ieee_8021x: Option<IEEE8021XSettings>,
 }
 
-fn is_zero(u: &u32) -> bool {
-    *u == 0
+fn is_zero<T: PartialEq + From<u16>>(u: &T) -> bool {
+    *u == T::from(0)
 }
 
 impl NetworkConnection {
diff --git a/rust/agama-server/src/network/error.rs b/rust/agama-server/src/network/error.rs
index a3f349582d..701a734c53 100644
--- a/rust/agama-server/src/network/error.rs
+++ b/rust/agama-server/src/network/error.rs
@@ -40,6 +40,16 @@ pub enum NetworkStateError {
     InvalidEAPMethod(String),
     #[error("Invalid phase2 authentication method: '{0}'")]
     InvalidPhase2AuthMethod(String),
+    #[error("Invalid group algorithm: '{0}'")]
+    InvalidGroupAlgorithm(String),
+    #[error("Invalid pairwise algorithm: '{0}'")]
+    InvalidPairwiseAlgorithm(String),
+    #[error("Invalid WPA protocol version: '{0}'")]
+    InvalidWPAProtocolVersion(String),
+    #[error("Invalid wireless band: '{0}'")]
+    InvalidWirelessBand(String),
+    #[error("Invalid bssid: '{0}'")]
+    InvalidBssid(String),
 }
 
 impl From<NetworkStateError> for zbus::fdo::Error {
diff --git a/rust/agama-server/src/network/model.rs b/rust/agama-server/src/network/model.rs
index 4c3dd0b043..7ffcd2327c 100644
--- a/rust/agama-server/src/network/model.rs
+++ b/rust/agama-server/src/network/model.rs
@@ -971,13 +971,19 @@ pub struct WirelessConfig {
     pub security: SecurityProtocol,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub band: Option<WirelessBand>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub channel: Option<u32>,
+    pub channel: u32,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub bssid: Option<macaddr::MacAddr6>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub wep_security: Option<WEPSecurity>,
     pub hidden: bool,
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub group_algorithms: Vec<GroupAlgorithm>,
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub pairwise_algorithms: Vec<PairwiseAlgorithm>,
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub wpa_protocol_versions: Vec<WPAProtocolVersion>,
+    pub pmf: i32,
 }
 
 impl TryFrom<ConnectionConfig> for WirelessConfig {
@@ -998,11 +1004,60 @@ impl TryFrom<WirelessSettings> for WirelessConfig {
         let ssid = SSID(settings.ssid.as_bytes().into());
         let mode = WirelessMode::try_from(settings.mode.as_str())?;
         let security = SecurityProtocol::try_from(settings.security.as_str())?;
+        let band = if let Some(band) = &settings.band {
+            Some(
+                WirelessBand::try_from(band.as_str())
+                    .map_err(|_| NetworkStateError::InvalidWirelessBand(band.to_string()))?,
+            )
+        } else {
+            None
+        };
+        let bssid = if let Some(bssid) = &settings.bssid {
+            Some(
+                macaddr::MacAddr6::from_str(bssid)
+                    .map_err(|_| NetworkStateError::InvalidBssid(bssid.to_string()))?,
+            )
+        } else {
+            None
+        };
+        let group_algorithms = settings
+            .group_algorithms
+            .iter()
+            .map(|x| {
+                GroupAlgorithm::from_str(x)
+                    .map_err(|_| NetworkStateError::InvalidGroupAlgorithm(x.to_string()))
+            })
+            .collect::<Result<Vec<GroupAlgorithm>, NetworkStateError>>()?;
+        let pairwise_algorithms = settings
+            .pairwise_algorithms
+            .iter()
+            .map(|x| {
+                PairwiseAlgorithm::from_str(x)
+                    .map_err(|_| NetworkStateError::InvalidGroupAlgorithm(x.to_string()))
+            })
+            .collect::<Result<Vec<PairwiseAlgorithm>, NetworkStateError>>()?;
+        let wpa_protocol_versions = settings
+            .wpa_protocol_versions
+            .iter()
+            .map(|x| {
+                WPAProtocolVersion::from_str(x)
+                    .map_err(|_| NetworkStateError::InvalidGroupAlgorithm(x.to_string()))
+            })
+            .collect::<Result<Vec<WPAProtocolVersion>, NetworkStateError>>()?;
+
         Ok(WirelessConfig {
             ssid,
             mode,
             security,
             password: settings.password,
+            band,
+            channel: settings.channel,
+            bssid,
+            hidden: settings.hidden,
+            group_algorithms,
+            pairwise_algorithms,
+            wpa_protocol_versions,
+            pmf: settings.pmf,
             ..Default::default()
         })
     }
@@ -1012,11 +1067,37 @@ impl TryFrom<WirelessConfig> for WirelessSettings {
     type Error = NetworkStateError;
 
     fn try_from(wireless: WirelessConfig) -> Result<Self, Self::Error> {
+        let band = wireless.band.map(|x| x.to_string());
+        let bssid = wireless.bssid.map(|x| x.to_string());
+        let group_algorithms = wireless
+            .group_algorithms
+            .iter()
+            .map(|x| x.to_string())
+            .collect::<Vec<String>>();
+        let pairwise_algorithms = wireless
+            .pairwise_algorithms
+            .iter()
+            .map(|x| x.to_string())
+            .collect::<Vec<String>>();
+        let wpa_protocol_versions = wireless
+            .wpa_protocol_versions
+            .iter()
+            .map(|x| x.to_string())
+            .collect::<Vec<String>>();
+
         Ok(WirelessSettings {
             ssid: wireless.ssid.to_string(),
             mode: wireless.mode.to_string(),
             security: wireless.security.to_string(),
             password: wireless.password,
+            band,
+            channel: wireless.channel,
+            bssid,
+            hidden: wireless.hidden,
+            group_algorithms,
+            pairwise_algorithms,
+            wpa_protocol_versions,
+            pmf: wireless.pmf,
         })
     }
 }
@@ -1105,6 +1186,108 @@ impl TryFrom<&str> for SecurityProtocol {
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
+pub enum GroupAlgorithm {
+    Wep40,
+    Wep104,
+    Tkip,
+    Ccmp,
+}
+
+#[derive(Debug, Error)]
+#[error("Invalid group algorithm: {0}")]
+pub struct InvalidGroupAlgorithm(String);
+
+impl FromStr for GroupAlgorithm {
+    type Err = InvalidGroupAlgorithm;
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        match value {
+            "wep40" => Ok(GroupAlgorithm::Wep40),
+            "wep104" => Ok(GroupAlgorithm::Wep104),
+            "tkip" => Ok(GroupAlgorithm::Tkip),
+            "ccmp" => Ok(GroupAlgorithm::Ccmp),
+            _ => Err(InvalidGroupAlgorithm(value.to_string())),
+        }
+    }
+}
+
+impl fmt::Display for GroupAlgorithm {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let name = match &self {
+            GroupAlgorithm::Wep40 => "wep40",
+            GroupAlgorithm::Wep104 => "wep104",
+            GroupAlgorithm::Tkip => "tkip",
+            GroupAlgorithm::Ccmp => "ccmp",
+        };
+        write!(f, "{}", name)
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
+pub enum PairwiseAlgorithm {
+    Tkip,
+    Ccmp,
+}
+
+#[derive(Debug, Error)]
+#[error("Invalid pairwise algorithm: {0}")]
+pub struct InvalidPairwiseAlgorithm(String);
+
+impl FromStr for PairwiseAlgorithm {
+    type Err = InvalidPairwiseAlgorithm;
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        match value {
+            "tkip" => Ok(PairwiseAlgorithm::Tkip),
+            "ccmp" => Ok(PairwiseAlgorithm::Ccmp),
+            _ => Err(InvalidPairwiseAlgorithm(value.to_string())),
+        }
+    }
+}
+
+impl fmt::Display for PairwiseAlgorithm {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let name = match &self {
+            PairwiseAlgorithm::Tkip => "tkip",
+            PairwiseAlgorithm::Ccmp => "ccmp",
+        };
+        write!(f, "{}", name)
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
+pub enum WPAProtocolVersion {
+    Wpa,
+    Rsn,
+}
+
+#[derive(Debug, Error)]
+#[error("Invalid WPA protocol version: {0}")]
+pub struct InvalidWPAProtocolVersion(String);
+
+impl FromStr for WPAProtocolVersion {
+    type Err = InvalidWPAProtocolVersion;
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        match value {
+            "wpa" => Ok(WPAProtocolVersion::Wpa),
+            "rsn" => Ok(WPAProtocolVersion::Rsn),
+            _ => Err(InvalidWPAProtocolVersion(value.to_string())),
+        }
+    }
+}
+
+impl fmt::Display for WPAProtocolVersion {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let name = match &self {
+            WPAProtocolVersion::Wpa => "wpa",
+            WPAProtocolVersion::Rsn => "rsn",
+        };
+        write!(f, "{}", name)
+    }
+}
+
 #[derive(Debug, Default, PartialEq, Clone, Serialize)]
 pub struct WEPSecurity {
     pub auth_alg: WEPAuthAlg,
diff --git a/rust/agama-server/src/network/nm/dbus.rs b/rust/agama-server/src/network/nm/dbus.rs
index 4d8a861e4e..68a3f5a095 100644
--- a/rust/agama-server/src/network/nm/dbus.rs
+++ b/rust/agama-server/src/network/nm/dbus.rs
@@ -384,16 +384,43 @@ fn wireless_config_to_dbus<'a>(config: &'a WirelessConfig) -> NestedHash<'a> {
 
     if let Some(band) = &config.band {
         wireless.insert("band", band.to_string().into());
-        if let Some(channel) = config.channel {
-            wireless.insert("channel", channel.into());
-        }
+        wireless.insert("channel", config.channel.into());
     }
     if let Some(bssid) = &config.bssid {
         wireless.insert("bssid", bssid.as_bytes().into());
     }
 
-    let mut security: HashMap<&str, zvariant::Value> =
-        HashMap::from([("key-mgmt", config.security.to_string().into())]);
+    let mut security: HashMap<&str, zvariant::Value> = HashMap::from([
+        ("key-mgmt", config.security.to_string().into()),
+        (
+            "group",
+            config
+                .group_algorithms
+                .iter()
+                .map(|x| x.to_string())
+                .collect::<Vec<String>>()
+                .into(),
+        ),
+        (
+            "pairwise",
+            config
+                .pairwise_algorithms
+                .iter()
+                .map(|x| x.to_string())
+                .collect::<Vec<String>>()
+                .into(),
+        ),
+        (
+            "proto",
+            config
+                .wpa_protocol_versions
+                .iter()
+                .map(|x| x.to_string())
+                .collect::<Vec<String>>()
+                .into(),
+        ),
+        ("pmf", Value::new(config.pmf)),
+    ]);
 
     if let Some(password) = &config.password {
         security.insert("psk", password.to_string().into());
@@ -878,7 +905,7 @@ fn wireless_config_from_dbus(conn: &OwnedNestedHash) -> Option<WirelessConfig> {
         wireless_config.band = Some(band.downcast_ref::<str>()?.try_into().ok()?)
     }
     if let Some(channel) = wireless.get("channel") {
-        wireless_config.channel = Some(*channel.downcast_ref()?);
+        wireless_config.channel = *channel.downcast_ref()?;
     }
     if let Some(bssid) = wireless.get("bssid") {
         let bssid: &zvariant::Array = bssid.downcast_ref()?;
@@ -896,7 +923,6 @@ fn wireless_config_from_dbus(conn: &OwnedNestedHash) -> Option<WirelessConfig> {
             *bssid.get(5)?,
         ));
     }
-
     if let Some(hidden) = wireless.get("hidden") {
         wireless_config.hidden = *hidden.downcast_ref::<bool>()?;
     }
@@ -931,6 +957,48 @@ fn wireless_config_from_dbus(conn: &OwnedNestedHash) -> Option<WirelessConfig> {
             }
             _ => wireless_config.wep_security = None,
         }
+        if let Some(group_algorithms) = security.get("group") {
+            let group_algorithms: &zvariant::Array = group_algorithms.downcast_ref()?;
+            let group_algorithms: Vec<&str> = group_algorithms
+                .iter()
+                .map(|x| x.downcast_ref::<str>())
+                .collect::<Option<Vec<&str>>>()?;
+            let group_algorithms: Vec<GroupAlgorithm> = group_algorithms
+                .iter()
+                .map(|x| GroupAlgorithm::from_str(x))
+                .collect::<Result<Vec<GroupAlgorithm>, InvalidGroupAlgorithm>>()
+                .ok()?;
+            wireless_config.group_algorithms = group_algorithms
+        }
+        if let Some(pairwise_algorithms) = security.get("pairwise") {
+            let pairwise_algorithms: &zvariant::Array = pairwise_algorithms.downcast_ref()?;
+            let pairwise_algorithms: Vec<&str> = pairwise_algorithms
+                .iter()
+                .map(|x| x.downcast_ref::<str>())
+                .collect::<Option<Vec<&str>>>()?;
+            let pairwise_algorithms: Vec<PairwiseAlgorithm> = pairwise_algorithms
+                .iter()
+                .map(|x| PairwiseAlgorithm::from_str(x))
+                .collect::<Result<Vec<PairwiseAlgorithm>, InvalidPairwiseAlgorithm>>()
+                .ok()?;
+            wireless_config.pairwise_algorithms = pairwise_algorithms
+        }
+        if let Some(wpa_protocol_versions) = security.get("proto") {
+            let wpa_protocol_versions: &zvariant::Array = wpa_protocol_versions.downcast_ref()?;
+            let wpa_protocol_versions: Vec<&str> = wpa_protocol_versions
+                .iter()
+                .map(|x| x.downcast_ref::<str>())
+                .collect::<Option<Vec<&str>>>()?;
+            let wpa_protocol_versions: Vec<WPAProtocolVersion> = wpa_protocol_versions
+                .iter()
+                .map(|x| WPAProtocolVersion::from_str(x))
+                .collect::<Result<Vec<WPAProtocolVersion>, InvalidWPAProtocolVersion>>()
+                .ok()?;
+            wireless_config.wpa_protocol_versions = wpa_protocol_versions
+        }
+        if let Some(pmf) = security.get("pmf") {
+            wireless_config.pmf = *pmf.downcast_ref::<i32>()?;
+        }
     }
 
     Some(wireless_config)
@@ -1369,6 +1437,16 @@ mod test {
             ),
             ("auth-alg".to_string(), Value::new("open").to_owned()),
             ("wep-tx-keyidx".to_string(), Value::new(1_u32).to_owned()),
+            (
+                "group".to_string(),
+                Value::new(vec!["wep40", "tkip"]).to_owned(),
+            ),
+            (
+                "pairwise".to_string(),
+                Value::new(vec!["tkip", "ccmp"]).to_owned(),
+            ),
+            ("proto".to_string(), Value::new(vec!["rsn"]).to_owned()),
+            ("pmf".to_string(), Value::new(2_i32).to_owned()),
         ]);
 
         let dbus_conn = HashMap::from([
@@ -1385,13 +1463,26 @@ mod test {
             assert_eq!(wireless.mode, WirelessMode::Infra);
             assert_eq!(wireless.security, SecurityProtocol::WPA2);
             assert_eq!(wireless.band, Some(WirelessBand::A));
-            assert_eq!(wireless.channel, Some(32_u32));
+            assert_eq!(wireless.channel, 32_u32);
             assert_eq!(
                 wireless.bssid,
                 Some(macaddr::MacAddr6::from_str("12:34:56:78:9A:BC").unwrap())
             );
             assert!(!wireless.hidden);
             assert_eq!(wireless.wep_security, None);
+            assert_eq!(
+                wireless.group_algorithms,
+                vec![GroupAlgorithm::Wep40, GroupAlgorithm::Tkip]
+            );
+            assert_eq!(
+                wireless.pairwise_algorithms,
+                vec![PairwiseAlgorithm::Tkip, PairwiseAlgorithm::Ccmp]
+            );
+            assert_eq!(
+                wireless.wpa_protocol_versions,
+                vec![WPAProtocolVersion::Rsn]
+            );
+            assert_eq!(wireless.pmf, 2_i32);
         }
     }
 
@@ -1569,7 +1660,7 @@ mod test {
             password: Some("wpa-password".to_string()),
             ssid: SSID(vec![97, 103, 97, 109, 97]),
             band: Some(WirelessBand::BG),
-            channel: Some(10),
+            channel: 10,
             bssid: Some(macaddr::MacAddr6::from_str("12:34:56:78:9A:BC").unwrap()),
             wep_security: Some(WEPSecurity {
                 auth_alg: WEPAuthAlg::Open,
@@ -1581,6 +1672,10 @@ mod test {
                 ],
             }),
             hidden: true,
+            group_algorithms: vec![GroupAlgorithm::Wep104, GroupAlgorithm::Tkip],
+            pairwise_algorithms: vec![PairwiseAlgorithm::Tkip, PairwiseAlgorithm::Ccmp],
+            wpa_protocol_versions: vec![WPAProtocolVersion::Wpa],
+            pmf: 1,
             ..Default::default()
         };
         let mut wireless = build_base_connection();
@@ -1656,6 +1751,51 @@ mod test {
         assert_eq!(wep_key0, "5b73215e232f4c577c5073455d");
         let wep_key1: &str = security.get("wep-key1").unwrap().downcast_ref().unwrap();
         assert_eq!(wep_key1, "hello");
+
+        let group_algorithms: &zvariant::Array =
+            security.get("group").unwrap().downcast_ref().unwrap();
+        let group_algorithms: Vec<GroupAlgorithm> = group_algorithms
+            .get()
+            .iter()
+            .map(|x| x.downcast_ref::<str>().unwrap())
+            .collect::<Vec<&str>>()
+            .iter()
+            .map(|x| GroupAlgorithm::from_str(x).unwrap())
+            .collect();
+        assert_eq!(
+            group_algorithms,
+            vec![GroupAlgorithm::Wep104, GroupAlgorithm::Tkip]
+        );
+
+        let pairwise_algorithms: &zvariant::Array =
+            security.get("pairwise").unwrap().downcast_ref().unwrap();
+        let pairwise_algorithms: Vec<PairwiseAlgorithm> = pairwise_algorithms
+            .get()
+            .iter()
+            .map(|x| x.downcast_ref::<str>().unwrap())
+            .collect::<Vec<&str>>()
+            .iter()
+            .map(|x| PairwiseAlgorithm::from_str(x).unwrap())
+            .collect();
+        assert_eq!(
+            pairwise_algorithms,
+            vec![PairwiseAlgorithm::Tkip, PairwiseAlgorithm::Ccmp]
+        );
+
+        let wpa_protocol_versions: &zvariant::Array =
+            security.get("proto").unwrap().downcast_ref().unwrap();
+        let wpa_protocol_versions: Vec<WPAProtocolVersion> = wpa_protocol_versions
+            .get()
+            .iter()
+            .map(|x| x.downcast_ref::<str>().unwrap())
+            .collect::<Vec<&str>>()
+            .iter()
+            .map(|x| WPAProtocolVersion::from_str(x).unwrap())
+            .collect();
+        assert_eq!(wpa_protocol_versions, vec![WPAProtocolVersion::Wpa]);
+
+        let pmf: i32 = *security.get("pmf").unwrap().downcast_ref().unwrap();
+        assert_eq!(pmf, 1);
     }
 
     #[test]