donut_lib/
serialize.rs

1// Copyright 2020 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 anyhow::{format_err, Error};
6use fidl_fuchsia_wlan_policy as wlan_policy;
7use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, CONTROLS};
8use serde::{Deserialize, Serialize};
9
10#[derive(Serialize, Deserialize)]
11struct WlanConfigDump {
12    version: u32,
13    data: Vec<SimplifiedNetworkConfig>,
14}
15#[derive(Serialize, Deserialize, Clone)]
16struct SimplifiedNetworkConfig {
17    ssid: Vec<u8>,
18    credential: Credential,
19    security_type: SecurityType,
20}
21
22#[derive(Serialize, Deserialize, Clone)]
23enum Credential {
24    None,
25    Password(Vec<u8>),
26    Psk(Vec<u8>),
27}
28
29#[derive(Serialize, Deserialize, Clone)]
30enum SecurityType {
31    None = 1,
32    Wep = 2,
33    Wpa = 3,
34    Wpa2 = 4,
35    Wpa3 = 5,
36}
37
38impl From<wlan_policy::SecurityType> for SecurityType {
39    fn from(security: wlan_policy::SecurityType) -> Self {
40        match security {
41            wlan_policy::SecurityType::None => SecurityType::None,
42            wlan_policy::SecurityType::Wep => SecurityType::Wep,
43            wlan_policy::SecurityType::Wpa => SecurityType::Wpa,
44            wlan_policy::SecurityType::Wpa2 => SecurityType::Wpa2,
45            wlan_policy::SecurityType::Wpa3 => SecurityType::Wpa3,
46        }
47    }
48}
49
50impl From<SecurityType> for wlan_policy::SecurityType {
51    fn from(security: SecurityType) -> Self {
52        match security {
53            SecurityType::None => wlan_policy::SecurityType::None,
54            SecurityType::Wep => wlan_policy::SecurityType::Wep,
55            SecurityType::Wpa => wlan_policy::SecurityType::Wpa,
56            SecurityType::Wpa2 => wlan_policy::SecurityType::Wpa2,
57            SecurityType::Wpa3 => wlan_policy::SecurityType::Wpa3,
58        }
59    }
60}
61
62impl TryFrom<wlan_policy::Credential> for Credential {
63    type Error = &'static str;
64
65    fn try_from(security: wlan_policy::Credential) -> Result<Self, Self::Error> {
66        match security {
67            wlan_policy::Credential::None(_) => Ok(Credential::None),
68            wlan_policy::Credential::Password(pass) => Ok(Credential::Password(pass)),
69            wlan_policy::Credential::Psk(psk) => Ok(Credential::Psk(psk)),
70            wlan_policy::CredentialUnknown!() => Err("Unrecognized credential"),
71        }
72    }
73}
74
75impl From<Credential> for wlan_policy::Credential {
76    fn from(security: Credential) -> Self {
77        match security {
78            Credential::None => wlan_policy::Credential::None(wlan_policy::Empty),
79            Credential::Password(pass) => wlan_policy::Credential::Password(pass),
80            Credential::Psk(psk) => wlan_policy::Credential::Psk(psk),
81        }
82    }
83}
84
85impl TryFrom<wlan_policy::NetworkConfig> for SimplifiedNetworkConfig {
86    type Error = &'static str;
87
88    fn try_from(config: wlan_policy::NetworkConfig) -> Result<Self, Self::Error> {
89        let id = match config.id {
90            Some(id) => id,
91            None => return Err("Network has no identifier"),
92        };
93
94        let credential = match config.credential {
95            Some(credential) => credential,
96            None => return Err("Network has no credential"),
97        };
98
99        let credential = credential.try_into()?;
100
101        Ok(SimplifiedNetworkConfig {
102            ssid: id.ssid,
103            credential: credential,
104            security_type: id.type_.into(),
105        })
106    }
107}
108
109impl From<SimplifiedNetworkConfig> for wlan_policy::NetworkConfig {
110    fn from(simplified_config: SimplifiedNetworkConfig) -> Self {
111        Self {
112            id: Some(wlan_policy::NetworkIdentifier {
113                ssid: simplified_config.ssid,
114                type_: simplified_config.security_type.into(),
115            }),
116            credential: Some(simplified_config.credential.into()),
117            ..Default::default()
118        }
119    }
120}
121
122/// Serializes a vector of network configurations for storing
123pub fn serialize_saved_networks(
124    saved_networks: Vec<wlan_policy::NetworkConfig>,
125) -> Result<String, Error> {
126    let simplified_saved_networks = saved_networks
127        .iter()
128        .filter_map(|network| match SimplifiedNetworkConfig::try_from(network.clone()) {
129            Ok(network) => Some(network),
130            Err(_e) => None,
131        })
132        .collect();
133    let dump = WlanConfigDump { version: 1, data: simplified_saved_networks };
134    let json = serde_json::to_string(&dump)?;
135    // Percent-encode the data to get rid of characters that are problematic for piping in shells
136    const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
137    let percent_encoded = utf8_percent_encode(&json, FRAGMENT).to_string();
138    Ok(percent_encoded)
139}
140
141pub fn deserialize_saved_networks(
142    raw_data: String,
143) -> Result<Vec<wlan_policy::NetworkConfig>, Error> {
144    let json = percent_decode_str(&raw_data).decode_utf8()?;
145    let data: WlanConfigDump =
146        serde_json::from_str(&json).map_err(|e| format_err!("Failed to parse config: {}", e))?;
147    let networks: Vec<wlan_policy::NetworkConfig> =
148        data.data.iter().map(|network| wlan_policy::NetworkConfig::from(network.clone())).collect();
149    Ok(networks)
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use ieee80211::Ssid;
156
157    fn generate_test_data() -> (String, Vec<wlan_policy::NetworkConfig>) {
158        let serialized = "{%22version%22:1,%22data%22:[{%22ssid%22:[84,101,115,116,87,76,65,78,49],%22credential%22:{%22Password%22:[49,50,51,52,53,54,55,56]},%22security_type%22:%22Wpa2%22}]}".to_string();
159
160        let deserialized: Vec<wlan_policy::NetworkConfig> = vec![wlan_policy::NetworkConfig {
161            id: Some(wlan_policy::NetworkIdentifier {
162                ssid: Ssid::try_from("TestWLAN1").unwrap().into(),
163                type_: wlan_policy::SecurityType::Wpa2,
164            }),
165            credential: Some(wlan_policy::Credential::Password("12345678".as_bytes().to_vec())),
166            ..Default::default()
167        }];
168
169        (serialized, deserialized)
170    }
171
172    #[fuchsia::test]
173    fn test_serialization() {
174        let (serialized, deserialized) = generate_test_data();
175        assert_eq!(serialized, serialize_saved_networks(deserialized).unwrap());
176    }
177
178    #[fuchsia::test]
179    fn test_serialization_with_malformed_network() {
180        let (serialized, mut deserialized) = generate_test_data();
181        // Add another network that's malformed (has no credential field)
182        let mut malformed = vec![wlan_policy::NetworkConfig {
183            id: Some(wlan_policy::NetworkIdentifier {
184                ssid: Ssid::try_from("MALFORMED - NO CREDENTIAL").unwrap().into(),
185                type_: wlan_policy::SecurityType::Wpa2,
186            }),
187            ..Default::default()
188        }];
189        deserialized.append(&mut malformed);
190        // Only the correctly-formed network should be serialized
191        assert_eq!(serialized, serialize_saved_networks(deserialized).unwrap());
192    }
193
194    #[fuchsia::test]
195    fn test_deserialization() {
196        let (serialized, deserialized) = generate_test_data();
197        assert_eq!(deserialized, deserialize_saved_networks(serialized).unwrap());
198    }
199}