• 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 clap::{Args, Parser, Subcommand, ValueEnum};
16 use frontend_client_cxx::ffi::{FrontendClient, GrpcMethod};
17 use frontend_proto::common::ChipKind;
18 use frontend_proto::frontend;
19 use frontend_proto::frontend::patch_capture_request::PatchCapture as PatchCaptureProto;
20 use frontend_proto::model;
21 use frontend_proto::model::chip::{Bluetooth as Chip_Bluetooth, Radio as Chip_Radio};
22 use frontend_proto::model::{Chip, State};
23 use frontend_proto::model::{Device, Position};
24 use netsim_common::util::time_display::TimeDisplay;
25 use protobuf::Message;
26 use std::fmt;
27 
28 pub type BinaryProtobuf = Vec<u8>;
29 
30 #[derive(Debug, Parser)]
31 pub struct NetsimArgs {
32     #[command(subcommand)]
33     pub command: Command,
34     /// Set verbose mode
35     #[arg(short, long)]
36     pub verbose: bool,
37 }
38 
39 #[derive(Debug, Subcommand)]
40 #[command(infer_subcommands = true)]
41 pub enum Command {
42     /// Print Netsim version information
43     Version,
44     /// Control the radio state of a device
45     Radio(Radio),
46     /// Set the device location
47     Move(Move),
48     /// Display device(s) information
49     Devices(Devices),
50     /// Reset Netsim device scene
51     Reset,
52     /// Open netsim Web UI
53     Gui,
54     /// Control the packet capture functionalities with commands: list, patch, get
55     #[command(subcommand)]
56     Pcap(Pcap),
57 }
58 
59 impl Command {
60     /// Return the generated request protobuf as a byte vector
61     /// The parsed command parameters are used to construct the request protobuf which is
62     /// returned as a byte vector that can be sent to the server.
get_request_bytes(&self) -> BinaryProtobuf63     pub fn get_request_bytes(&self) -> BinaryProtobuf {
64         match self {
65             Command::Version => Vec::new(),
66             Command::Radio(cmd) => {
67                 let mut chip = Chip { ..Default::default() };
68                 let chip_state = match cmd.status {
69                     UpDownStatus::Up => State::ON,
70                     UpDownStatus::Down => State::OFF,
71                 };
72                 if cmd.radio_type == RadioType::Wifi {
73                     let mut wifi_chip = Chip_Radio::new();
74                     wifi_chip.state = chip_state.into();
75                     chip.set_wifi(wifi_chip);
76                     chip.kind = ChipKind::WIFI.into();
77                 } else if cmd.radio_type == RadioType::Uwb {
78                     let mut uwb_chip = Chip_Radio::new();
79                     uwb_chip.state = chip_state.into();
80                     chip.set_uwb(uwb_chip);
81                     chip.kind = ChipKind::UWB.into();
82                 } else {
83                     let mut bt_chip = Chip_Bluetooth::new();
84                     let mut bt_chip_radio = Chip_Radio::new();
85                     bt_chip_radio.state = chip_state.into();
86                     if cmd.radio_type == RadioType::Ble {
87                         bt_chip.low_energy = Some(bt_chip_radio).into();
88                     } else {
89                         bt_chip.classic = Some(bt_chip_radio).into();
90                     }
91                     chip.kind = ChipKind::BLUETOOTH.into();
92                     chip.set_bt(bt_chip);
93                 }
94                 let mut result = frontend::PatchDeviceRequest::new();
95                 let mut device = Device::new();
96                 device.name = cmd.name.to_owned();
97                 device.chips.push(chip);
98                 result.device = Some(device).into();
99                 result.write_to_bytes().unwrap()
100             }
101             Command::Move(cmd) => {
102                 let mut result = frontend::PatchDeviceRequest::new();
103                 let mut device = Device::new();
104                 let position = Position {
105                     x: cmd.x,
106                     y: cmd.y,
107                     z: cmd.z.unwrap_or_default(),
108                     ..Default::default()
109                 };
110                 device.name = cmd.name.to_owned();
111                 device.position = Some(position).into();
112                 result.device = Some(device).into();
113                 result.write_to_bytes().unwrap()
114             }
115             Command::Devices(_) => Vec::new(),
116             Command::Reset => Vec::new(),
117             Command::Gui => {
118                 unimplemented!("get_request_bytes is not implemented for Gui Command.");
119             }
120             Command::Pcap(pcap_cmd) => match pcap_cmd {
121                 Pcap::List(_) => Vec::new(),
122                 Pcap::Get(_) => {
123                     unimplemented!("get_request_bytes not implemented for Pcap Get command. Use get_requests instead.")
124                 }
125                 Pcap::Patch(_) => {
126                     unimplemented!("get_request_bytes not implemented for Pcap Patch command. Use get_requests instead.")
127                 }
128             },
129         }
130     }
131 
132     /// Create and return the request protobuf(s) for the command.
133     /// In the case of a command with pattern argument(s) there may be multiple gRPC requests.
134     /// The parsed command parameters are used to construct the request protobuf.
135     /// The client is used to send gRPC call(s) to retrieve information needed for request protobufs.
get_requests(&mut self, client: &cxx::UniquePtr<FrontendClient>) -> Vec<BinaryProtobuf>136     pub fn get_requests(&mut self, client: &cxx::UniquePtr<FrontendClient>) -> Vec<BinaryProtobuf> {
137         match self {
138             Command::Pcap(Pcap::Patch(cmd)) => {
139                 let mut reqs = Vec::new();
140                 let filtered_captures = Self::get_filtered_captures(client, &cmd.patterns);
141                 // Create a request for each capture
142                 for capture in &filtered_captures {
143                     let mut result = frontend::PatchCaptureRequest::new();
144                     result.id = capture.id;
145                     let capture_state = match cmd.state {
146                         OnOffState::On => State::ON,
147                         OnOffState::Off => State::OFF,
148                     };
149                     let mut patch_capture = PatchCaptureProto::new();
150                     patch_capture.state = capture_state.into();
151                     result.patch = Some(patch_capture).into();
152                     reqs.push(result.write_to_bytes().unwrap())
153                 }
154                 reqs
155             }
156             Command::Pcap(Pcap::Get(cmd)) => {
157                 let mut reqs = Vec::new();
158                 let filtered_captures = Self::get_filtered_captures(client, &cmd.patterns);
159                 // Create a request for each capture
160                 for capture in &filtered_captures {
161                     let mut result = frontend::GetCaptureRequest::new();
162                     result.id = capture.id;
163                     reqs.push(result.write_to_bytes().unwrap());
164                     let time_display = TimeDisplay::new(
165                         capture.timestamp.get_or_default().seconds,
166                         capture.timestamp.get_or_default().nanos as u32,
167                     );
168                     cmd.filenames.push(format!(
169                         "{:?}-{}-{}-{}",
170                         capture.id,
171                         capture.device_name.to_owned().replace(' ', "_"),
172                         Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()),
173                         time_display.utc_display()
174                     ));
175                 }
176                 reqs
177             }
178             _ => {
179                 unimplemented!(
180                     "get_requests not implemented for this command. Use get_request_bytes instead."
181                 )
182             }
183         }
184     }
185 
get_filtered_captures( client: &cxx::UniquePtr<FrontendClient>, patterns: &Vec<String>, ) -> Vec<model::Capture>186     fn get_filtered_captures(
187         client: &cxx::UniquePtr<FrontendClient>,
188         patterns: &Vec<String>,
189     ) -> Vec<model::Capture> {
190         // Get list of captures
191         let result = client.send_grpc(&GrpcMethod::ListCapture, &Vec::new());
192         if !result.is_ok() {
193             eprintln!("Grpc call error: {}", result.err());
194             return Vec::new();
195         }
196         let mut response =
197             frontend::ListCaptureResponse::parse_from_bytes(result.byte_vec().as_slice()).unwrap();
198         if !patterns.is_empty() {
199             // Filter out list of captures with matching patterns
200             Self::filter_captures(&mut response.captures, patterns)
201         }
202         response.captures
203     }
204 }
205 
206 #[derive(Debug, Args)]
207 pub struct Radio {
208     /// Radio type
209     #[arg(value_enum, ignore_case = true)]
210     pub radio_type: RadioType,
211     /// Radio status
212     #[arg(value_enum, ignore_case = true)]
213     pub status: UpDownStatus,
214     /// Device name
215     pub name: String,
216 }
217 
218 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
219 pub enum RadioType {
220     Ble,
221     Classic,
222     Wifi,
223     Uwb,
224 }
225 
226 impl fmt::Display for RadioType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result227     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228         write!(f, "{:?}", self)
229     }
230 }
231 
232 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
233 pub enum UpDownStatus {
234     Up,
235     Down,
236 }
237 
238 impl fmt::Display for UpDownStatus {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result239     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
240         write!(f, "{:?}", self)
241     }
242 }
243 
244 #[derive(Debug, Args)]
245 pub struct Move {
246     /// Device name
247     pub name: String,
248     /// x position of device
249     pub x: f32,
250     /// y position of device
251     pub y: f32,
252     /// Optional z position of device
253     pub z: Option<f32>,
254 }
255 
256 #[derive(Debug, Args)]
257 pub struct Devices {
258     /// Continuously print device(s) information every second
259     #[arg(short, long)]
260     pub continuous: bool,
261 }
262 
263 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
264 pub enum OnOffState {
265     On,
266     Off,
267 }
268 
269 #[derive(Debug, Subcommand)]
270 pub enum Pcap {
271     /// List currently available Captures (packet captures)
272     List(ListCapture),
273     /// Patch a Capture source to turn packet capture on/off
274     Patch(PatchCapture),
275     /// Download the packet capture content
276     Get(GetCapture),
277 }
278 
279 #[derive(Debug, Args)]
280 pub struct ListCapture {
281     /// Optional strings of pattern for captures to list. Possible filter fields include Capture ID, Device Name, and Chip Kind
282     pub patterns: Vec<String>,
283 }
284 
285 #[derive(Debug, Args)]
286 pub struct PatchCapture {
287     /// Packet capture state
288     #[arg(value_enum, ignore_case = true)]
289     pub state: OnOffState,
290     /// Optional strings of pattern for captures to patch. Possible filter fields include Capture ID, Device Name, and Chip Kind
291     pub patterns: Vec<String>,
292 }
293 
294 #[derive(Debug, Args)]
295 pub struct GetCapture {
296     /// Optional strings of pattern for captures to get. Possible filter fields include Capture ID, Device Name, and Chip Kind
297     pub patterns: Vec<String>,
298     /// Directory to store downloaded capture(s)
299     #[arg(short = 'o', long)]
300     pub location: Option<String>,
301     #[arg(skip)]
302     pub filenames: Vec<String>,
303 }
304