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 //! Command Line Interface for Netsim
16
17 mod args;
18 mod browser;
19 mod pcap_handler;
20 mod requests;
21 mod response;
22
23 use std::env;
24 use std::fs::File;
25 use std::path::PathBuf;
26
27 use args::{BinaryProtobuf, GetCapture, NetsimArgs};
28 use clap::Parser;
29 use cxx::UniquePtr;
30 use frontend_client_cxx::ffi::{new_frontend_client, ClientResult, FrontendClient, GrpcMethod};
31 use frontend_client_cxx::ClientResponseReader;
32 use pcap_handler::CaptureHandler;
33
34 // helper function to process streaming Grpc request
perform_streaming_request( client: &cxx::UniquePtr<FrontendClient>, cmd: &GetCapture, req: &BinaryProtobuf, filename: &str, ) -> UniquePtr<ClientResult>35 fn perform_streaming_request(
36 client: &cxx::UniquePtr<FrontendClient>,
37 cmd: &GetCapture,
38 req: &BinaryProtobuf,
39 filename: &str,
40 ) -> UniquePtr<ClientResult> {
41 let dir = if cmd.location.is_some() {
42 PathBuf::from(cmd.location.to_owned().unwrap())
43 } else {
44 env::current_dir().unwrap()
45 };
46 // Find next available file name
47 let mut output_file = dir.join(filename.to_string() + ".pcap");
48 let mut idx = 0;
49 while output_file.exists() {
50 idx += 1;
51 output_file = dir.join(format!("{}_{}.pcap", filename, idx));
52 }
53 client.get_capture(
54 req,
55 &ClientResponseReader {
56 handler: Box::new(CaptureHandler {
57 file: File::create(&output_file).unwrap_or_else(|_| {
58 panic!("Failed to create file: {}", &output_file.display())
59 }),
60 path: output_file,
61 }),
62 },
63 )
64 }
65
66 /// helper function to send the Grpc request(s) and handle the response(s) per the given command
perform_command( command: &mut args::Command, client: cxx::UniquePtr<FrontendClient>, grpc_method: GrpcMethod, verbose: bool, ) -> Result<(), String>67 fn perform_command(
68 command: &mut args::Command,
69 client: cxx::UniquePtr<FrontendClient>,
70 grpc_method: GrpcMethod,
71 verbose: bool,
72 ) -> Result<(), String> {
73 // Get command's gRPC request(s)
74 let requests = match command {
75 args::Command::Pcap(args::Pcap::Patch(_) | args::Pcap::Get(_)) => {
76 command.get_requests(&client)
77 }
78 _ => vec![command.get_request_bytes()],
79 };
80
81 // Process each request
82 for (i, req) in requests.iter().enumerate() {
83 let result = match command {
84 // Continuous option sends the gRPC call every second
85 args::Command::Devices(ref cmd) if cmd.continuous => loop {
86 process_result(command, client.send_grpc(&grpc_method, req), verbose)?;
87 std::thread::sleep(std::time::Duration::from_secs(1));
88 },
89 // Get Pcap use streaming gRPC reader request
90 args::Command::Pcap(args::Pcap::Get(ref cmd)) => {
91 perform_streaming_request(&client, cmd, req, &cmd.filenames[i])
92 }
93 // All other commands use a single gRPC call
94 _ => client.send_grpc(&grpc_method, req),
95 };
96 process_result(command, result, verbose)?;
97 }
98 Ok(())
99 }
100
101 /// Check and handle the gRPC call result
process_result( command: &args::Command, result: UniquePtr<ClientResult>, verbose: bool, ) -> Result<(), String>102 fn process_result(
103 command: &args::Command,
104 result: UniquePtr<ClientResult>,
105 verbose: bool,
106 ) -> Result<(), String> {
107 if result.is_ok() {
108 command.print_response(result.byte_vec().as_slice(), verbose);
109 } else {
110 return Err(format!("Grpc call error: {}", result.err()));
111 }
112 Ok(())
113 }
114 #[no_mangle]
115 /// main Rust netsim CLI function to be called by C wrapper netsim.cc
rust_main()116 pub extern "C" fn rust_main() {
117 let mut args = NetsimArgs::parse();
118 if matches!(args.command, args::Command::Gui) {
119 browser::open("http://localhost:7681/");
120 return;
121 }
122 let grpc_method = args.command.grpc_method();
123 let client = new_frontend_client();
124 if client.is_null() {
125 eprintln!("Unable to create frontend client. Please ensure netsimd is running.");
126 return;
127 }
128 if let Err(e) = perform_command(&mut args.command, client, grpc_method, args.verbose) {
129 eprintln!("{e}");
130 }
131 }
132