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