1use anyhow::{Error, format_err};
6use clap::{Parser, Subcommand};
7use eui48::MacAddress;
8use flex_fuchsia_wlan_common as wlan_common;
9use flex_fuchsia_wlan_policy as wlan_policy;
10
11#[derive(PartialEq, Copy, Clone, Debug, clap::ValueEnum)]
12pub enum RoleArg {
13 Client,
14 Ap,
15}
16
17#[derive(PartialEq, Copy, Clone, Debug, clap::ValueEnum)]
18pub enum ScanTypeArg {
19 Active,
20 Passive,
21}
22
23#[derive(PartialEq, Copy, Clone, Debug, clap::ValueEnum)]
24pub enum SecurityTypeArg {
25 None,
26 Wep,
27 Wpa,
28 Wpa2,
29 Wpa3,
30}
31
32#[derive(PartialEq, Copy, Clone, Debug, clap::ValueEnum)]
33pub enum CredentialTypeArg {
34 None,
35 Psk,
36 Password,
37}
38
39impl From<RoleArg> for wlan_common::WlanMacRole {
40 fn from(arg: RoleArg) -> Self {
41 match arg {
42 RoleArg::Client => wlan_common::WlanMacRole::Client,
43 RoleArg::Ap => wlan_common::WlanMacRole::Ap,
44 }
45 }
46}
47
48impl From<ScanTypeArg> for wlan_common::ScanType {
49 fn from(arg: ScanTypeArg) -> Self {
50 match arg {
51 ScanTypeArg::Active => wlan_common::ScanType::Active,
52 ScanTypeArg::Passive => wlan_common::ScanType::Passive,
53 }
54 }
55}
56
57impl From<SecurityTypeArg> for wlan_policy::SecurityType {
58 fn from(arg: SecurityTypeArg) -> Self {
59 match arg {
60 SecurityTypeArg::r#None => wlan_policy::SecurityType::None,
61 SecurityTypeArg::Wep => wlan_policy::SecurityType::Wep,
62 SecurityTypeArg::Wpa => wlan_policy::SecurityType::Wpa,
63 SecurityTypeArg::Wpa2 => wlan_policy::SecurityType::Wpa2,
64 SecurityTypeArg::Wpa3 => wlan_policy::SecurityType::Wpa3,
65 }
66 }
67}
68
69impl From<PolicyNetworkConfig> for wlan_policy::NetworkConfig {
70 fn from(arg: PolicyNetworkConfig) -> Self {
71 let credential = match arg.credential_type {
72 Some(CredentialTypeArg::r#None) => wlan_policy::Credential::None(wlan_policy::Empty),
73 Some(CredentialTypeArg::Psk) => {
74 wlan_policy::Credential::Psk(parse_psk_string(arg.credential.unwrap()))
75 }
76 Some(CredentialTypeArg::Password) => {
77 wlan_policy::Credential::Password(arg.credential.unwrap().as_bytes().to_vec())
78 }
79 None => {
80 credential_from_string(arg.credential.unwrap_or_else(|| "".to_string()))
82 }
83 };
84
85 let security_type = security_type_from_args(arg.security_type, &credential);
86
87 let network_id = wlan_policy::NetworkIdentifier {
88 ssid: arg.ssid.as_bytes().to_vec(),
89 type_: security_type,
90 };
91 wlan_policy::NetworkConfig {
92 id: Some(network_id),
93 credential: Some(credential),
94 ..Default::default()
95 }
96 }
97}
98
99fn parse_psk_string(credential: String) -> Vec<u8> {
102 let psk_arg = credential.as_bytes().to_vec();
103 hex::decode(psk_arg).expect(
104 "Error: PSK must be 64 hexadecimal characters.\
105 Example: \"123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF1234\"",
106 )
107}
108
109fn credential_from_string(credential: String) -> wlan_policy::Credential {
112 match credential.len() {
113 0 => wlan_policy::Credential::None(wlan_policy::Empty),
114 0..=63 => wlan_policy::Credential::Password(credential.into_bytes()),
115 64 => wlan_policy::Credential::Psk(parse_psk_string(credential)),
116 65..=usize::MAX => {
117 panic!(
118 "Provided credential is too long. A password must be between 0 and 63 \
119 characters and a PSK must be 64 hexadecimal characters. Provided \
120 credential is {} characters.",
121 credential.len()
122 );
123 }
124 _ => {
125 panic!("Invalid credential of length {}", credential.len())
127 }
128 }
129}
130
131fn security_type_from_args(
134 security_arg: Option<SecurityTypeArg>,
135 credential: &wlan_policy::Credential,
136) -> wlan_policy::SecurityType {
137 if let Some(arg) = security_arg {
138 match arg {
139 SecurityTypeArg::Wep => wlan_policy::SecurityType::Wep,
140 SecurityTypeArg::Wpa => wlan_policy::SecurityType::Wpa,
141 SecurityTypeArg::Wpa2 => wlan_policy::SecurityType::Wpa2,
142 SecurityTypeArg::Wpa3 => wlan_policy::SecurityType::Wpa3,
143 SecurityTypeArg::r#None => wlan_policy::SecurityType::None,
144 }
145 } else {
146 match credential {
147 wlan_policy::Credential::None(_) => wlan_policy::SecurityType::None,
148 _ => wlan_policy::SecurityType::Wpa2,
149 }
150 }
151}
152
153#[derive(clap::Args, Clone, Debug)]
154pub struct PolicyNetworkConfig {
155 #[arg(long)]
156 pub ssid: String,
157 #[arg(long = "security-type", value_enum, ignore_case = true)]
158 pub security_type: Option<SecurityTypeArg>,
159 #[arg(long = "credential-type", value_enum, ignore_case = true)]
160 pub credential_type: Option<CredentialTypeArg>,
161 #[arg(long)]
162 pub credential: Option<String>,
163}
164
165#[derive(clap::Args, Clone, Debug)]
176pub struct RemoveArgs {
177 #[arg(long)]
178 pub ssid: String,
179 #[arg(long = "security-type", value_enum, ignore_case = true)]
180 pub security_type: Option<SecurityTypeArg>,
181 #[arg(long = "credential-type", value_enum, ignore_case = true)]
182 pub credential_type: Option<CredentialTypeArg>,
183 #[arg(long)]
184 pub credential: Option<String>,
185}
186
187impl RemoveArgs {
188 pub fn parse_security(&self) -> Option<wlan_policy::SecurityType> {
189 self.security_type.map(|s| s.into())
190 }
191
192 pub fn try_parse_credential(&self) -> Result<Option<wlan_policy::Credential>, Error> {
199 let credential = if let Some(cred_val) = &self.credential {
200 match self.credential_type {
201 Some(CredentialTypeArg::Password) => {
202 Some(wlan_policy::Credential::Password(cred_val.as_bytes().to_vec()))
203 }
204 Some(CredentialTypeArg::Psk) => {
205 let psk_arg = cred_val.as_bytes().to_vec();
208 let psk = hex::decode(psk_arg).expect(
209 "Error: PSK must be 64 hexadecimal characters.\
210 Example: \"123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF1234\"",
211 );
212 Some(wlan_policy::Credential::Psk(psk))
213 }
214 Some(CredentialTypeArg::None) => {
215 if cred_val.is_empty() {
218 Some(wlan_policy::Credential::None(wlan_policy::Empty))
219 } else {
220 return Err(format_err!(
221 "A credential value was provided with a credential \
222 type of None. No value be provided if there is no credential, or the \
223 credential type should be PSK or Password."
224 ));
225 }
226 }
227 None => {
228 if cred_val.is_empty() {
232 Some(wlan_policy::Credential::None(wlan_policy::Empty))
233 } else {
234 Some(wlan_policy::Credential::Password(cred_val.as_bytes().to_vec()))
235 }
236 }
237 }
238 } else {
239 if let Some(credential_type) = self.credential_type {
240 if credential_type == CredentialTypeArg::None {
241 Some(wlan_policy::Credential::None(wlan_policy::Empty))
242 } else {
243 return Err(format_err!(
244 "A credential type was provided with no value. Please \
245 provide the credential to be used"
246 ));
247 }
248 } else {
249 None
253 }
254 };
255 Ok(credential)
256 }
257}
258
259#[derive(clap::Args, Clone, Debug)]
260pub struct SaveNetworkArgs {
261 #[arg(long)]
262 pub ssid: String,
263 #[arg(long = "security-type", value_enum, ignore_case = true)]
264 pub security_type: SecurityTypeArg,
265 #[arg(long = "credential-type", value_enum, ignore_case = true)]
266 pub credential_type: Option<CredentialTypeArg>,
267 #[arg(long)]
268 pub credential: String,
269}
270
271#[derive(clap::Args, Clone, Debug)]
272pub struct ConnectArgs {
273 #[arg(long)]
274 pub ssid: String,
275 #[arg(long = "security-type", value_enum, ignore_case = true)]
276 pub security_type: Option<SecurityTypeArg>,
277}
278
279#[derive(Subcommand, Clone, Debug)]
280pub enum PolicyClientCmd {
281 #[command(name = "connect")]
282 Connect(ConnectArgs),
283 #[command(name = "list-saved-networks")]
284 GetSavedNetworks,
285 #[command(name = "listen")]
286 Listen,
287 #[command(name = "remove-network")]
288 RemoveNetwork(RemoveArgs),
289 #[command(name = "save-network")]
290 SaveNetwork(PolicyNetworkConfig),
291 #[command(name = "scan")]
292 ScanForNetworks,
293 #[command(name = "start-client-connections")]
294 StartClientConnections,
295 #[command(name = "stop-client-connections")]
296 StopClientConnections,
297 #[command(name = "dump-config")]
298 DumpConfig,
299 #[command(name = "restore-config")]
300 RestoreConfig { serialized_config: String },
301 #[command(name = "status")]
302 Status,
303}
304
305#[derive(Subcommand, Clone, Debug)]
306pub enum PolicyAccessPointCmd {
307 #[command(name = "start")]
309 Start(PolicyNetworkConfig),
310 #[command(name = "stop")]
311 Stop(PolicyNetworkConfig),
312 #[command(name = "stop-all")]
313 StopAllAccessPoints,
314 #[command(name = "listen")]
315 Listen,
316 #[command(name = "status")]
317 Status,
318}
319
320#[derive(Subcommand, Clone, Debug)]
321pub enum DeprecatedConfiguratorCmd {
322 #[command(name = "suggest-mac")]
323 SuggestAccessPointMacAddress {
324 #[arg(required = true)]
325 mac: MacAddress,
326 },
327}
328
329#[derive(Parser, Clone, Debug)]
330pub enum Opt {
331 #[command(subcommand, name = "client")]
332 Client(PolicyClientCmd),
333 #[command(subcommand, name = "ap")]
334 AccessPoint(PolicyAccessPointCmd),
335 #[command(subcommand, name = "deprecated")]
336 Deprecated(DeprecatedConfiguratorCmd),
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use test_case::test_case;
343
344 #[fuchsia::test]
346 fn test_construct_config_wep() {
347 test_construct_config_security(wlan_policy::SecurityType::Wep, SecurityTypeArg::Wep);
348 }
349
350 #[fuchsia::test]
352 fn test_construct_config_wpa() {
353 test_construct_config_security(wlan_policy::SecurityType::Wpa, SecurityTypeArg::Wpa);
354 }
355
356 #[fuchsia::test]
358 fn test_construct_config_wpa2() {
359 test_construct_config_security(wlan_policy::SecurityType::Wpa2, SecurityTypeArg::Wpa2);
360 }
361
362 #[fuchsia::test]
364 fn test_construct_config_wpa3() {
365 test_construct_config_security(wlan_policy::SecurityType::Wpa3, SecurityTypeArg::Wpa3);
366 }
367
368 #[fuchsia::test]
371 fn test_construct_config_open() {
372 let open_config = PolicyNetworkConfig {
373 ssid: "some_ssid".to_string(),
374 security_type: None,
375 credential_type: None,
376 credential: Some("".to_string()),
377 };
378 let expected_cfg = wlan_policy::NetworkConfig {
379 id: Some(wlan_policy::NetworkIdentifier {
380 ssid: "some_ssid".as_bytes().to_vec(),
381 type_: wlan_policy::SecurityType::None,
382 }),
383 credential: Some(wlan_policy::Credential::None(wlan_policy::Empty {})),
384 ..Default::default()
385 };
386 let result_cfg = wlan_policy::NetworkConfig::from(open_config);
387 assert_eq!(expected_cfg, result_cfg);
388 }
389
390 #[fuchsia::test]
393 fn test_construct_config_open_with_omitted_args() {
394 let open_config = PolicyNetworkConfig {
395 ssid: "some_ssid".to_string(),
396 security_type: Some(SecurityTypeArg::None),
397 credential_type: Some(CredentialTypeArg::None),
398 credential: Some("".to_string()),
399 };
400 let expected_cfg = wlan_policy::NetworkConfig {
401 id: Some(wlan_policy::NetworkIdentifier {
402 ssid: "some_ssid".as_bytes().to_vec(),
403 type_: wlan_policy::SecurityType::None,
404 }),
405 credential: Some(wlan_policy::Credential::None(wlan_policy::Empty {})),
406 ..Default::default()
407 };
408 let result_cfg = wlan_policy::NetworkConfig::from(open_config);
409 assert_eq!(expected_cfg, result_cfg);
410 }
411
412 #[fuchsia::test]
415 fn test_construct_config_password_provided_no_security() {
416 let password = "mypassword";
417 let ssid = "some_ssid";
418 let arg_config = PolicyNetworkConfig {
419 ssid: ssid.to_string(),
420 security_type: None,
421 credential_type: None,
422 credential: Some(password.to_string()),
423 };
424 let expected_cfg = wlan_policy::NetworkConfig {
425 id: Some(wlan_policy::NetworkIdentifier {
426 ssid: ssid.as_bytes().to_vec(),
427 type_: wlan_policy::SecurityType::Wpa2,
428 }),
429 credential: Some(wlan_policy::Credential::Password(password.as_bytes().to_vec())),
430 ..Default::default()
431 };
432 let result_cfg = wlan_policy::NetworkConfig::from(arg_config);
433 assert_eq!(expected_cfg, result_cfg);
434 }
435
436 #[fuchsia::test]
439 fn test_construct_config_psk_provided_no_security() {
440 let psk = "123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF1234".to_string();
441 let psk_bytes = hex::decode(psk.as_bytes().to_vec()).unwrap();
442 let ssid = "some_ssid";
443 let arg_config = PolicyNetworkConfig {
444 ssid: ssid.to_string(),
445 security_type: None,
446 credential_type: None,
447 credential: Some(psk),
448 };
449 let expected_cfg = wlan_policy::NetworkConfig {
450 id: Some(wlan_policy::NetworkIdentifier {
451 ssid: ssid.as_bytes().to_vec(),
452 type_: wlan_policy::SecurityType::Wpa2,
453 }),
454 credential: Some(wlan_policy::Credential::Psk(psk_bytes)),
455 ..Default::default()
456 };
457 let result_cfg = wlan_policy::NetworkConfig::from(arg_config);
458 assert_eq!(expected_cfg, result_cfg);
459 }
460
461 #[fuchsia::test]
464 fn test_construct_config_psk() {
465 const ASCII_ZERO: u8 = 49;
467 let psk =
468 String::from_utf8([ASCII_ZERO; 64].to_vec()).expect("Failed to create PSK test value");
469 let wpa_config = PolicyNetworkConfig {
470 ssid: "some_ssid".to_string(),
471 security_type: Some(SecurityTypeArg::Wpa2),
472 credential_type: Some(CredentialTypeArg::Psk),
473 credential: Some(psk),
474 };
475 let expected_cfg = wlan_policy::NetworkConfig {
476 id: Some(wlan_policy::NetworkIdentifier {
477 ssid: "some_ssid".as_bytes().to_vec(),
478 type_: wlan_policy::SecurityType::Wpa2,
479 }),
480 credential: Some(wlan_policy::Credential::Psk([17; 32].to_vec())),
481 ..Default::default()
482 };
483 let result_cfg = wlan_policy::NetworkConfig::from(wpa_config);
484 assert_eq!(expected_cfg, result_cfg);
485 }
486
487 fn test_construct_config_security(
490 fidl_type: wlan_policy::SecurityType,
491 tool_type: SecurityTypeArg,
492 ) {
493 let args_config = PolicyNetworkConfig {
494 ssid: "some_ssid".to_string(),
495 security_type: Some(tool_type),
496 credential_type: Some(CredentialTypeArg::Password),
497 credential: Some("some_password_here".to_string()),
498 };
499 let expected_cfg = wlan_policy::NetworkConfig {
500 id: Some(wlan_policy::NetworkIdentifier {
501 ssid: "some_ssid".as_bytes().to_vec(),
502 type_: fidl_type,
503 }),
504 credential: Some(wlan_policy::Credential::Password(
505 "some_password_here".as_bytes().to_vec(),
506 )),
507 ..Default::default()
508 };
509 let result_cfg = wlan_policy::NetworkConfig::from(args_config);
510 assert_eq!(expected_cfg, result_cfg);
511 }
512
513 #[test_case(Some(SecurityTypeArg::None))]
516 #[test_case(None)]
517 fn test_try_parse_credentialcredential_not_provided(security_type: Option<SecurityTypeArg>) {
518 let args = RemoveArgs {
519 ssid: "some_ssid".to_string(),
520 security_type,
521 credential_type: None,
522 credential: None,
523 };
524 let expected_credential = None;
525 let result = args.try_parse_credential().expect("error occurred parsing credential");
526 assert_eq!(result, expected_credential);
527 }
528
529 #[fuchsia::test]
532 fn test_try_parse_credential_open_network_credential_empty_string() {
533 let args = RemoveArgs {
534 ssid: "some_ssid".to_string(),
535 security_type: Some(SecurityTypeArg::None),
536 credential_type: None,
537 credential: Some("".to_string()),
538 };
539 let expected_credential = Some(wlan_policy::Credential::None(wlan_policy::Empty));
540 let result = args.try_parse_credential().expect("error occurred parsing credential");
541 assert_eq!(result, expected_credential);
542 }
543
544 #[fuchsia::test]
547 fn test_try_parse_credential_with_no_type() {
548 let password = "somepassword";
549 let args = RemoveArgs {
550 ssid: "some_ssid".to_string(),
551 security_type: None,
552 credential_type: None,
553 credential: Some(password.to_string()),
554 };
555 let expected_credential =
556 Some(wlan_policy::Credential::Password(password.as_bytes().to_vec()));
557 let result = args.try_parse_credential().expect("error occurred parsing credential");
558 assert_eq!(result, expected_credential);
559 assert_eq!(args.parse_security(), None);
560 }
561
562 #[fuchsia::test]
565 fn test_try_parse_credential_exact_args_provided() {
566 let psk = "123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF1234";
567 let args = RemoveArgs {
568 ssid: "some_ssid".to_string(),
569 security_type: Some(SecurityTypeArg::None),
570 credential_type: Some(CredentialTypeArg::Psk),
571 credential: Some(psk.to_string()),
572 };
573 let expected_credential = Some(wlan_policy::Credential::Psk(hex::decode(psk).unwrap()));
574 let result = args.try_parse_credential().expect("error occurred parsing credential");
575 assert_eq!(result, expected_credential);
576 }
577
578 #[fuchsia::test]
581 fn test_try_parse_credential_password_with_no_value_is_err() {
582 let args = RemoveArgs {
583 ssid: "some_ssid".to_string(),
584 security_type: Some(SecurityTypeArg::Wpa2),
585 credential_type: Some(CredentialTypeArg::Password),
586 credential: None,
587 };
588 args.try_parse_credential().expect_err("an error should occur parsing this credential");
589 }
590}