1*cf78ab8cSAndroid Build Coastguard Worker // Copyright 2022 Google LLC 2*cf78ab8cSAndroid Build Coastguard Worker // 3*cf78ab8cSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); 4*cf78ab8cSAndroid Build Coastguard Worker // you may not use this file except in compliance with the License. 5*cf78ab8cSAndroid Build Coastguard Worker // You may obtain a copy of the License at 6*cf78ab8cSAndroid Build Coastguard Worker // 7*cf78ab8cSAndroid Build Coastguard Worker // https://www.apache.org/licenses/LICENSE-2.0 8*cf78ab8cSAndroid Build Coastguard Worker // 9*cf78ab8cSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software 10*cf78ab8cSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, 11*cf78ab8cSAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*cf78ab8cSAndroid Build Coastguard Worker // See the License for the specific language governing permissions and 13*cf78ab8cSAndroid Build Coastguard Worker // limitations under the License. 14*cf78ab8cSAndroid Build Coastguard Worker 15*cf78ab8cSAndroid Build Coastguard Worker use std::cmp::max; 16*cf78ab8cSAndroid Build Coastguard Worker 17*cf78ab8cSAndroid Build Coastguard Worker use crate::args::{self, Beacon, BeaconCreate, BeaconPatch, Capture, Command, OnOffState}; 18*cf78ab8cSAndroid Build Coastguard Worker use crate::display::Displayer; 19*cf78ab8cSAndroid Build Coastguard Worker use netsim_common::util::time_display::TimeDisplay; 20*cf78ab8cSAndroid Build Coastguard Worker use netsim_proto::{ 21*cf78ab8cSAndroid Build Coastguard Worker common::ChipKind, 22*cf78ab8cSAndroid Build Coastguard Worker frontend::{CreateDeviceResponse, ListCaptureResponse, ListDeviceResponse, VersionResponse}, 23*cf78ab8cSAndroid Build Coastguard Worker model, 24*cf78ab8cSAndroid Build Coastguard Worker }; 25*cf78ab8cSAndroid Build Coastguard Worker use protobuf::Message; 26*cf78ab8cSAndroid Build Coastguard Worker 27*cf78ab8cSAndroid Build Coastguard Worker impl args::Command { 28*cf78ab8cSAndroid Build Coastguard Worker /// Format and print the response received from the frontend server for the command print_response(&self, response: &[u8], verbose: bool)29*cf78ab8cSAndroid Build Coastguard Worker pub fn print_response(&self, response: &[u8], verbose: bool) { 30*cf78ab8cSAndroid Build Coastguard Worker match self { 31*cf78ab8cSAndroid Build Coastguard Worker Command::Version => { 32*cf78ab8cSAndroid Build Coastguard Worker Self::print_version_response(VersionResponse::parse_from_bytes(response).unwrap()); 33*cf78ab8cSAndroid Build Coastguard Worker } 34*cf78ab8cSAndroid Build Coastguard Worker Command::Radio(cmd) => { 35*cf78ab8cSAndroid Build Coastguard Worker if verbose { 36*cf78ab8cSAndroid Build Coastguard Worker println!( 37*cf78ab8cSAndroid Build Coastguard Worker "Radio {} is {} for {}", 38*cf78ab8cSAndroid Build Coastguard Worker cmd.radio_type, 39*cf78ab8cSAndroid Build Coastguard Worker cmd.status, 40*cf78ab8cSAndroid Build Coastguard Worker cmd.name.to_owned() 41*cf78ab8cSAndroid Build Coastguard Worker ); 42*cf78ab8cSAndroid Build Coastguard Worker } 43*cf78ab8cSAndroid Build Coastguard Worker } 44*cf78ab8cSAndroid Build Coastguard Worker Command::Move(cmd) => { 45*cf78ab8cSAndroid Build Coastguard Worker if verbose { 46*cf78ab8cSAndroid Build Coastguard Worker println!( 47*cf78ab8cSAndroid Build Coastguard Worker "Moved device:{} to x: {:.2}, y: {:.2}, z: {:.2}", 48*cf78ab8cSAndroid Build Coastguard Worker cmd.name, 49*cf78ab8cSAndroid Build Coastguard Worker cmd.x, 50*cf78ab8cSAndroid Build Coastguard Worker cmd.y, 51*cf78ab8cSAndroid Build Coastguard Worker cmd.z.unwrap_or_default() 52*cf78ab8cSAndroid Build Coastguard Worker ) 53*cf78ab8cSAndroid Build Coastguard Worker } 54*cf78ab8cSAndroid Build Coastguard Worker } 55*cf78ab8cSAndroid Build Coastguard Worker Command::Devices(_) => { 56*cf78ab8cSAndroid Build Coastguard Worker println!( 57*cf78ab8cSAndroid Build Coastguard Worker "{}", 58*cf78ab8cSAndroid Build Coastguard Worker Displayer::new( 59*cf78ab8cSAndroid Build Coastguard Worker ListDeviceResponse::parse_from_bytes(response).unwrap(), 60*cf78ab8cSAndroid Build Coastguard Worker verbose 61*cf78ab8cSAndroid Build Coastguard Worker ) 62*cf78ab8cSAndroid Build Coastguard Worker ); 63*cf78ab8cSAndroid Build Coastguard Worker } 64*cf78ab8cSAndroid Build Coastguard Worker Command::Reset => { 65*cf78ab8cSAndroid Build Coastguard Worker if verbose { 66*cf78ab8cSAndroid Build Coastguard Worker println!("All devices have been reset."); 67*cf78ab8cSAndroid Build Coastguard Worker } 68*cf78ab8cSAndroid Build Coastguard Worker } 69*cf78ab8cSAndroid Build Coastguard Worker Command::Capture(Capture::List(cmd)) => Self::print_list_capture_response( 70*cf78ab8cSAndroid Build Coastguard Worker ListCaptureResponse::parse_from_bytes(response).unwrap(), 71*cf78ab8cSAndroid Build Coastguard Worker verbose, 72*cf78ab8cSAndroid Build Coastguard Worker cmd.patterns.to_owned(), 73*cf78ab8cSAndroid Build Coastguard Worker ), 74*cf78ab8cSAndroid Build Coastguard Worker Command::Capture(Capture::Patch(cmd)) => { 75*cf78ab8cSAndroid Build Coastguard Worker if verbose { 76*cf78ab8cSAndroid Build Coastguard Worker println!( 77*cf78ab8cSAndroid Build Coastguard Worker "Patched Capture state to {}", 78*cf78ab8cSAndroid Build Coastguard Worker Self::on_off_state_to_string(cmd.state), 79*cf78ab8cSAndroid Build Coastguard Worker ); 80*cf78ab8cSAndroid Build Coastguard Worker } 81*cf78ab8cSAndroid Build Coastguard Worker } 82*cf78ab8cSAndroid Build Coastguard Worker Command::Capture(Capture::Get(cmd)) => { 83*cf78ab8cSAndroid Build Coastguard Worker if verbose { 84*cf78ab8cSAndroid Build Coastguard Worker println!("Successfully downloaded file: {}", cmd.current_file); 85*cf78ab8cSAndroid Build Coastguard Worker } 86*cf78ab8cSAndroid Build Coastguard Worker } 87*cf78ab8cSAndroid Build Coastguard Worker Command::Gui => { 88*cf78ab8cSAndroid Build Coastguard Worker unimplemented!("No Grpc Response for Gui Command."); 89*cf78ab8cSAndroid Build Coastguard Worker } 90*cf78ab8cSAndroid Build Coastguard Worker Command::Artifact => { 91*cf78ab8cSAndroid Build Coastguard Worker unimplemented!("No Grpc Response for Artifact Command."); 92*cf78ab8cSAndroid Build Coastguard Worker } 93*cf78ab8cSAndroid Build Coastguard Worker Command::Beacon(action) => match action { 94*cf78ab8cSAndroid Build Coastguard Worker Beacon::Create(kind) => match kind { 95*cf78ab8cSAndroid Build Coastguard Worker BeaconCreate::Ble(_) => { 96*cf78ab8cSAndroid Build Coastguard Worker if !verbose { 97*cf78ab8cSAndroid Build Coastguard Worker return; 98*cf78ab8cSAndroid Build Coastguard Worker } 99*cf78ab8cSAndroid Build Coastguard Worker let device = CreateDeviceResponse::parse_from_bytes(response) 100*cf78ab8cSAndroid Build Coastguard Worker .expect("could not read device from response") 101*cf78ab8cSAndroid Build Coastguard Worker .device; 102*cf78ab8cSAndroid Build Coastguard Worker 103*cf78ab8cSAndroid Build Coastguard Worker if device.chips.len() == 1 { 104*cf78ab8cSAndroid Build Coastguard Worker println!( 105*cf78ab8cSAndroid Build Coastguard Worker "Created device '{}' with ble beacon chip '{}'", 106*cf78ab8cSAndroid Build Coastguard Worker device.name, device.chips[0].name 107*cf78ab8cSAndroid Build Coastguard Worker ); 108*cf78ab8cSAndroid Build Coastguard Worker } else { 109*cf78ab8cSAndroid Build Coastguard Worker panic!("the gRPC request completed successfully but the response contained an unexpected number of chips"); 110*cf78ab8cSAndroid Build Coastguard Worker } 111*cf78ab8cSAndroid Build Coastguard Worker } 112*cf78ab8cSAndroid Build Coastguard Worker }, 113*cf78ab8cSAndroid Build Coastguard Worker Beacon::Patch(kind) => { 114*cf78ab8cSAndroid Build Coastguard Worker match kind { 115*cf78ab8cSAndroid Build Coastguard Worker BeaconPatch::Ble(args) => { 116*cf78ab8cSAndroid Build Coastguard Worker if !verbose { 117*cf78ab8cSAndroid Build Coastguard Worker return; 118*cf78ab8cSAndroid Build Coastguard Worker } 119*cf78ab8cSAndroid Build Coastguard Worker if let Some(advertise_mode) = &args.settings.advertise_mode { 120*cf78ab8cSAndroid Build Coastguard Worker match advertise_mode { 121*cf78ab8cSAndroid Build Coastguard Worker args::Interval::Mode(mode) => { 122*cf78ab8cSAndroid Build Coastguard Worker println!("Set advertise mode to {:#?}", mode) 123*cf78ab8cSAndroid Build Coastguard Worker } 124*cf78ab8cSAndroid Build Coastguard Worker args::Interval::Milliseconds(ms) => { 125*cf78ab8cSAndroid Build Coastguard Worker println!("Set advertise interval to {} ms", ms) 126*cf78ab8cSAndroid Build Coastguard Worker } 127*cf78ab8cSAndroid Build Coastguard Worker } 128*cf78ab8cSAndroid Build Coastguard Worker } 129*cf78ab8cSAndroid Build Coastguard Worker if let Some(tx_power_level) = &args.settings.tx_power_level { 130*cf78ab8cSAndroid Build Coastguard Worker match tx_power_level { 131*cf78ab8cSAndroid Build Coastguard Worker args::TxPower::Level(level) => { 132*cf78ab8cSAndroid Build Coastguard Worker println!("Set transmit power level to {:#?}", level) 133*cf78ab8cSAndroid Build Coastguard Worker } 134*cf78ab8cSAndroid Build Coastguard Worker args::TxPower::Dbm(dbm) => { 135*cf78ab8cSAndroid Build Coastguard Worker println!("Set transmit power level to {} dBm", dbm) 136*cf78ab8cSAndroid Build Coastguard Worker } 137*cf78ab8cSAndroid Build Coastguard Worker } 138*cf78ab8cSAndroid Build Coastguard Worker } 139*cf78ab8cSAndroid Build Coastguard Worker if args.settings.scannable { 140*cf78ab8cSAndroid Build Coastguard Worker println!("Set scannable to true"); 141*cf78ab8cSAndroid Build Coastguard Worker } 142*cf78ab8cSAndroid Build Coastguard Worker if let Some(timeout) = args.settings.timeout { 143*cf78ab8cSAndroid Build Coastguard Worker println!("Set timeout to {} ms", timeout); 144*cf78ab8cSAndroid Build Coastguard Worker } 145*cf78ab8cSAndroid Build Coastguard Worker if args.advertise_data.include_device_name { 146*cf78ab8cSAndroid Build Coastguard Worker println!("Added the device's name to the advertise packet") 147*cf78ab8cSAndroid Build Coastguard Worker } 148*cf78ab8cSAndroid Build Coastguard Worker if args.advertise_data.include_tx_power_level { 149*cf78ab8cSAndroid Build Coastguard Worker println!("Added the beacon's transmit power level to the advertise packet") 150*cf78ab8cSAndroid Build Coastguard Worker } 151*cf78ab8cSAndroid Build Coastguard Worker if args.advertise_data.manufacturer_data.is_some() { 152*cf78ab8cSAndroid Build Coastguard Worker println!("Added manufacturer data to the advertise packet") 153*cf78ab8cSAndroid Build Coastguard Worker } 154*cf78ab8cSAndroid Build Coastguard Worker if args.settings.scannable { 155*cf78ab8cSAndroid Build Coastguard Worker println!("Set scannable to true"); 156*cf78ab8cSAndroid Build Coastguard Worker } 157*cf78ab8cSAndroid Build Coastguard Worker if let Some(timeout) = args.settings.timeout { 158*cf78ab8cSAndroid Build Coastguard Worker println!("Set timeout to {} ms", timeout); 159*cf78ab8cSAndroid Build Coastguard Worker } 160*cf78ab8cSAndroid Build Coastguard Worker } 161*cf78ab8cSAndroid Build Coastguard Worker } 162*cf78ab8cSAndroid Build Coastguard Worker } 163*cf78ab8cSAndroid Build Coastguard Worker Beacon::Remove(args) => { 164*cf78ab8cSAndroid Build Coastguard Worker if !verbose { 165*cf78ab8cSAndroid Build Coastguard Worker return; 166*cf78ab8cSAndroid Build Coastguard Worker } 167*cf78ab8cSAndroid Build Coastguard Worker if let Some(chip_name) = &args.chip_name { 168*cf78ab8cSAndroid Build Coastguard Worker println!("Removed chip '{}' from device '{}'", chip_name, args.device_name) 169*cf78ab8cSAndroid Build Coastguard Worker } else { 170*cf78ab8cSAndroid Build Coastguard Worker println!("Removed device '{}'", args.device_name) 171*cf78ab8cSAndroid Build Coastguard Worker } 172*cf78ab8cSAndroid Build Coastguard Worker } 173*cf78ab8cSAndroid Build Coastguard Worker }, 174*cf78ab8cSAndroid Build Coastguard Worker Command::Bumble => { 175*cf78ab8cSAndroid Build Coastguard Worker unimplemented!("No Grpc Response for Bumble Command."); 176*cf78ab8cSAndroid Build Coastguard Worker } 177*cf78ab8cSAndroid Build Coastguard Worker } 178*cf78ab8cSAndroid Build Coastguard Worker } 179*cf78ab8cSAndroid Build Coastguard Worker capture_state_to_string(state: Option<bool>) -> String180*cf78ab8cSAndroid Build Coastguard Worker fn capture_state_to_string(state: Option<bool>) -> String { 181*cf78ab8cSAndroid Build Coastguard Worker state.map(|value| if value { "on" } else { "off" }).unwrap_or("unknown").to_string() 182*cf78ab8cSAndroid Build Coastguard Worker } 183*cf78ab8cSAndroid Build Coastguard Worker on_off_state_to_string(state: OnOffState) -> String184*cf78ab8cSAndroid Build Coastguard Worker fn on_off_state_to_string(state: OnOffState) -> String { 185*cf78ab8cSAndroid Build Coastguard Worker match state { 186*cf78ab8cSAndroid Build Coastguard Worker OnOffState::On => "on".to_string(), 187*cf78ab8cSAndroid Build Coastguard Worker OnOffState::Off => "off".to_string(), 188*cf78ab8cSAndroid Build Coastguard Worker } 189*cf78ab8cSAndroid Build Coastguard Worker } 190*cf78ab8cSAndroid Build Coastguard Worker 191*cf78ab8cSAndroid Build Coastguard Worker /// Helper function to format and print VersionResponse print_version_response(response: VersionResponse)192*cf78ab8cSAndroid Build Coastguard Worker fn print_version_response(response: VersionResponse) { 193*cf78ab8cSAndroid Build Coastguard Worker println!("Netsim version: {}", response.version); 194*cf78ab8cSAndroid Build Coastguard Worker } 195*cf78ab8cSAndroid Build Coastguard Worker 196*cf78ab8cSAndroid Build Coastguard Worker /// Helper function to format and print ListCaptureResponse print_list_capture_response( mut response: ListCaptureResponse, verbose: bool, patterns: Vec<String>, )197*cf78ab8cSAndroid Build Coastguard Worker fn print_list_capture_response( 198*cf78ab8cSAndroid Build Coastguard Worker mut response: ListCaptureResponse, 199*cf78ab8cSAndroid Build Coastguard Worker verbose: bool, 200*cf78ab8cSAndroid Build Coastguard Worker patterns: Vec<String>, 201*cf78ab8cSAndroid Build Coastguard Worker ) { 202*cf78ab8cSAndroid Build Coastguard Worker if response.captures.is_empty() { 203*cf78ab8cSAndroid Build Coastguard Worker if verbose { 204*cf78ab8cSAndroid Build Coastguard Worker println!("No available Capture found."); 205*cf78ab8cSAndroid Build Coastguard Worker } 206*cf78ab8cSAndroid Build Coastguard Worker return; 207*cf78ab8cSAndroid Build Coastguard Worker } 208*cf78ab8cSAndroid Build Coastguard Worker if patterns.is_empty() { 209*cf78ab8cSAndroid Build Coastguard Worker println!("List of Captures:"); 210*cf78ab8cSAndroid Build Coastguard Worker } else { 211*cf78ab8cSAndroid Build Coastguard Worker // Filter out list of captures with matching patterns 212*cf78ab8cSAndroid Build Coastguard Worker Self::filter_captures(&mut response.captures, &patterns); 213*cf78ab8cSAndroid Build Coastguard Worker if response.captures.is_empty() { 214*cf78ab8cSAndroid Build Coastguard Worker if verbose { 215*cf78ab8cSAndroid Build Coastguard Worker println!("No available Capture found matching pattern(s) `{:?}`:", patterns); 216*cf78ab8cSAndroid Build Coastguard Worker } 217*cf78ab8cSAndroid Build Coastguard Worker return; 218*cf78ab8cSAndroid Build Coastguard Worker } 219*cf78ab8cSAndroid Build Coastguard Worker println!("List of Captures matching pattern(s) `{:?}`:", patterns); 220*cf78ab8cSAndroid Build Coastguard Worker } 221*cf78ab8cSAndroid Build Coastguard Worker // Create the header row and determine column widths 222*cf78ab8cSAndroid Build Coastguard Worker let id_hdr = "ID"; 223*cf78ab8cSAndroid Build Coastguard Worker let name_hdr = "Device Name"; 224*cf78ab8cSAndroid Build Coastguard Worker let chipkind_hdr = "Chip Kind"; 225*cf78ab8cSAndroid Build Coastguard Worker let state_hdr = "State"; 226*cf78ab8cSAndroid Build Coastguard Worker let time_hdr = "Timestamp"; 227*cf78ab8cSAndroid Build Coastguard Worker let records_hdr = "Records"; 228*cf78ab8cSAndroid Build Coastguard Worker let size_hdr = "Size (bytes)"; 229*cf78ab8cSAndroid Build Coastguard Worker let id_width = 4; // ID width of 4 since capture id (=chip_id) starts at 1000 230*cf78ab8cSAndroid Build Coastguard Worker let state_width = 8; // State width of 8 for 'detached' if device is disconnected 231*cf78ab8cSAndroid Build Coastguard Worker let chipkind_width = 11; // ChipKind width 11 for 'UNSPECIFIED' 232*cf78ab8cSAndroid Build Coastguard Worker let time_width = 9; // Timestamp width 9 for header (value format set to HH:MM:SS) 233*cf78ab8cSAndroid Build Coastguard Worker let name_width = max( 234*cf78ab8cSAndroid Build Coastguard Worker (response.captures.iter().max_by_key(|x| x.device_name.len())) 235*cf78ab8cSAndroid Build Coastguard Worker .unwrap_or_default() 236*cf78ab8cSAndroid Build Coastguard Worker .device_name 237*cf78ab8cSAndroid Build Coastguard Worker .len(), 238*cf78ab8cSAndroid Build Coastguard Worker name_hdr.len(), 239*cf78ab8cSAndroid Build Coastguard Worker ); 240*cf78ab8cSAndroid Build Coastguard Worker let records_width = max( 241*cf78ab8cSAndroid Build Coastguard Worker (response.captures.iter().max_by_key(|x| x.records)) 242*cf78ab8cSAndroid Build Coastguard Worker .unwrap_or_default() 243*cf78ab8cSAndroid Build Coastguard Worker .records 244*cf78ab8cSAndroid Build Coastguard Worker .to_string() 245*cf78ab8cSAndroid Build Coastguard Worker .len(), 246*cf78ab8cSAndroid Build Coastguard Worker records_hdr.len(), 247*cf78ab8cSAndroid Build Coastguard Worker ); 248*cf78ab8cSAndroid Build Coastguard Worker let size_width = max( 249*cf78ab8cSAndroid Build Coastguard Worker (response.captures.iter().max_by_key(|x| x.size)) 250*cf78ab8cSAndroid Build Coastguard Worker .unwrap_or_default() 251*cf78ab8cSAndroid Build Coastguard Worker .size 252*cf78ab8cSAndroid Build Coastguard Worker .to_string() 253*cf78ab8cSAndroid Build Coastguard Worker .len(), 254*cf78ab8cSAndroid Build Coastguard Worker size_hdr.len(), 255*cf78ab8cSAndroid Build Coastguard Worker ); 256*cf78ab8cSAndroid Build Coastguard Worker // Print header for capture list 257*cf78ab8cSAndroid Build Coastguard Worker println!( 258*cf78ab8cSAndroid Build Coastguard Worker "{}", 259*cf78ab8cSAndroid Build Coastguard Worker if verbose { 260*cf78ab8cSAndroid Build Coastguard Worker format!("{:id_width$} | {:name_width$} | {:chipkind_width$} | {:state_width$} | {:time_width$} | {:records_width$} | {:size_width$} |", 261*cf78ab8cSAndroid Build Coastguard Worker id_hdr, 262*cf78ab8cSAndroid Build Coastguard Worker name_hdr, 263*cf78ab8cSAndroid Build Coastguard Worker chipkind_hdr, 264*cf78ab8cSAndroid Build Coastguard Worker state_hdr, 265*cf78ab8cSAndroid Build Coastguard Worker time_hdr, 266*cf78ab8cSAndroid Build Coastguard Worker records_hdr, 267*cf78ab8cSAndroid Build Coastguard Worker size_hdr, 268*cf78ab8cSAndroid Build Coastguard Worker ) 269*cf78ab8cSAndroid Build Coastguard Worker } else { 270*cf78ab8cSAndroid Build Coastguard Worker format!( 271*cf78ab8cSAndroid Build Coastguard Worker "{:name_width$} | {:chipkind_width$} | {:state_width$} | {:records_width$} |", 272*cf78ab8cSAndroid Build Coastguard Worker name_hdr, chipkind_hdr, state_hdr, records_hdr 273*cf78ab8cSAndroid Build Coastguard Worker ) 274*cf78ab8cSAndroid Build Coastguard Worker } 275*cf78ab8cSAndroid Build Coastguard Worker ); 276*cf78ab8cSAndroid Build Coastguard Worker // Print information of each Capture 277*cf78ab8cSAndroid Build Coastguard Worker for capture in &response.captures { 278*cf78ab8cSAndroid Build Coastguard Worker println!( 279*cf78ab8cSAndroid Build Coastguard Worker "{}", 280*cf78ab8cSAndroid Build Coastguard Worker if verbose { 281*cf78ab8cSAndroid Build Coastguard Worker format!("{:id_width$} | {:name_width$} | {:chipkind_width$} | {:state_width$} | {:time_width$} | {:records_width$} | {:size_width$} |", 282*cf78ab8cSAndroid Build Coastguard Worker capture.id.to_string(), 283*cf78ab8cSAndroid Build Coastguard Worker capture.device_name, 284*cf78ab8cSAndroid Build Coastguard Worker Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()), 285*cf78ab8cSAndroid Build Coastguard Worker if capture.valid {Self::capture_state_to_string(capture.state)} else {"detached".to_string()}, 286*cf78ab8cSAndroid Build Coastguard Worker TimeDisplay::new( 287*cf78ab8cSAndroid Build Coastguard Worker capture.timestamp.get_or_default().seconds, 288*cf78ab8cSAndroid Build Coastguard Worker capture.timestamp.get_or_default().nanos as u32, 289*cf78ab8cSAndroid Build Coastguard Worker ).utc_display_hms(), 290*cf78ab8cSAndroid Build Coastguard Worker capture.records, 291*cf78ab8cSAndroid Build Coastguard Worker capture.size, 292*cf78ab8cSAndroid Build Coastguard Worker ) 293*cf78ab8cSAndroid Build Coastguard Worker } else { 294*cf78ab8cSAndroid Build Coastguard Worker format!( 295*cf78ab8cSAndroid Build Coastguard Worker "{:name_width$} | {:chipkind_width$} | {:state_width$} | {:records_width$} |", 296*cf78ab8cSAndroid Build Coastguard Worker capture.device_name, 297*cf78ab8cSAndroid Build Coastguard Worker Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()), 298*cf78ab8cSAndroid Build Coastguard Worker if capture.valid {Self::capture_state_to_string(capture.state)} else {"detached".to_string()}, 299*cf78ab8cSAndroid Build Coastguard Worker capture.records, 300*cf78ab8cSAndroid Build Coastguard Worker ) 301*cf78ab8cSAndroid Build Coastguard Worker } 302*cf78ab8cSAndroid Build Coastguard Worker ); 303*cf78ab8cSAndroid Build Coastguard Worker } 304*cf78ab8cSAndroid Build Coastguard Worker } 305*cf78ab8cSAndroid Build Coastguard Worker chip_kind_to_string(chip_kind: ChipKind) -> String306*cf78ab8cSAndroid Build Coastguard Worker pub fn chip_kind_to_string(chip_kind: ChipKind) -> String { 307*cf78ab8cSAndroid Build Coastguard Worker match chip_kind { 308*cf78ab8cSAndroid Build Coastguard Worker ChipKind::UNSPECIFIED => "UNSPECIFIED".to_string(), 309*cf78ab8cSAndroid Build Coastguard Worker ChipKind::BLUETOOTH => "BLUETOOTH".to_string(), 310*cf78ab8cSAndroid Build Coastguard Worker ChipKind::WIFI => "WIFI".to_string(), 311*cf78ab8cSAndroid Build Coastguard Worker ChipKind::UWB => "UWB".to_string(), 312*cf78ab8cSAndroid Build Coastguard Worker ChipKind::BLUETOOTH_BEACON => "BLUETOOTH_BEACON".to_string(), 313*cf78ab8cSAndroid Build Coastguard Worker } 314*cf78ab8cSAndroid Build Coastguard Worker } 315*cf78ab8cSAndroid Build Coastguard Worker filter_captures(captures: &mut Vec<model::Capture>, keys: &[String])316*cf78ab8cSAndroid Build Coastguard Worker pub fn filter_captures(captures: &mut Vec<model::Capture>, keys: &[String]) { 317*cf78ab8cSAndroid Build Coastguard Worker // Filter out list of captures with matching pattern 318*cf78ab8cSAndroid Build Coastguard Worker captures.retain(|capture| { 319*cf78ab8cSAndroid Build Coastguard Worker keys.iter().map(|key| key.to_uppercase()).all(|key| { 320*cf78ab8cSAndroid Build Coastguard Worker capture.id.to_string().contains(&key) 321*cf78ab8cSAndroid Build Coastguard Worker || capture.device_name.to_uppercase().contains(&key) 322*cf78ab8cSAndroid Build Coastguard Worker || Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()) 323*cf78ab8cSAndroid Build Coastguard Worker .contains(&key) 324*cf78ab8cSAndroid Build Coastguard Worker }) 325*cf78ab8cSAndroid Build Coastguard Worker }); 326*cf78ab8cSAndroid Build Coastguard Worker } 327*cf78ab8cSAndroid Build Coastguard Worker } 328*cf78ab8cSAndroid Build Coastguard Worker 329*cf78ab8cSAndroid Build Coastguard Worker #[cfg(test)] 330*cf78ab8cSAndroid Build Coastguard Worker mod tests { 331*cf78ab8cSAndroid Build Coastguard Worker use super::*; test_filter_captures_helper(patterns: Vec<String>, expected_captures: Vec<model::Capture>)332*cf78ab8cSAndroid Build Coastguard Worker fn test_filter_captures_helper(patterns: Vec<String>, expected_captures: Vec<model::Capture>) { 333*cf78ab8cSAndroid Build Coastguard Worker let mut captures = all_test_captures(); 334*cf78ab8cSAndroid Build Coastguard Worker Command::filter_captures(&mut captures, &patterns); 335*cf78ab8cSAndroid Build Coastguard Worker assert_eq!(captures, expected_captures); 336*cf78ab8cSAndroid Build Coastguard Worker } 337*cf78ab8cSAndroid Build Coastguard Worker capture_1() -> model::Capture338*cf78ab8cSAndroid Build Coastguard Worker fn capture_1() -> model::Capture { 339*cf78ab8cSAndroid Build Coastguard Worker model::Capture { 340*cf78ab8cSAndroid Build Coastguard Worker id: 4001, 341*cf78ab8cSAndroid Build Coastguard Worker chip_kind: ChipKind::BLUETOOTH.into(), 342*cf78ab8cSAndroid Build Coastguard Worker device_name: "device 1".to_string(), 343*cf78ab8cSAndroid Build Coastguard Worker ..Default::default() 344*cf78ab8cSAndroid Build Coastguard Worker } 345*cf78ab8cSAndroid Build Coastguard Worker } capture_1_wifi() -> model::Capture346*cf78ab8cSAndroid Build Coastguard Worker fn capture_1_wifi() -> model::Capture { 347*cf78ab8cSAndroid Build Coastguard Worker model::Capture { 348*cf78ab8cSAndroid Build Coastguard Worker id: 4002, 349*cf78ab8cSAndroid Build Coastguard Worker chip_kind: ChipKind::WIFI.into(), 350*cf78ab8cSAndroid Build Coastguard Worker device_name: "device 1".to_string(), 351*cf78ab8cSAndroid Build Coastguard Worker ..Default::default() 352*cf78ab8cSAndroid Build Coastguard Worker } 353*cf78ab8cSAndroid Build Coastguard Worker } capture_2() -> model::Capture354*cf78ab8cSAndroid Build Coastguard Worker fn capture_2() -> model::Capture { 355*cf78ab8cSAndroid Build Coastguard Worker model::Capture { 356*cf78ab8cSAndroid Build Coastguard Worker id: 4003, 357*cf78ab8cSAndroid Build Coastguard Worker chip_kind: ChipKind::BLUETOOTH.into(), 358*cf78ab8cSAndroid Build Coastguard Worker device_name: "device 2".to_string(), 359*cf78ab8cSAndroid Build Coastguard Worker ..Default::default() 360*cf78ab8cSAndroid Build Coastguard Worker } 361*cf78ab8cSAndroid Build Coastguard Worker } capture_3() -> model::Capture362*cf78ab8cSAndroid Build Coastguard Worker fn capture_3() -> model::Capture { 363*cf78ab8cSAndroid Build Coastguard Worker model::Capture { 364*cf78ab8cSAndroid Build Coastguard Worker id: 4004, 365*cf78ab8cSAndroid Build Coastguard Worker chip_kind: ChipKind::WIFI.into(), 366*cf78ab8cSAndroid Build Coastguard Worker device_name: "device 3".to_string(), 367*cf78ab8cSAndroid Build Coastguard Worker ..Default::default() 368*cf78ab8cSAndroid Build Coastguard Worker } 369*cf78ab8cSAndroid Build Coastguard Worker } capture_4_uwb() -> model::Capture370*cf78ab8cSAndroid Build Coastguard Worker fn capture_4_uwb() -> model::Capture { 371*cf78ab8cSAndroid Build Coastguard Worker model::Capture { 372*cf78ab8cSAndroid Build Coastguard Worker id: 4005, 373*cf78ab8cSAndroid Build Coastguard Worker chip_kind: ChipKind::UWB.into(), 374*cf78ab8cSAndroid Build Coastguard Worker device_name: "device 4".to_string(), 375*cf78ab8cSAndroid Build Coastguard Worker ..Default::default() 376*cf78ab8cSAndroid Build Coastguard Worker } 377*cf78ab8cSAndroid Build Coastguard Worker } all_test_captures() -> Vec<model::Capture>378*cf78ab8cSAndroid Build Coastguard Worker fn all_test_captures() -> Vec<model::Capture> { 379*cf78ab8cSAndroid Build Coastguard Worker vec![capture_1(), capture_1_wifi(), capture_2(), capture_3(), capture_4_uwb()] 380*cf78ab8cSAndroid Build Coastguard Worker } 381*cf78ab8cSAndroid Build Coastguard Worker 382*cf78ab8cSAndroid Build Coastguard Worker #[test] test_no_match()383*cf78ab8cSAndroid Build Coastguard Worker fn test_no_match() { 384*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["test".to_string()], vec![]); 385*cf78ab8cSAndroid Build Coastguard Worker } 386*cf78ab8cSAndroid Build Coastguard Worker 387*cf78ab8cSAndroid Build Coastguard Worker #[test] test_all_match()388*cf78ab8cSAndroid Build Coastguard Worker fn test_all_match() { 389*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["device".to_string()], all_test_captures()); 390*cf78ab8cSAndroid Build Coastguard Worker } 391*cf78ab8cSAndroid Build Coastguard Worker 392*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_capture_id()393*cf78ab8cSAndroid Build Coastguard Worker fn test_match_capture_id() { 394*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["4001".to_string()], vec![capture_1()]); 395*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["03".to_string()], vec![capture_2()]); 396*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["40".to_string()], all_test_captures()); 397*cf78ab8cSAndroid Build Coastguard Worker } 398*cf78ab8cSAndroid Build Coastguard Worker 399*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_device_name()400*cf78ab8cSAndroid Build Coastguard Worker fn test_match_device_name() { 401*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper( 402*cf78ab8cSAndroid Build Coastguard Worker vec!["device 1".to_string()], 403*cf78ab8cSAndroid Build Coastguard Worker vec![capture_1(), capture_1_wifi()], 404*cf78ab8cSAndroid Build Coastguard Worker ); 405*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec![" 2".to_string()], vec![capture_2()]); 406*cf78ab8cSAndroid Build Coastguard Worker } 407*cf78ab8cSAndroid Build Coastguard Worker 408*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_device_name_case_insensitive()409*cf78ab8cSAndroid Build Coastguard Worker fn test_match_device_name_case_insensitive() { 410*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper( 411*cf78ab8cSAndroid Build Coastguard Worker vec!["DEVICE 1".to_string()], 412*cf78ab8cSAndroid Build Coastguard Worker vec![capture_1(), capture_1_wifi()], 413*cf78ab8cSAndroid Build Coastguard Worker ); 414*cf78ab8cSAndroid Build Coastguard Worker } 415*cf78ab8cSAndroid Build Coastguard Worker 416*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_wifi()417*cf78ab8cSAndroid Build Coastguard Worker fn test_match_wifi() { 418*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["wifi".to_string()], vec![capture_1_wifi(), capture_3()]); 419*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["WIFI".to_string()], vec![capture_1_wifi(), capture_3()]); 420*cf78ab8cSAndroid Build Coastguard Worker } 421*cf78ab8cSAndroid Build Coastguard Worker 422*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_uwb()423*cf78ab8cSAndroid Build Coastguard Worker fn test_match_uwb() { 424*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["uwb".to_string()], vec![capture_4_uwb()]); 425*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["UWB".to_string()], vec![capture_4_uwb()]); 426*cf78ab8cSAndroid Build Coastguard Worker } 427*cf78ab8cSAndroid Build Coastguard Worker 428*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_bt()429*cf78ab8cSAndroid Build Coastguard Worker fn test_match_bt() { 430*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["BLUETOOTH".to_string()], vec![capture_1(), capture_2()]); 431*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper(vec!["blue".to_string()], vec![capture_1(), capture_2()]); 432*cf78ab8cSAndroid Build Coastguard Worker } 433*cf78ab8cSAndroid Build Coastguard Worker 434*cf78ab8cSAndroid Build Coastguard Worker #[test] test_match_name_and_chip()435*cf78ab8cSAndroid Build Coastguard Worker fn test_match_name_and_chip() { 436*cf78ab8cSAndroid Build Coastguard Worker test_filter_captures_helper( 437*cf78ab8cSAndroid Build Coastguard Worker vec!["device 1".to_string(), "wifi".to_string()], 438*cf78ab8cSAndroid Build Coastguard Worker vec![capture_1_wifi()], 439*cf78ab8cSAndroid Build Coastguard Worker ); 440*cf78ab8cSAndroid Build Coastguard Worker } 441*cf78ab8cSAndroid Build Coastguard Worker } 442