blob: c18aba3e4a2a3b00058bfdfe6194bf967586866c [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::cmp::max;
use crate::args::{self, Capture, Command, OnOffState};
use frontend_proto::{
common::ChipKind,
frontend::{GetDevicesResponse, ListCaptureResponse, VersionResponse},
model::{self, chip::Chip as Chip_oneof_chip, State},
};
use protobuf::Message;
impl args::Command {
/// Format and print the response received from the frontend server for the command
pub fn print_response(&self, response: &[u8], verbose: bool) {
match self {
Command::Version => {
Self::print_version_response(VersionResponse::parse_from_bytes(response).unwrap());
}
Command::Radio(cmd) => {
if verbose {
println!(
"Radio {} is {} for {}",
cmd.radio_type,
cmd.status,
cmd.name.to_owned()
);
}
}
Command::Move(cmd) => {
if verbose {
println!(
"Moved device:{} to x: {:.2}, y: {:.2}, z: {:.2}",
cmd.name,
cmd.x,
cmd.y,
cmd.z.unwrap_or_default()
)
}
}
Command::Devices(_) => {
Self::print_device_response(
GetDevicesResponse::parse_from_bytes(response).unwrap(),
verbose,
);
}
Command::Reset => {
if verbose {
println!("All devices have been reset.");
}
}
Command::Capture(Capture::List(cmd)) => Self::print_list_capture_response(
ListCaptureResponse::parse_from_bytes(response).unwrap(),
verbose,
cmd.patterns.to_owned(),
),
Command::Capture(Capture::Patch(cmd)) => {
if verbose {
println!(
"Patched Capture state to {}",
Self::on_off_state_to_string(cmd.state),
);
}
}
Command::Capture(Capture::Get(cmd)) => {
if verbose {
println!("Successfully downloaded file: {}", cmd.current_file);
}
}
Command::Gui => {
unimplemented!("No Grpc Response for Gui Command.");
}
}
}
/// Helper function to format and print GetDevicesResponse
fn print_device_response(response: GetDevicesResponse, verbose: bool) {
let pos_prec = 2;
let name_width = 16;
let state_width = 5;
let cnt_width = 9;
let chip_indent = 2;
let radio_width = 9;
if verbose {
if response.devices.is_empty() {
println!("No attached devices found.");
} else {
println!("List of attached devices:");
}
for device in response.devices {
let pos = device.position;
println!(
"{:name_width$} position: {:.pos_prec$}, {:.pos_prec$}, {:.pos_prec$}",
device.name, pos.x, pos.y, pos.z
);
for chip in &device.chips {
match &chip.chip {
Some(Chip_oneof_chip::Bt(bt)) => {
if bt.low_energy.is_some() {
let ble_chip = &bt.low_energy;
println!(
"{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?}",
"",
"ble:",
Self::chip_state_to_string(ble_chip.state.enum_value_or_default()),
ble_chip.rx_count,
ble_chip.tx_count,
);
}
if bt.classic.is_some() {
let classic_chip = &bt.classic;
println!(
"{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?}",
"",
"classic:",
Self::chip_state_to_string(classic_chip.state.enum_value_or_default()),
classic_chip.rx_count,
classic_chip.tx_count,
);
}
}
Some(Chip_oneof_chip::Wifi(wifi_chip)) => {
println!(
"{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?}",
"",
"wifi:",
Self::chip_state_to_string(wifi_chip.state.enum_value_or_default()),
wifi_chip.rx_count,
wifi_chip.tx_count,
);
}
Some(Chip_oneof_chip::Uwb(uwb_chip)) => {
println!(
"{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?}",
"",
"uwb:",
Self::chip_state_to_string(uwb_chip.state.enum_value_or_default()),
uwb_chip.rx_count,
uwb_chip.tx_count,
);
}
_ => println!("{:chip_indent$}Unknown chip: down ", ""),
}
}
}
} else {
for device in response.devices {
let pos = device.position;
print!("{:name_width$} ", device.name,);
if pos.x != 0.0 || pos.y != 0.0 || pos.z != 0.0 {
print!(
"position: {:.pos_prec$}, {:.pos_prec$}, {:.pos_prec$}",
pos.x, pos.y, pos.z
);
}
for chip in &device.chips {
match &chip.chip {
Some(Chip_oneof_chip::Bt(bt)) => {
if bt.low_energy.is_some() {
let ble_chip = &bt.low_energy;
if ble_chip.state.enum_value_or_default() == State::OFF {
print!(
"{:chip_indent$}{:radio_width$}{:state_width$}",
"",
"ble:",
Self::chip_state_to_string(
ble_chip.state.enum_value_or_default()
),
);
}
}
if bt.classic.is_some() {
let classic_chip = &bt.classic;
if classic_chip.state.enum_value_or_default() == State::OFF {
print!(
"{:chip_indent$}{:radio_width$}{:state_width$}",
"",
"classic:",
Self::chip_state_to_string(
classic_chip.state.enum_value_or_default()
)
);
}
}
}
Some(Chip_oneof_chip::Wifi(wifi_chip)) => {
if wifi_chip.state.enum_value_or_default() == State::OFF {
print!(
"{:chip_indent$}{:radio_width$}{:state_width$}",
"",
"wifi:",
Self::chip_state_to_string(
wifi_chip.state.enum_value_or_default()
)
);
}
}
Some(Chip_oneof_chip::Uwb(uwb_chip)) => {
if uwb_chip.state.enum_value_or_default() == State::OFF {
print!(
"{:chip_indent$}{:radio_width$}{:state_width$}",
"",
"uwb:",
Self::chip_state_to_string(
uwb_chip.state.enum_value_or_default()
)
);
}
}
_ => {}
}
}
println!();
}
}
}
/// Helper function to convert frontend_proto::model::State to string for output
fn chip_state_to_string(state: State) -> String {
match state {
State::ON => "up".to_string(),
State::OFF => "down".to_string(),
_ => "unknown".to_string(),
}
}
fn capture_state_to_string(state: State) -> String {
match state {
State::ON => "on".to_string(),
State::OFF => "off".to_string(),
_ => "unknown".to_string(),
}
}
fn on_off_state_to_string(state: OnOffState) -> String {
match state {
OnOffState::On => "on".to_string(),
OnOffState::Off => "off".to_string(),
}
}
/// Helper function to format and print VersionResponse
fn print_version_response(response: VersionResponse) {
println!("Netsim version: {}", response.version);
}
/// Helper function to format and print ListCaptureResponse
fn print_list_capture_response(
mut response: ListCaptureResponse,
verbose: bool,
patterns: Vec<String>,
) {
if response.captures.is_empty() {
if verbose {
println!("No available Capture found.");
}
return;
}
if patterns.is_empty() {
println!("List of Captures:");
} else {
// Filter out list of captures with matching patterns
Self::filter_captures(&mut response.captures, &patterns);
if response.captures.is_empty() {
if verbose {
println!("No available Capture found matching pattern(s) `{:?}`:", patterns);
}
return;
}
println!("List of Captures matching pattern(s) `{:?}`:", patterns);
}
// Create the header row and determine column widths
let id_hdr = "ID";
let name_hdr = "Device Name";
let chipkind_hdr = "Chip Kind";
let state_hdr = "State";
let size_hdr = "Size";
let id_width = 4; // ID width of 4 since capture id (=chip_id) starts at 1000
let state_width = 7; // State width of 7 for 'unknown'
let chipkind_width = 11; // ChipKind width 11 for 'UNSPECIFIED'
let name_width = max(
(response.captures.iter().max_by_key(|x| x.device_name.len()))
.unwrap_or_default()
.device_name
.len(),
name_hdr.len(),
);
let size_width = max(
(response.captures.iter().max_by_key(|x| x.size))
.unwrap_or_default()
.size
.to_string()
.len(),
size_hdr.len(),
);
// Print header for capture list
println!(
"{}",
if verbose {
format!("{:id_width$} | {:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
id_hdr,
name_hdr,
chipkind_hdr,
state_hdr,
size_hdr,
)
} else {
format!(
"{:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
name_hdr, chipkind_hdr, state_hdr, size_hdr,
)
}
);
// Print information of each Capture
for capture in &response.captures {
println!(
"{}",
if verbose {
format!("{:id_width$} | {:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
capture.id.to_string(),
capture.device_name,
Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()),
Self::capture_state_to_string(capture.state.enum_value_or_default()),
capture.size,
)
} else {
format!(
"{:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
capture.device_name,
Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()),
Self::capture_state_to_string(capture.state.enum_value_or_default()),
capture.size,
)
}
);
}
}
pub fn chip_kind_to_string(chip_kind: ChipKind) -> String {
match chip_kind {
ChipKind::UNSPECIFIED => "UNSPECIFIED".to_string(),
ChipKind::BLUETOOTH => "BLUETOOTH".to_string(),
ChipKind::WIFI => "WIFI".to_string(),
ChipKind::UWB => "UWB".to_string(),
}
}
pub fn filter_captures(captures: &mut Vec<model::Capture>, keys: &[String]) {
// Filter out list of captures with matching pattern
captures.retain(|capture| {
keys.iter().map(|key| key.to_uppercase()).all(|key| {
capture.id.to_string().contains(&key)
|| capture.device_name.to_uppercase().contains(&key)
|| Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default())
.contains(&key)
})
});
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_filter_captures_helper(patterns: Vec<String>, expected_captures: Vec<model::Capture>) {
let mut captures = all_test_captures();
Command::filter_captures(&mut captures, &patterns);
assert_eq!(captures, expected_captures);
}
fn capture_1() -> model::Capture {
model::Capture {
id: 4001,
chip_kind: ChipKind::BLUETOOTH.into(),
device_name: "device 1".to_string(),
..Default::default()
}
}
fn capture_1_wifi() -> model::Capture {
model::Capture {
id: 4002,
chip_kind: ChipKind::WIFI.into(),
device_name: "device 1".to_string(),
..Default::default()
}
}
fn capture_2() -> model::Capture {
model::Capture {
id: 4003,
chip_kind: ChipKind::BLUETOOTH.into(),
device_name: "device 2".to_string(),
..Default::default()
}
}
fn capture_3() -> model::Capture {
model::Capture {
id: 4004,
chip_kind: ChipKind::WIFI.into(),
device_name: "device 3".to_string(),
..Default::default()
}
}
fn capture_4_uwb() -> model::Capture {
model::Capture {
id: 4005,
chip_kind: ChipKind::UWB.into(),
device_name: "device 4".to_string(),
..Default::default()
}
}
fn all_test_captures() -> Vec<model::Capture> {
vec![capture_1(), capture_1_wifi(), capture_2(), capture_3(), capture_4_uwb()]
}
#[test]
fn test_no_match() {
test_filter_captures_helper(vec!["test".to_string()], vec![]);
}
#[test]
fn test_all_match() {
test_filter_captures_helper(vec!["device".to_string()], all_test_captures());
}
#[test]
fn test_match_capture_id() {
test_filter_captures_helper(vec!["4001".to_string()], vec![capture_1()]);
test_filter_captures_helper(vec!["03".to_string()], vec![capture_2()]);
test_filter_captures_helper(vec!["40".to_string()], all_test_captures());
}
#[test]
fn test_match_device_name() {
test_filter_captures_helper(
vec!["device 1".to_string()],
vec![capture_1(), capture_1_wifi()],
);
test_filter_captures_helper(vec![" 2".to_string()], vec![capture_2()]);
}
#[test]
fn test_match_device_name_case_insensitive() {
test_filter_captures_helper(
vec!["DEVICE 1".to_string()],
vec![capture_1(), capture_1_wifi()],
);
}
#[test]
fn test_match_wifi() {
test_filter_captures_helper(vec!["wifi".to_string()], vec![capture_1_wifi(), capture_3()]);
test_filter_captures_helper(vec!["WIFI".to_string()], vec![capture_1_wifi(), capture_3()]);
}
#[test]
fn test_match_uwb() {
test_filter_captures_helper(vec!["uwb".to_string()], vec![capture_4_uwb()]);
test_filter_captures_helper(vec!["UWB".to_string()], vec![capture_4_uwb()]);
}
#[test]
fn test_match_bt() {
test_filter_captures_helper(vec!["BLUETOOTH".to_string()], vec![capture_1(), capture_2()]);
test_filter_captures_helper(vec!["blue".to_string()], vec![capture_1(), capture_2()]);
}
#[test]
fn test_match_name_and_chip() {
test_filter_captures_helper(
vec!["device 1".to_string(), "wifi".to_string()],
vec![capture_1_wifi()],
);
}
}