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