// 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 clap::{Args, Parser, Subcommand, ValueEnum}; use frontend_client_cxx::ffi::{FrontendClient, GrpcMethod}; use frontend_proto::common::ChipKind; use frontend_proto::frontend; use frontend_proto::frontend::patch_capture_request::PatchCapture as PatchCaptureProto; use frontend_proto::model; use frontend_proto::model::chip::{Bluetooth as Chip_Bluetooth, Radio as Chip_Radio}; use frontend_proto::model::{Chip, State}; use frontend_proto::model::{Device, Position}; use netsim_common::util::time_display::TimeDisplay; use protobuf::Message; use std::fmt; pub type BinaryProtobuf = Vec; #[derive(Debug, Parser)] pub struct NetsimArgs { #[command(subcommand)] pub command: Command, /// Set verbose mode #[arg(short, long)] pub verbose: bool, } #[derive(Debug, Subcommand)] #[command(infer_subcommands = true)] pub enum Command { /// Print Netsim version information Version, /// Control the radio state of a device Radio(Radio), /// Set the device location Move(Move), /// Display device(s) information Devices(Devices), /// Reset Netsim device scene Reset, /// Open netsim Web UI Gui, /// Control the packet capture functionalities with commands: list, patch, get #[command(subcommand)] Pcap(Pcap), } impl Command { /// Return the generated request protobuf as a byte vector /// The parsed command parameters are used to construct the request protobuf which is /// returned as a byte vector that can be sent to the server. pub fn get_request_bytes(&self) -> BinaryProtobuf { match self { Command::Version => Vec::new(), Command::Radio(cmd) => { let mut chip = Chip { ..Default::default() }; let chip_state = match cmd.status { UpDownStatus::Up => State::ON, UpDownStatus::Down => State::OFF, }; if cmd.radio_type == RadioType::Wifi { let mut wifi_chip = Chip_Radio::new(); wifi_chip.state = chip_state.into(); chip.set_wifi(wifi_chip); chip.kind = ChipKind::WIFI.into(); } else if cmd.radio_type == RadioType::Uwb { let mut uwb_chip = Chip_Radio::new(); uwb_chip.state = chip_state.into(); chip.set_uwb(uwb_chip); chip.kind = ChipKind::UWB.into(); } else { let mut bt_chip = Chip_Bluetooth::new(); let mut bt_chip_radio = Chip_Radio::new(); bt_chip_radio.state = chip_state.into(); if cmd.radio_type == RadioType::Ble { bt_chip.low_energy = Some(bt_chip_radio).into(); } else { bt_chip.classic = Some(bt_chip_radio).into(); } chip.kind = ChipKind::BLUETOOTH.into(); chip.set_bt(bt_chip); } let mut result = frontend::PatchDeviceRequest::new(); let mut device = Device::new(); device.name = cmd.name.to_owned(); device.chips.push(chip); result.device = Some(device).into(); result.write_to_bytes().unwrap() } Command::Move(cmd) => { let mut result = frontend::PatchDeviceRequest::new(); let mut device = Device::new(); let position = Position { x: cmd.x, y: cmd.y, z: cmd.z.unwrap_or_default(), ..Default::default() }; device.name = cmd.name.to_owned(); device.position = Some(position).into(); result.device = Some(device).into(); result.write_to_bytes().unwrap() } Command::Devices(_) => Vec::new(), Command::Reset => Vec::new(), Command::Gui => { unimplemented!("get_request_bytes is not implemented for Gui Command."); } Command::Pcap(pcap_cmd) => match pcap_cmd { Pcap::List(_) => Vec::new(), Pcap::Get(_) => { unimplemented!("get_request_bytes not implemented for Pcap Get command. Use get_requests instead.") } Pcap::Patch(_) => { unimplemented!("get_request_bytes not implemented for Pcap Patch command. Use get_requests instead.") } }, } } /// Create and return the request protobuf(s) for the command. /// In the case of a command with pattern argument(s) there may be multiple gRPC requests. /// The parsed command parameters are used to construct the request protobuf. /// The client is used to send gRPC call(s) to retrieve information needed for request protobufs. pub fn get_requests(&mut self, client: &cxx::UniquePtr) -> Vec { match self { Command::Pcap(Pcap::Patch(cmd)) => { let mut reqs = Vec::new(); let filtered_captures = Self::get_filtered_captures(client, &cmd.patterns); // Create a request for each capture for capture in &filtered_captures { let mut result = frontend::PatchCaptureRequest::new(); result.id = capture.id; let capture_state = match cmd.state { OnOffState::On => State::ON, OnOffState::Off => State::OFF, }; let mut patch_capture = PatchCaptureProto::new(); patch_capture.state = capture_state.into(); result.patch = Some(patch_capture).into(); reqs.push(result.write_to_bytes().unwrap()) } reqs } Command::Pcap(Pcap::Get(cmd)) => { let mut reqs = Vec::new(); let filtered_captures = Self::get_filtered_captures(client, &cmd.patterns); // Create a request for each capture for capture in &filtered_captures { let mut result = frontend::GetCaptureRequest::new(); result.id = capture.id; reqs.push(result.write_to_bytes().unwrap()); let time_display = TimeDisplay::new( capture.timestamp.get_or_default().seconds, capture.timestamp.get_or_default().nanos as u32, ); cmd.filenames.push(format!( "{:?}-{}-{}-{}", capture.id, capture.device_name.to_owned().replace(' ', "_"), Self::chip_kind_to_string(capture.chip_kind.enum_value_or_default()), time_display.utc_display() )); } reqs } _ => { unimplemented!( "get_requests not implemented for this command. Use get_request_bytes instead." ) } } } fn get_filtered_captures( client: &cxx::UniquePtr, patterns: &Vec, ) -> Vec { // Get list of captures let result = client.send_grpc(&GrpcMethod::ListCapture, &Vec::new()); if !result.is_ok() { eprintln!("Grpc call error: {}", result.err()); return Vec::new(); } let mut response = frontend::ListCaptureResponse::parse_from_bytes(result.byte_vec().as_slice()).unwrap(); if !patterns.is_empty() { // Filter out list of captures with matching patterns Self::filter_captures(&mut response.captures, patterns) } response.captures } } #[derive(Debug, Args)] pub struct Radio { /// Radio type #[arg(value_enum, ignore_case = true)] pub radio_type: RadioType, /// Radio status #[arg(value_enum, ignore_case = true)] pub status: UpDownStatus, /// Device name pub name: String, } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum RadioType { Ble, Classic, Wifi, Uwb, } impl fmt::Display for RadioType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum UpDownStatus { Up, Down, } impl fmt::Display for UpDownStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } #[derive(Debug, Args)] pub struct Move { /// Device name pub name: String, /// x position of device pub x: f32, /// y position of device pub y: f32, /// Optional z position of device pub z: Option, } #[derive(Debug, Args)] pub struct Devices { /// Continuously print device(s) information every second #[arg(short, long)] pub continuous: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum OnOffState { On, Off, } #[derive(Debug, Subcommand)] pub enum Pcap { /// List currently available Captures (packet captures) List(ListCapture), /// Patch a Capture source to turn packet capture on/off Patch(PatchCapture), /// Download the packet capture content Get(GetCapture), } #[derive(Debug, Args)] pub struct ListCapture { /// Optional strings of pattern for captures to list. Possible filter fields include Capture ID, Device Name, and Chip Kind pub patterns: Vec, } #[derive(Debug, Args)] pub struct PatchCapture { /// Packet capture state #[arg(value_enum, ignore_case = true)] pub state: OnOffState, /// Optional strings of pattern for captures to patch. Possible filter fields include Capture ID, Device Name, and Chip Kind pub patterns: Vec, } #[derive(Debug, Args)] pub struct GetCapture { /// Optional strings of pattern for captures to get. Possible filter fields include Capture ID, Device Name, and Chip Kind pub patterns: Vec, /// Directory to store downloaded capture(s) #[arg(short = 'o', long)] pub location: Option, #[arg(skip)] pub filenames: Vec, }