• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 std::cmp::max;
16 
17 use crate::args::{self, Command, OnOffState, Pcap};
18 use frontend_proto::{
19     common::ChipKind,
20     frontend::{GetDevicesResponse, ListCaptureResponse, VersionResponse},
21     model::{self, chip::Chip as Chip_oneof_chip, State},
22 };
23 use protobuf::Message;
24 
25 impl args::Command {
26     /// Format and print the response received from the frontend server for the command
print_response(&self, response: &[u8], verbose: bool)27     pub fn print_response(&self, response: &[u8], verbose: bool) {
28         match self {
29             Command::Version => {
30                 Self::print_version_response(VersionResponse::parse_from_bytes(response).unwrap());
31             }
32             Command::Radio(cmd) => {
33                 if verbose {
34                     println!(
35                         "Radio {} is {} for {}",
36                         cmd.radio_type,
37                         cmd.status,
38                         cmd.name.to_owned()
39                     );
40                 }
41             }
42             Command::Move(cmd) => {
43                 if verbose {
44                     println!(
45                         "Moved device:{} to x: {:.2}, y: {:.2}, z: {:.2}",
46                         cmd.name,
47                         cmd.x,
48                         cmd.y,
49                         cmd.z.unwrap_or_default()
50                     )
51                 }
52             }
53             Command::Devices(_) => {
54                 Self::print_device_response(
55                     GetDevicesResponse::parse_from_bytes(response).unwrap(),
56                     verbose,
57                 );
58             }
59             Command::Reset => {
60                 if verbose {
61                     println!("All devices have been reset.");
62                 }
63             }
64             Command::Pcap(Pcap::List(cmd)) => Self::print_list_capture_response(
65                 ListCaptureResponse::parse_from_bytes(response).unwrap(),
66                 verbose,
67                 cmd.patterns.to_owned(),
68             ),
69             Command::Pcap(Pcap::Patch(cmd)) => {
70                 if verbose {
71                     println!(
72                         "Patched Capture state to {}",
73                         Self::on_off_state_to_string(cmd.state),
74                     );
75                 }
76             }
77             Command::Pcap(Pcap::Get(_)) => {
78                 if verbose {
79                     println!("Successfully downloaded Pcap.");
80                 }
81             }
82             Command::Gui => {
83                 unimplemented!("No Grpc Response for Gui Command.");
84             }
85         }
86     }
87 
88     /// Helper function to format and print GetDevicesResponse
print_device_response(response: GetDevicesResponse, verbose: bool)89     fn print_device_response(response: GetDevicesResponse, verbose: bool) {
90         let pos_prec = 2;
91         let name_width = 16;
92         let state_width = 5;
93         let cnt_width = 9;
94         let chip_indent = 2;
95         let radio_width = 9;
96         if verbose {
97             if response.devices.is_empty() {
98                 println!("No attached devices found.");
99             } else {
100                 println!("List of attached devices:");
101             }
102             for device in response.devices {
103                 let pos = device.position;
104                 println!(
105                     "{:name_width$}  position: {:.pos_prec$}, {:.pos_prec$}, {:.pos_prec$}",
106                     device.name, pos.x, pos.y, pos.z
107                 );
108                 for chip in &device.chips {
109                     match &chip.chip {
110                         Some(Chip_oneof_chip::Bt(bt)) => {
111                             if bt.low_energy.is_some() {
112                                 let ble_chip = &bt.low_energy;
113                                 println!(
114                                     "{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?} | capture: {}",
115                                     "",
116                                     "ble:",
117                                     Self::chip_state_to_string(ble_chip.state.enum_value_or_default()),
118                                     ble_chip.rx_count,
119                                     ble_chip.tx_count,
120                                     Self::capture_state_to_string(chip.capture.enum_value_or_default())
121                                 );
122                             }
123                             if bt.classic.is_some() {
124                                 let classic_chip = &bt.classic;
125                                 println!(
126                                     "{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?} | capture: {}",
127                                     "",
128                                     "classic:",
129                                     Self::chip_state_to_string(classic_chip.state.enum_value_or_default()),
130                                     classic_chip.rx_count,
131                                     classic_chip.tx_count,
132                                     Self::capture_state_to_string(chip.capture.enum_value_or_default())
133                                 );
134                             }
135                         }
136                         Some(Chip_oneof_chip::Wifi(wifi_chip)) => {
137                             println!(
138                                 "{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?} | capture: {}",
139                                 "",
140                                 "wifi:",
141                                 Self::chip_state_to_string(wifi_chip.state.enum_value_or_default()),
142                                 wifi_chip.rx_count,
143                                 wifi_chip.tx_count,
144                                 Self::capture_state_to_string(chip.capture.enum_value_or_default())
145                             );
146                         }
147                         Some(Chip_oneof_chip::Uwb(uwb_chip)) => {
148                             println!(
149                                 "{:chip_indent$}{:radio_width$}{:state_width$}| rx_count: {:cnt_width$?} | tx_count: {:cnt_width$?} | capture: {}",
150                                 "",
151                                 "uwb:",
152                                 Self::chip_state_to_string(uwb_chip.state.enum_value_or_default()),
153                                 uwb_chip.rx_count,
154                                 uwb_chip.tx_count,
155                                 Self::capture_state_to_string(chip.capture.enum_value_or_default())
156                             );
157                         }
158                         _ => println!("{:chip_indent$}Unknown chip: down  ", ""),
159                     }
160                 }
161             }
162         } else {
163             for device in response.devices {
164                 let pos = device.position;
165                 print!("{:name_width$}  ", device.name,);
166                 if pos.x != 0.0 || pos.y != 0.0 || pos.z != 0.0 {
167                     print!(
168                         "position: {:.pos_prec$}, {:.pos_prec$}, {:.pos_prec$}",
169                         pos.x, pos.y, pos.z
170                     );
171                 }
172                 for chip in &device.chips {
173                     match &chip.chip {
174                         Some(Chip_oneof_chip::Bt(bt)) => {
175                             if bt.low_energy.is_some() {
176                                 let ble_chip = &bt.low_energy;
177                                 if ble_chip.state.enum_value_or_default() == State::OFF {
178                                     print!(
179                                         "{:chip_indent$}{:radio_width$}{:state_width$}",
180                                         "",
181                                         "ble:",
182                                         Self::chip_state_to_string(
183                                             ble_chip.state.enum_value_or_default()
184                                         ),
185                                     );
186                                 }
187                             }
188                             if bt.classic.is_some() {
189                                 let classic_chip = &bt.classic;
190                                 if classic_chip.state.enum_value_or_default() == State::OFF {
191                                     print!(
192                                         "{:chip_indent$}{:radio_width$}{:state_width$}",
193                                         "",
194                                         "classic:",
195                                         Self::chip_state_to_string(
196                                             classic_chip.state.enum_value_or_default()
197                                         )
198                                     );
199                                 }
200                             }
201                         }
202                         Some(Chip_oneof_chip::Wifi(wifi_chip)) => {
203                             if wifi_chip.state.enum_value_or_default() == State::OFF {
204                                 print!(
205                                     "{:chip_indent$}{:radio_width$}{:state_width$}",
206                                     "",
207                                     "wifi:",
208                                     Self::chip_state_to_string(
209                                         wifi_chip.state.enum_value_or_default()
210                                     )
211                                 );
212                             }
213                         }
214                         Some(Chip_oneof_chip::Uwb(uwb_chip)) => {
215                             if uwb_chip.state.enum_value_or_default() == State::OFF {
216                                 print!(
217                                     "{:chip_indent$}{:radio_width$}{:state_width$}",
218                                     "",
219                                     "uwb:",
220                                     Self::chip_state_to_string(
221                                         uwb_chip.state.enum_value_or_default()
222                                     )
223                                 );
224                             }
225                         }
226                         _ => {}
227                     }
228                     if chip.capture.enum_value_or_default() == State::ON {
229                         print!("{:chip_indent$}capture: on", "");
230                     }
231                 }
232                 println!();
233             }
234         }
235     }
236 
237     /// Helper function to convert frontend_proto::model::State to string for output
chip_state_to_string(state: State) -> String238     fn chip_state_to_string(state: State) -> String {
239         match state {
240             State::ON => "up".to_string(),
241             State::OFF => "down".to_string(),
242             _ => "unknown".to_string(),
243         }
244     }
245 
capture_state_to_string(state: State) -> String246     fn capture_state_to_string(state: State) -> String {
247         match state {
248             State::ON => "on".to_string(),
249             State::OFF => "off".to_string(),
250             _ => "unknown".to_string(),
251         }
252     }
253 
on_off_state_to_string(state: OnOffState) -> String254     fn on_off_state_to_string(state: OnOffState) -> String {
255         match state {
256             OnOffState::On => "on".to_string(),
257             OnOffState::Off => "off".to_string(),
258         }
259     }
260 
261     /// Helper function to format and print VersionResponse
print_version_response(response: VersionResponse)262     fn print_version_response(response: VersionResponse) {
263         println!("Netsim version: {}", response.version);
264     }
265 
266     /// Helper function to format and print ListCaptureResponse
print_list_capture_response( mut response: ListCaptureResponse, verbose: bool, patterns: Vec<String>, )267     fn print_list_capture_response(
268         mut response: ListCaptureResponse,
269         verbose: bool,
270         patterns: Vec<String>,
271     ) {
272         if response.captures.is_empty() {
273             if verbose {
274                 println!("No available Capture found.");
275             }
276             return;
277         }
278         if patterns.is_empty() {
279             println!("List of Captures:");
280         } else {
281             // Filter out list of captures with matching patterns
282             Self::filter_captures(&mut response.captures, &patterns);
283             if response.captures.is_empty() {
284                 if verbose {
285                     println!("No available Capture found matching pattern(s) `{:?}`:", patterns);
286                 }
287                 return;
288             }
289             println!("List of Captures matching pattern(s) `{:?}`:", patterns);
290         }
291         // Create the header row and determine column widths
292         let id_hdr = "ID";
293         let name_hdr = "Device Name";
294         let chipkind_hdr = "Chip Kind";
295         let state_hdr = "State";
296         let size_hdr = "Size";
297         let id_width = 4; // ID width of 4 since capture id (=chip_id) starts at 1000
298         let state_width = 7; // State width of 7 for 'unknown'
299         let chipkind_width = 11; // ChipKind width 11 for 'UNSPECIFIED'
300         let name_width = max(
301             (response.captures.iter().max_by_key(|x| x.device_name.len()))
302                 .unwrap_or_default()
303                 .device_name
304                 .len(),
305             name_hdr.len(),
306         );
307         let size_width = max(
308             (response.captures.iter().max_by_key(|x| x.size))
309                 .unwrap_or_default()
310                 .size
311                 .to_string()
312                 .len(),
313             size_hdr.len(),
314         );
315         // Print header for capture list
316         println!(
317             "{}",
318             if verbose {
319                 format!("{:id_width$} | {:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
320                     id_hdr,
321                     name_hdr,
322                     chipkind_hdr,
323                     state_hdr,
324                     size_hdr,
325                 )
326             } else {
327                 format!(
328                     "{:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
329                     name_hdr, chipkind_hdr, state_hdr, size_hdr,
330                 )
331             }
332         );
333         // Print information of each Capture
334         for capture in &response.captures {
335             println!(
336                 "{}",
337                 if verbose {
338                     format!("{:id_width$} | {:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
339                         capture.id.to_string(),
340                         capture.device_name,
341                         Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()),
342                         Self::capture_state_to_string(capture.state.enum_value_or_default()),
343                         capture.size,
344                     )
345                 } else {
346                     format!(
347                         "{:name_width$} | {:chipkind_width$} | {:state_width$} | {:size_width$} |",
348                         capture.device_name,
349                         Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()),
350                         Self::capture_state_to_string(capture.state.enum_value_or_default()),
351                         capture.size,
352                     )
353                 }
354             );
355         }
356     }
357 
chip_kind_to_string(chip_kind: ChipKind) -> String358     pub fn chip_kind_to_string(chip_kind: ChipKind) -> String {
359         match chip_kind {
360             ChipKind::UNSPECIFIED => "UNSPECIFIED".to_string(),
361             ChipKind::BLUETOOTH => "BLUETOOTH".to_string(),
362             ChipKind::WIFI => "WIFI".to_string(),
363             ChipKind::UWB => "UWB".to_string(),
364         }
365     }
366 
filter_captures(captures: &mut Vec<model::Capture>, keys: &[String])367     pub fn filter_captures(captures: &mut Vec<model::Capture>, keys: &[String]) {
368         // Filter out list of captures with matching pattern
369         captures.retain(|capture| {
370             keys.iter().map(|key| key.to_uppercase()).all(|key| {
371                 capture.id.to_string().contains(&key)
372                     || capture.device_name.to_uppercase().contains(&key)
373                     || Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default())
374                         .contains(&key)
375             })
376         });
377     }
378 }
379 
380 #[cfg(test)]
381 mod tests {
382     use super::*;
test_filter_captures_helper(patterns: Vec<String>, expected_captures: Vec<model::Capture>)383     fn test_filter_captures_helper(patterns: Vec<String>, expected_captures: Vec<model::Capture>) {
384         let mut captures = all_test_captures();
385         Command::filter_captures(&mut captures, &patterns);
386         assert_eq!(captures, expected_captures);
387     }
388 
capture_1() -> model::Capture389     fn capture_1() -> model::Capture {
390         model::Capture {
391             id: 4001,
392             chip_kind: ChipKind::BLUETOOTH.into(),
393             device_name: "device 1".to_string(),
394             ..Default::default()
395         }
396     }
capture_1_wifi() -> model::Capture397     fn capture_1_wifi() -> model::Capture {
398         model::Capture {
399             id: 4002,
400             chip_kind: ChipKind::WIFI.into(),
401             device_name: "device 1".to_string(),
402             ..Default::default()
403         }
404     }
capture_2() -> model::Capture405     fn capture_2() -> model::Capture {
406         model::Capture {
407             id: 4003,
408             chip_kind: ChipKind::BLUETOOTH.into(),
409             device_name: "device 2".to_string(),
410             ..Default::default()
411         }
412     }
capture_3() -> model::Capture413     fn capture_3() -> model::Capture {
414         model::Capture {
415             id: 4004,
416             chip_kind: ChipKind::WIFI.into(),
417             device_name: "device 3".to_string(),
418             ..Default::default()
419         }
420     }
capture_4_uwb() -> model::Capture421     fn capture_4_uwb() -> model::Capture {
422         model::Capture {
423             id: 4005,
424             chip_kind: ChipKind::UWB.into(),
425             device_name: "device 4".to_string(),
426             ..Default::default()
427         }
428     }
all_test_captures() -> Vec<model::Capture>429     fn all_test_captures() -> Vec<model::Capture> {
430         vec![capture_1(), capture_1_wifi(), capture_2(), capture_3(), capture_4_uwb()]
431     }
432 
433     #[test]
test_no_match()434     fn test_no_match() {
435         test_filter_captures_helper(vec!["test".to_string()], vec![]);
436     }
437 
438     #[test]
test_all_match()439     fn test_all_match() {
440         test_filter_captures_helper(vec!["device".to_string()], all_test_captures());
441     }
442 
443     #[test]
test_match_capture_id()444     fn test_match_capture_id() {
445         test_filter_captures_helper(vec!["4001".to_string()], vec![capture_1()]);
446         test_filter_captures_helper(vec!["03".to_string()], vec![capture_2()]);
447         test_filter_captures_helper(vec!["40".to_string()], all_test_captures());
448     }
449 
450     #[test]
test_match_device_name()451     fn test_match_device_name() {
452         test_filter_captures_helper(
453             vec!["device 1".to_string()],
454             vec![capture_1(), capture_1_wifi()],
455         );
456         test_filter_captures_helper(vec![" 2".to_string()], vec![capture_2()]);
457     }
458 
459     #[test]
test_match_device_name_case_insensitive()460     fn test_match_device_name_case_insensitive() {
461         test_filter_captures_helper(
462             vec!["DEVICE 1".to_string()],
463             vec![capture_1(), capture_1_wifi()],
464         );
465     }
466 
467     #[test]
test_match_wifi()468     fn test_match_wifi() {
469         test_filter_captures_helper(vec!["wifi".to_string()], vec![capture_1_wifi(), capture_3()]);
470         test_filter_captures_helper(vec!["WIFI".to_string()], vec![capture_1_wifi(), capture_3()]);
471     }
472 
473     #[test]
test_match_uwb()474     fn test_match_uwb() {
475         test_filter_captures_helper(vec!["uwb".to_string()], vec![capture_4_uwb()]);
476         test_filter_captures_helper(vec!["UWB".to_string()], vec![capture_4_uwb()]);
477     }
478 
479     #[test]
test_match_bt()480     fn test_match_bt() {
481         test_filter_captures_helper(vec!["BLUETOOTH".to_string()], vec![capture_1(), capture_2()]);
482         test_filter_captures_helper(vec!["blue".to_string()], vec![capture_1(), capture_2()]);
483     }
484 
485     #[test]
test_match_name_and_chip()486     fn test_match_name_and_chip() {
487         test_filter_captures_helper(
488             vec!["device 1".to_string(), "wifi".to_string()],
489             vec![capture_1_wifi()],
490         );
491     }
492 }
493