// 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. //! Command Line Interface for Netsim mod args; mod browser; mod pcap_handler; mod requests; mod response; use std::env; use std::fs::File; use std::path::PathBuf; use args::{BinaryProtobuf, GetCapture, NetsimArgs}; use clap::Parser; use cxx::UniquePtr; use frontend_client_cxx::ffi::{new_frontend_client, ClientResult, FrontendClient, GrpcMethod}; use frontend_client_cxx::ClientResponseReader; use pcap_handler::CaptureHandler; // helper function to process streaming Grpc request fn perform_streaming_request( client: &cxx::UniquePtr, cmd: &GetCapture, req: &BinaryProtobuf, filename: &str, ) -> UniquePtr { let dir = if cmd.location.is_some() { PathBuf::from(cmd.location.to_owned().unwrap()) } else { env::current_dir().unwrap() }; // Find next available file name let mut output_file = dir.join(filename.to_string() + ".pcap"); let mut idx = 0; while output_file.exists() { idx += 1; output_file = dir.join(format!("{}_{}.pcap", filename, idx)); } client.get_capture( req, &ClientResponseReader { handler: Box::new(CaptureHandler { file: File::create(&output_file).unwrap_or_else(|_| { panic!("Failed to create file: {}", &output_file.display()) }), path: output_file, }), }, ) } /// helper function to send the Grpc request(s) and handle the response(s) per the given command fn perform_command( command: &mut args::Command, client: cxx::UniquePtr, grpc_method: GrpcMethod, verbose: bool, ) -> Result<(), String> { // Get command's gRPC request(s) let requests = match command { args::Command::Pcap(args::Pcap::Patch(_) | args::Pcap::Get(_)) => { command.get_requests(&client) } _ => vec![command.get_request_bytes()], }; // Process each request for (i, req) in requests.iter().enumerate() { let result = match command { // Continuous option sends the gRPC call every second args::Command::Devices(ref cmd) if cmd.continuous => loop { process_result(command, client.send_grpc(&grpc_method, req), verbose)?; std::thread::sleep(std::time::Duration::from_secs(1)); }, // Get Pcap use streaming gRPC reader request args::Command::Pcap(args::Pcap::Get(ref cmd)) => { perform_streaming_request(&client, cmd, req, &cmd.filenames[i]) } // All other commands use a single gRPC call _ => client.send_grpc(&grpc_method, req), }; process_result(command, result, verbose)?; } Ok(()) } /// Check and handle the gRPC call result fn process_result( command: &args::Command, result: UniquePtr, verbose: bool, ) -> Result<(), String> { if result.is_ok() { command.print_response(result.byte_vec().as_slice(), verbose); } else { return Err(format!("Grpc call error: {}", result.err())); } Ok(()) } #[no_mangle] /// main Rust netsim CLI function to be called by C wrapper netsim.cc pub extern "C" fn rust_main() { let mut args = NetsimArgs::parse(); if matches!(args.command, args::Command::Gui) { browser::open("http://localhost:7681/"); return; } let grpc_method = args.command.grpc_method(); let client = new_frontend_client(); if client.is_null() { eprintln!("Unable to create frontend client. Please ensure netsimd is running."); return; } if let Err(e) = perform_command(&mut args.command, client, grpc_method, args.verbose) { eprintln!("{e}"); } }