1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use crate::args::{self, Beacon, Command}; 16 use crate::ffi::frontend_client_ffi::GrpcMethod; 17 18 impl args::Command { 19 /// Return the respective GrpcMethod for the command grpc_method(&self) -> GrpcMethod20 pub fn grpc_method(&self) -> GrpcMethod { 21 match self { 22 Command::Version => GrpcMethod::GetVersion, 23 Command::Radio(_) => GrpcMethod::PatchDevice, 24 Command::Move(_) => GrpcMethod::PatchDevice, 25 Command::Devices(_) => GrpcMethod::ListDevice, 26 Command::Reset => GrpcMethod::Reset, 27 Command::Capture(cmd) => match cmd { 28 args::Capture::List(_) => GrpcMethod::ListCapture, 29 args::Capture::Get(_) => GrpcMethod::GetCapture, 30 args::Capture::Patch(_) => GrpcMethod::PatchCapture, 31 }, 32 Command::Gui => { 33 panic!("No GrpcMethod for Ui Command."); 34 } 35 Command::Artifact => { 36 panic!("No GrpcMethod for Artifact Command."); 37 } 38 Command::Beacon(action) => match action { 39 Beacon::Create(_) => GrpcMethod::CreateDevice, 40 Beacon::Patch(_) => GrpcMethod::PatchDevice, 41 Beacon::Remove(_) => GrpcMethod::DeleteChip, 42 }, 43 Command::Bumble => { 44 panic!("No GrpcMethod for Bumble Command."); 45 } 46 } 47 } 48 } 49 50 #[cfg(test)] 51 mod tests { 52 use super::*; 53 use args::{BinaryProtobuf, NetsimArgs}; 54 use clap::Parser; 55 use netsim_proto::frontend::{ 56 patch_device_request::PatchDeviceFields as PatchDeviceFieldsProto, CreateDeviceRequest, 57 PatchDeviceRequest, 58 }; 59 use netsim_proto::model::chip::ble_beacon::AdvertiseData as AdvertiseDataProto; 60 use netsim_proto::model::chip::{ 61 ble_beacon::{ 62 advertise_settings::{ 63 AdvertiseMode as AdvertiseModeProto, AdvertiseTxPower as AdvertiseTxPowerProto, 64 Interval as IntervalProto, Tx_power as TxPowerProto, 65 }, 66 AdvertiseSettings as AdvertiseSettingsProto, 67 }, 68 BleBeacon as BleBeaconProto, Chip as ChipKindProto, 69 }; 70 use netsim_proto::model::chip_create::{ 71 BleBeaconCreate as BleBeaconCreateProto, Chip as ChipKindCreateProto, 72 }; 73 use netsim_proto::model::{ 74 Chip as ChipProto, ChipCreate as ChipCreateProto, DeviceCreate as DeviceCreateProto, 75 }; 76 use netsim_proto::{ 77 common::ChipKind, 78 frontend, 79 model::{ 80 self, 81 chip::{Bluetooth as Chip_Bluetooth, Radio as Chip_Radio}, 82 Position, 83 }, 84 }; 85 use protobuf::Message; 86 use protobuf::MessageField; 87 test_command( command: &str, expected_grpc_method: GrpcMethod, expected_request_byte_str: BinaryProtobuf, )88 fn test_command( 89 command: &str, 90 expected_grpc_method: GrpcMethod, 91 expected_request_byte_str: BinaryProtobuf, 92 ) { 93 let command = NetsimArgs::parse_from(command.split_whitespace()).command; 94 assert_eq!(expected_grpc_method, command.grpc_method()); 95 let request = command.get_request_bytes(); 96 assert_eq!(request, expected_request_byte_str); 97 } 98 99 #[test] test_version_request()100 fn test_version_request() { 101 test_command("netsim-cli version", GrpcMethod::GetVersion, Vec::new()) 102 } 103 get_expected_radio(name: &str, radio_type: &str, state: &str) -> BinaryProtobuf104 fn get_expected_radio(name: &str, radio_type: &str, state: &str) -> BinaryProtobuf { 105 let mut chip = model::Chip { ..Default::default() }; 106 let chip_state = state == "up"; 107 if radio_type == "wifi" { 108 let mut wifi_chip = Chip_Radio::new(); 109 wifi_chip.state = chip_state.into(); 110 chip.set_wifi(wifi_chip); 111 chip.kind = ChipKind::WIFI.into(); 112 } else if radio_type == "uwb" { 113 let mut uwb_chip = Chip_Radio::new(); 114 uwb_chip.state = chip_state.into(); 115 chip.set_uwb(uwb_chip); 116 chip.kind = ChipKind::UWB.into(); 117 } else { 118 let mut bt_chip = Chip_Bluetooth::new(); 119 let mut bt_chip_radio = Chip_Radio::new(); 120 bt_chip_radio.state = chip_state.into(); 121 if radio_type == "ble" { 122 bt_chip.low_energy = Some(bt_chip_radio).into(); 123 } else { 124 bt_chip.classic = Some(bt_chip_radio).into(); 125 } 126 chip.kind = ChipKind::BLUETOOTH.into(); 127 chip.set_bt(bt_chip); 128 } 129 let mut result = frontend::PatchDeviceRequest::new(); 130 let mut device = PatchDeviceFieldsProto::new(); 131 device.name = Some(name.to_string()); 132 device.chips.push(chip); 133 result.device = Some(device).into(); 134 result.write_to_bytes().unwrap() 135 } 136 137 #[test] test_radio_ble()138 fn test_radio_ble() { 139 test_command( 140 "netsim-cli radio ble down 1000", 141 GrpcMethod::PatchDevice, 142 get_expected_radio("1000", "ble", "down"), 143 ); 144 test_command( 145 "netsim-cli radio ble up 1000", 146 GrpcMethod::PatchDevice, 147 get_expected_radio("1000", "ble", "up"), 148 ); 149 } 150 151 #[test] test_radio_ble_aliases()152 fn test_radio_ble_aliases() { 153 test_command( 154 "netsim-cli radio ble Down 1000", 155 GrpcMethod::PatchDevice, 156 get_expected_radio("1000", "ble", "down"), 157 ); 158 test_command( 159 "netsim-cli radio ble Up 1000", 160 GrpcMethod::PatchDevice, 161 get_expected_radio("1000", "ble", "up"), 162 ); 163 test_command( 164 "netsim-cli radio ble DOWN 1000", 165 GrpcMethod::PatchDevice, 166 get_expected_radio("1000", "ble", "down"), 167 ); 168 test_command( 169 "netsim-cli radio ble UP 1000", 170 GrpcMethod::PatchDevice, 171 get_expected_radio("1000", "ble", "up"), 172 ); 173 } 174 175 #[test] test_radio_classic()176 fn test_radio_classic() { 177 test_command( 178 "netsim-cli radio classic down 100", 179 GrpcMethod::PatchDevice, 180 get_expected_radio("100", "classic", "down"), 181 ); 182 test_command( 183 "netsim-cli radio classic up 100", 184 GrpcMethod::PatchDevice, 185 get_expected_radio("100", "classic", "up"), 186 ); 187 } 188 189 #[test] test_radio_wifi()190 fn test_radio_wifi() { 191 test_command( 192 "netsim-cli radio wifi down a", 193 GrpcMethod::PatchDevice, 194 get_expected_radio("a", "wifi", "down"), 195 ); 196 test_command( 197 "netsim-cli radio wifi up b", 198 GrpcMethod::PatchDevice, 199 get_expected_radio("b", "wifi", "up"), 200 ); 201 } 202 203 #[test] test_radio_uwb()204 fn test_radio_uwb() { 205 test_command( 206 "netsim-cli radio uwb down a", 207 GrpcMethod::PatchDevice, 208 get_expected_radio("a", "uwb", "down"), 209 ); 210 test_command( 211 "netsim-cli radio uwb up b", 212 GrpcMethod::PatchDevice, 213 get_expected_radio("b", "uwb", "up"), 214 ); 215 } 216 get_expected_move(name: &str, x: f32, y: f32, z: Option<f32>) -> BinaryProtobuf217 fn get_expected_move(name: &str, x: f32, y: f32, z: Option<f32>) -> BinaryProtobuf { 218 let mut result = frontend::PatchDeviceRequest::new(); 219 let mut device = PatchDeviceFieldsProto::new(); 220 let position = Position { x, y, z: z.unwrap_or_default(), ..Default::default() }; 221 device.name = Some(name.to_string()); 222 device.position = Some(position).into(); 223 result.device = Some(device).into(); 224 result.write_to_bytes().unwrap() 225 } 226 227 #[test] test_move_int()228 fn test_move_int() { 229 test_command( 230 "netsim-cli move 1 1 2 3", 231 GrpcMethod::PatchDevice, 232 get_expected_move("1", 1.0, 2.0, Some(3.0)), 233 ) 234 } 235 236 #[test] test_move_float()237 fn test_move_float() { 238 test_command( 239 "netsim-cli move 1000 1.2 3.4 5.6", 240 GrpcMethod::PatchDevice, 241 get_expected_move("1000", 1.2, 3.4, Some(5.6)), 242 ) 243 } 244 245 #[test] test_move_mixed()246 fn test_move_mixed() { 247 test_command( 248 "netsim-cli move 1000 1.1 2 3.4", 249 GrpcMethod::PatchDevice, 250 get_expected_move("1000", 1.1, 2.0, Some(3.4)), 251 ) 252 } 253 254 #[test] test_move_no_z()255 fn test_move_no_z() { 256 test_command( 257 "netsim-cli move 1000 1.2 3.4", 258 GrpcMethod::PatchDevice, 259 get_expected_move("1000", 1.2, 3.4, None), 260 ) 261 } 262 263 #[test] test_devices()264 fn test_devices() { 265 test_command("netsim-cli devices", GrpcMethod::ListDevice, Vec::new()) 266 } 267 268 #[test] test_reset()269 fn test_reset() { 270 test_command("netsim-cli reset", GrpcMethod::Reset, Vec::new()) 271 } 272 273 #[test] test_capture_list()274 fn test_capture_list() { 275 test_command("netsim-cli capture list", GrpcMethod::ListCapture, Vec::new()) 276 } 277 278 #[test] test_capture_list_alias()279 fn test_capture_list_alias() { 280 test_command("netsim-cli pcap list", GrpcMethod::ListCapture, Vec::new()) 281 } 282 283 //TODO: Add capture patch and get tests once able to run tests with cxx definitions 284 get_create_device_req_bytes( device_name: &str, chip_name: &str, settings: AdvertiseSettingsProto, adv_data: AdvertiseDataProto, scan_response: AdvertiseDataProto, ) -> Vec<u8>285 fn get_create_device_req_bytes( 286 device_name: &str, 287 chip_name: &str, 288 settings: AdvertiseSettingsProto, 289 adv_data: AdvertiseDataProto, 290 scan_response: AdvertiseDataProto, 291 ) -> Vec<u8> { 292 let device = MessageField::some(DeviceCreateProto { 293 name: String::from(device_name), 294 chips: vec![ChipCreateProto { 295 name: String::from(chip_name), 296 kind: ChipKind::BLUETOOTH_BEACON.into(), 297 chip: Some(ChipKindCreateProto::BleBeacon(BleBeaconCreateProto { 298 settings: MessageField::some(settings), 299 adv_data: MessageField::some(adv_data), 300 scan_response: MessageField::some(scan_response), 301 ..Default::default() 302 })), 303 ..Default::default() 304 }], 305 ..Default::default() 306 }); 307 308 CreateDeviceRequest { device, ..Default::default() }.write_to_bytes().unwrap() 309 } 310 get_patch_device_req_bytes( device_name: &str, chip_name: &str, settings: AdvertiseSettingsProto, adv_data: AdvertiseDataProto, scan_response: AdvertiseDataProto, ) -> Vec<u8>311 fn get_patch_device_req_bytes( 312 device_name: &str, 313 chip_name: &str, 314 settings: AdvertiseSettingsProto, 315 adv_data: AdvertiseDataProto, 316 scan_response: AdvertiseDataProto, 317 ) -> Vec<u8> { 318 let device = MessageField::some(PatchDeviceFieldsProto { 319 name: Some(String::from(device_name)), 320 chips: vec![ChipProto { 321 name: String::from(chip_name), 322 kind: ChipKind::BLUETOOTH_BEACON.into(), 323 chip: Some(ChipKindProto::BleBeacon(BleBeaconProto { 324 bt: MessageField::some(Chip_Bluetooth::new()), 325 settings: MessageField::some(settings), 326 adv_data: MessageField::some(adv_data), 327 scan_response: MessageField::some(scan_response), 328 ..Default::default() 329 })), 330 ..Default::default() 331 }], 332 ..Default::default() 333 }); 334 335 PatchDeviceRequest { device, ..Default::default() }.write_to_bytes().unwrap() 336 } 337 338 #[test] test_beacon_create_all_params_ble()339 fn test_beacon_create_all_params_ble() { 340 let device_name = String::from("device"); 341 let chip_name = String::from("chip"); 342 343 let timeout = 1234; 344 let manufacturer_data = vec![0x12, 0x34]; 345 346 let settings = AdvertiseSettingsProto { 347 interval: Some(IntervalProto::AdvertiseMode(AdvertiseModeProto::BALANCED.into())), 348 tx_power: Some(TxPowerProto::TxPowerLevel(AdvertiseTxPowerProto::ULTRA_LOW.into())), 349 scannable: true, 350 timeout, 351 ..Default::default() 352 }; 353 354 let adv_data = AdvertiseDataProto { 355 include_device_name: true, 356 include_tx_power_level: true, 357 manufacturer_data, 358 ..Default::default() 359 }; 360 361 let request = get_create_device_req_bytes( 362 &device_name, 363 &chip_name, 364 settings, 365 adv_data, 366 Default::default(), 367 ); 368 369 test_command( 370 format!( 371 "netsim-cli beacon create ble {} {} --advertise-mode balanced --tx-power-level ultra-low --scannable --timeout {} --include-device-name --include-tx-power-level --manufacturer-data 0x1234", 372 device_name, chip_name, timeout, 373 ) 374 .as_str(), 375 GrpcMethod::CreateDevice, 376 request, 377 ) 378 } 379 380 #[test] test_beacon_patch_all_params_ble()381 fn test_beacon_patch_all_params_ble() { 382 let device_name = String::from("device"); 383 let chip_name = String::from("chip"); 384 385 let interval = 1234; 386 let timeout = 9999; 387 let tx_power_level = -3; 388 let manufacturer_data = vec![0xab, 0xcd, 0xef]; 389 390 let settings = AdvertiseSettingsProto { 391 interval: Some(IntervalProto::Milliseconds(interval)), 392 tx_power: Some(TxPowerProto::Dbm(tx_power_level)), 393 scannable: true, 394 timeout, 395 ..Default::default() 396 }; 397 let adv_data = AdvertiseDataProto { 398 include_device_name: true, 399 include_tx_power_level: true, 400 manufacturer_data, 401 ..Default::default() 402 }; 403 404 let request = get_patch_device_req_bytes( 405 &device_name, 406 &chip_name, 407 settings, 408 adv_data, 409 Default::default(), 410 ); 411 412 test_command( 413 format!( 414 "netsim-cli beacon patch ble {} {} --advertise-mode {} --scannable --timeout {} --tx-power-level {} --manufacturer-data 0xabcdef --include-device-name --include-tx-power-level", 415 device_name, chip_name, interval, timeout, tx_power_level 416 ) 417 .as_str(), 418 GrpcMethod::PatchDevice, 419 request, 420 ) 421 } 422 423 #[test] test_beacon_create_scan_response()424 fn test_beacon_create_scan_response() { 425 let device_name = String::from("device"); 426 let chip_name = String::from("chip"); 427 428 let scan_response = AdvertiseDataProto { 429 include_device_name: true, 430 include_tx_power_level: true, 431 manufacturer_data: vec![0x21, 0xbe, 0xef], 432 ..Default::default() 433 }; 434 435 let request = get_create_device_req_bytes( 436 &device_name, 437 &chip_name, 438 Default::default(), 439 Default::default(), 440 scan_response, 441 ); 442 443 test_command( 444 format!( 445 "netsim-cli beacon create ble {} {} --scan-response-include-device-name --scan-response-include-tx-power-level --scan-response-manufacturer-data 0x21beef", 446 device_name, chip_name 447 ) 448 .as_str(), 449 GrpcMethod::CreateDevice, 450 request, 451 ); 452 } 453 454 #[test] test_beacon_patch_scan_response()455 fn test_beacon_patch_scan_response() { 456 let device_name = String::from("device"); 457 let chip_name = String::from("chip"); 458 459 let scan_response = AdvertiseDataProto { 460 include_device_name: true, 461 include_tx_power_level: true, 462 manufacturer_data: vec![0x59, 0xbe, 0xac, 0x09], 463 ..Default::default() 464 }; 465 466 let request = get_patch_device_req_bytes( 467 &device_name, 468 &chip_name, 469 Default::default(), 470 Default::default(), 471 scan_response, 472 ); 473 474 test_command( 475 format!( 476 "netsim-cli beacon patch ble {} {} --scan-response-include-device-name --scan-response-include-tx-power-level --scan-response-manufacturer-data 59beac09", 477 device_name, chip_name 478 ) 479 .as_str(), 480 GrpcMethod::PatchDevice, 481 request, 482 ); 483 } 484 485 #[test] test_beacon_create_ble_tx_power()486 fn test_beacon_create_ble_tx_power() { 487 let device_name = String::from("device"); 488 let chip_name = String::from("chip"); 489 490 let settings = AdvertiseSettingsProto { 491 tx_power: Some(TxPowerProto::TxPowerLevel(AdvertiseTxPowerProto::HIGH.into())), 492 ..Default::default() 493 }; 494 let adv_data = AdvertiseDataProto { include_tx_power_level: true, ..Default::default() }; 495 496 let request = get_create_device_req_bytes( 497 &device_name, 498 &chip_name, 499 settings, 500 adv_data, 501 Default::default(), 502 ); 503 504 test_command( 505 format!( 506 "netsim-cli beacon create ble {} {} --tx-power-level high --include-tx-power-level", 507 device_name, chip_name 508 ) 509 .as_str(), 510 GrpcMethod::CreateDevice, 511 request, 512 ) 513 } 514 515 #[test] test_beacon_create_default()516 fn test_beacon_create_default() { 517 let request = get_create_device_req_bytes( 518 Default::default(), 519 Default::default(), 520 Default::default(), 521 Default::default(), 522 Default::default(), 523 ); 524 525 test_command("netsim-cli beacon create ble", GrpcMethod::CreateDevice, request) 526 } 527 528 #[test] test_beacon_patch_interval()529 fn test_beacon_patch_interval() { 530 let device_name = String::from("device"); 531 let chip_name = String::from("chip"); 532 533 let settings = AdvertiseSettingsProto { 534 interval: Some(IntervalProto::AdvertiseMode(AdvertiseModeProto::LOW_LATENCY.into())), 535 ..Default::default() 536 }; 537 538 let request = get_patch_device_req_bytes( 539 &device_name, 540 &chip_name, 541 settings, 542 Default::default(), 543 Default::default(), 544 ); 545 546 test_command( 547 format!( 548 "netsim-cli beacon patch ble {} {} --advertise-mode low-latency", 549 device_name, chip_name 550 ) 551 .as_str(), 552 GrpcMethod::PatchDevice, 553 request, 554 ) 555 } 556 557 #[test] test_beacon_create_ble_with_address()558 fn test_beacon_create_ble_with_address() { 559 let address = String::from("12:34:56:78:9a:bc"); 560 561 let device = MessageField::some(DeviceCreateProto { 562 chips: vec![ChipCreateProto { 563 kind: ChipKind::BLUETOOTH_BEACON.into(), 564 chip: Some(ChipKindCreateProto::BleBeacon(BleBeaconCreateProto { 565 address: address.clone(), 566 settings: MessageField::some(AdvertiseSettingsProto::default()), 567 adv_data: MessageField::some(AdvertiseDataProto::default()), 568 scan_response: MessageField::some(AdvertiseDataProto::default()), 569 ..Default::default() 570 })), 571 ..Default::default() 572 }], 573 ..Default::default() 574 }); 575 576 let request = frontend::CreateDeviceRequest { device, ..Default::default() } 577 .write_to_bytes() 578 .unwrap(); 579 580 test_command( 581 format!("netsim-cli beacon create ble --address {}", address).as_str(), 582 GrpcMethod::CreateDevice, 583 request, 584 ) 585 } 586 587 #[test] test_beacon_patch_ble_with_address()588 fn test_beacon_patch_ble_with_address() { 589 let address = String::from("12:34:56:78:9a:bc"); 590 let device_name = String::from("device"); 591 let chip_name = String::from("chip"); 592 593 let device = MessageField::some(PatchDeviceFieldsProto { 594 name: Some(device_name.clone()), 595 chips: vec![ChipProto { 596 name: chip_name.clone(), 597 kind: ChipKind::BLUETOOTH_BEACON.into(), 598 chip: Some(ChipKindProto::BleBeacon(BleBeaconProto { 599 bt: MessageField::some(Chip_Bluetooth::new()), 600 address: address.clone(), 601 settings: MessageField::some(AdvertiseSettingsProto::default()), 602 adv_data: MessageField::some(AdvertiseDataProto::default()), 603 scan_response: MessageField::some(AdvertiseDataProto::default()), 604 ..Default::default() 605 })), 606 ..Default::default() 607 }], 608 ..Default::default() 609 }); 610 611 let request = 612 frontend::PatchDeviceRequest { device, ..Default::default() }.write_to_bytes().unwrap(); 613 614 test_command( 615 format!( 616 "netsim-cli beacon patch ble {} {} --address {}", 617 device_name, chip_name, address 618 ) 619 .as_str(), 620 GrpcMethod::PatchDevice, 621 request, 622 ) 623 } 624 625 #[test] test_beacon_negative_timeout_fails()626 fn test_beacon_negative_timeout_fails() { 627 let command = String::from("netsim-cli beacon create ble --timeout -1234"); 628 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 629 } 630 631 #[test] test_create_beacon_large_tx_power_fails()632 fn test_create_beacon_large_tx_power_fails() { 633 let command = 634 format!("netsim-cli beacon create ble --tx-power-level {}", (i8::MAX as i32) + 1); 635 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 636 } 637 638 #[test] test_create_beacon_unknown_mode_fails()639 fn test_create_beacon_unknown_mode_fails() { 640 let command = String::from("netsim-cli beacon create ble --advertise-mode not-a-mode"); 641 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 642 } 643 644 #[test] test_patch_beacon_negative_timeout_fails()645 fn test_patch_beacon_negative_timeout_fails() { 646 let command = String::from("netsim-cli beacon patch ble --timeout -1234"); 647 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 648 } 649 650 #[test] test_patch_beacon_large_tx_power_fails()651 fn test_patch_beacon_large_tx_power_fails() { 652 let command = 653 format!("netsim-cli beacon patch ble --tx-power-level {}", (i8::MAX as i32) + 1); 654 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 655 } 656 657 #[test] test_patch_beacon_unknown_mode_fails()658 fn test_patch_beacon_unknown_mode_fails() { 659 let command = String::from("netsim-cli beacon patch ble --advertise-mode not-a-mode"); 660 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 661 } 662 663 #[test] test_create_beacon_mfg_data_fails()664 fn test_create_beacon_mfg_data_fails() { 665 let command = String::from("netsim-cli beacon create ble --manufacturer-data not-a-number"); 666 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 667 } 668 669 #[test] test_patch_beacon_mfg_data_fails()670 fn test_patch_beacon_mfg_data_fails() { 671 let command = String::from("netsim-cli beacon patch ble --manufacturer-data not-a-number"); 672 assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err()); 673 } 674 } 675