xref: /aosp_15_r20/tools/netsim/rust/cli/src/lib.rs (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
1*cf78ab8cSAndroid Build Coastguard Worker // Copyright 2022 Google LLC
2*cf78ab8cSAndroid Build Coastguard Worker //
3*cf78ab8cSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License");
4*cf78ab8cSAndroid Build Coastguard Worker // you may not use this file except in compliance with the License.
5*cf78ab8cSAndroid Build Coastguard Worker // You may obtain a copy of the License at
6*cf78ab8cSAndroid Build Coastguard Worker //
7*cf78ab8cSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*cf78ab8cSAndroid Build Coastguard Worker //
9*cf78ab8cSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*cf78ab8cSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
11*cf78ab8cSAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*cf78ab8cSAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
13*cf78ab8cSAndroid Build Coastguard Worker // limitations under the License.
14*cf78ab8cSAndroid Build Coastguard Worker 
15*cf78ab8cSAndroid Build Coastguard Worker //! Command Line Interface for Netsim
16*cf78ab8cSAndroid Build Coastguard Worker 
17*cf78ab8cSAndroid Build Coastguard Worker mod args;
18*cf78ab8cSAndroid Build Coastguard Worker mod browser;
19*cf78ab8cSAndroid Build Coastguard Worker mod display;
20*cf78ab8cSAndroid Build Coastguard Worker mod ffi;
21*cf78ab8cSAndroid Build Coastguard Worker mod file_handler;
22*cf78ab8cSAndroid Build Coastguard Worker mod requests;
23*cf78ab8cSAndroid Build Coastguard Worker mod response;
24*cf78ab8cSAndroid Build Coastguard Worker 
25*cf78ab8cSAndroid Build Coastguard Worker use netsim_common::util::os_utils::{get_instance, get_server_address};
26*cf78ab8cSAndroid Build Coastguard Worker use netsim_proto::frontend::{DeleteChipRequest, ListDeviceResponse};
27*cf78ab8cSAndroid Build Coastguard Worker use protobuf::Message;
28*cf78ab8cSAndroid Build Coastguard Worker use std::env;
29*cf78ab8cSAndroid Build Coastguard Worker use std::fs::File;
30*cf78ab8cSAndroid Build Coastguard Worker use std::path::PathBuf;
31*cf78ab8cSAndroid Build Coastguard Worker use tracing::error;
32*cf78ab8cSAndroid Build Coastguard Worker 
33*cf78ab8cSAndroid Build Coastguard Worker use crate::ffi::frontend_client_ffi::{
34*cf78ab8cSAndroid Build Coastguard Worker     new_frontend_client, ClientResult, FrontendClient, GrpcMethod,
35*cf78ab8cSAndroid Build Coastguard Worker };
36*cf78ab8cSAndroid Build Coastguard Worker use crate::ffi::ClientResponseReader;
37*cf78ab8cSAndroid Build Coastguard Worker use args::{BinaryProtobuf, GetCapture, NetsimArgs};
38*cf78ab8cSAndroid Build Coastguard Worker use clap::Parser;
39*cf78ab8cSAndroid Build Coastguard Worker use cxx::{let_cxx_string, UniquePtr};
40*cf78ab8cSAndroid Build Coastguard Worker use file_handler::FileHandler;
41*cf78ab8cSAndroid Build Coastguard Worker use netsim_common::util::netsim_logger;
42*cf78ab8cSAndroid Build Coastguard Worker 
43*cf78ab8cSAndroid Build Coastguard Worker // helper function to process streaming Grpc request
perform_streaming_request( client: &cxx::UniquePtr<FrontendClient>, cmd: &mut GetCapture, req: &BinaryProtobuf, filename: &str, ) -> UniquePtr<ClientResult>44*cf78ab8cSAndroid Build Coastguard Worker fn perform_streaming_request(
45*cf78ab8cSAndroid Build Coastguard Worker     client: &cxx::UniquePtr<FrontendClient>,
46*cf78ab8cSAndroid Build Coastguard Worker     cmd: &mut GetCapture,
47*cf78ab8cSAndroid Build Coastguard Worker     req: &BinaryProtobuf,
48*cf78ab8cSAndroid Build Coastguard Worker     filename: &str,
49*cf78ab8cSAndroid Build Coastguard Worker ) -> UniquePtr<ClientResult> {
50*cf78ab8cSAndroid Build Coastguard Worker     let dir = if cmd.location.is_some() {
51*cf78ab8cSAndroid Build Coastguard Worker         PathBuf::from(cmd.location.to_owned().unwrap())
52*cf78ab8cSAndroid Build Coastguard Worker     } else {
53*cf78ab8cSAndroid Build Coastguard Worker         env::current_dir().unwrap()
54*cf78ab8cSAndroid Build Coastguard Worker     };
55*cf78ab8cSAndroid Build Coastguard Worker     let output_file = dir.join(filename);
56*cf78ab8cSAndroid Build Coastguard Worker     cmd.current_file = output_file.display().to_string();
57*cf78ab8cSAndroid Build Coastguard Worker     client.get_capture(
58*cf78ab8cSAndroid Build Coastguard Worker         req,
59*cf78ab8cSAndroid Build Coastguard Worker         &ClientResponseReader {
60*cf78ab8cSAndroid Build Coastguard Worker             handler: Box::new(FileHandler {
61*cf78ab8cSAndroid Build Coastguard Worker                 file: File::create(&output_file).unwrap_or_else(|_| {
62*cf78ab8cSAndroid Build Coastguard Worker                     panic!("Failed to create file: {}", &output_file.display())
63*cf78ab8cSAndroid Build Coastguard Worker                 }),
64*cf78ab8cSAndroid Build Coastguard Worker                 path: output_file,
65*cf78ab8cSAndroid Build Coastguard Worker             }),
66*cf78ab8cSAndroid Build Coastguard Worker         },
67*cf78ab8cSAndroid Build Coastguard Worker     )
68*cf78ab8cSAndroid Build Coastguard Worker }
69*cf78ab8cSAndroid Build Coastguard Worker 
70*cf78ab8cSAndroid Build Coastguard Worker /// 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>71*cf78ab8cSAndroid Build Coastguard Worker fn perform_command(
72*cf78ab8cSAndroid Build Coastguard Worker     command: &mut args::Command,
73*cf78ab8cSAndroid Build Coastguard Worker     client: cxx::UniquePtr<FrontendClient>,
74*cf78ab8cSAndroid Build Coastguard Worker     grpc_method: GrpcMethod,
75*cf78ab8cSAndroid Build Coastguard Worker     verbose: bool,
76*cf78ab8cSAndroid Build Coastguard Worker ) -> Result<(), String> {
77*cf78ab8cSAndroid Build Coastguard Worker     // Get command's gRPC request(s)
78*cf78ab8cSAndroid Build Coastguard Worker     let requests = match command {
79*cf78ab8cSAndroid Build Coastguard Worker         args::Command::Capture(args::Capture::Patch(_) | args::Capture::Get(_)) => {
80*cf78ab8cSAndroid Build Coastguard Worker             command.get_requests(&client)
81*cf78ab8cSAndroid Build Coastguard Worker         }
82*cf78ab8cSAndroid Build Coastguard Worker         args::Command::Beacon(args::Beacon::Remove(_)) => {
83*cf78ab8cSAndroid Build Coastguard Worker             vec![args::Command::Devices(args::Devices { continuous: false }).get_request_bytes()]
84*cf78ab8cSAndroid Build Coastguard Worker         }
85*cf78ab8cSAndroid Build Coastguard Worker         _ => vec![command.get_request_bytes()],
86*cf78ab8cSAndroid Build Coastguard Worker     };
87*cf78ab8cSAndroid Build Coastguard Worker     let mut process_error = false;
88*cf78ab8cSAndroid Build Coastguard Worker     // Process each request
89*cf78ab8cSAndroid Build Coastguard Worker     for (i, req) in requests.iter().enumerate() {
90*cf78ab8cSAndroid Build Coastguard Worker         let result = match command {
91*cf78ab8cSAndroid Build Coastguard Worker             // Continuous option sends the gRPC call every second
92*cf78ab8cSAndroid Build Coastguard Worker             args::Command::Devices(ref cmd) if cmd.continuous => {
93*cf78ab8cSAndroid Build Coastguard Worker                 continuous_perform_command(command, &client, grpc_method, req, verbose)?
94*cf78ab8cSAndroid Build Coastguard Worker             }
95*cf78ab8cSAndroid Build Coastguard Worker             args::Command::Capture(args::Capture::List(ref cmd)) if cmd.continuous => {
96*cf78ab8cSAndroid Build Coastguard Worker                 continuous_perform_command(command, &client, grpc_method, req, verbose)?
97*cf78ab8cSAndroid Build Coastguard Worker             }
98*cf78ab8cSAndroid Build Coastguard Worker             // Get Capture use streaming gRPC reader request
99*cf78ab8cSAndroid Build Coastguard Worker             args::Command::Capture(args::Capture::Get(ref mut cmd)) => {
100*cf78ab8cSAndroid Build Coastguard Worker                 perform_streaming_request(&client, cmd, req, &cmd.filenames[i].to_owned())
101*cf78ab8cSAndroid Build Coastguard Worker             }
102*cf78ab8cSAndroid Build Coastguard Worker             args::Command::Beacon(args::Beacon::Remove(ref cmd)) => {
103*cf78ab8cSAndroid Build Coastguard Worker                 let devices = client.send_grpc(&GrpcMethod::ListDevice, req);
104*cf78ab8cSAndroid Build Coastguard Worker                 let id = find_id_for_remove(devices.byte_vec().as_slice(), cmd)?;
105*cf78ab8cSAndroid Build Coastguard Worker                 let req = &DeleteChipRequest { id, ..Default::default() }
106*cf78ab8cSAndroid Build Coastguard Worker                     .write_to_bytes()
107*cf78ab8cSAndroid Build Coastguard Worker                     .map_err(|err| format!("{err}"))?;
108*cf78ab8cSAndroid Build Coastguard Worker 
109*cf78ab8cSAndroid Build Coastguard Worker                 client.send_grpc(&grpc_method, req)
110*cf78ab8cSAndroid Build Coastguard Worker             }
111*cf78ab8cSAndroid Build Coastguard Worker             // All other commands use a single gRPC call
112*cf78ab8cSAndroid Build Coastguard Worker             _ => client.send_grpc(&grpc_method, req),
113*cf78ab8cSAndroid Build Coastguard Worker         };
114*cf78ab8cSAndroid Build Coastguard Worker         if let Err(e) = process_result(command, result, verbose) {
115*cf78ab8cSAndroid Build Coastguard Worker             error!("{}", e);
116*cf78ab8cSAndroid Build Coastguard Worker             process_error = true;
117*cf78ab8cSAndroid Build Coastguard Worker         };
118*cf78ab8cSAndroid Build Coastguard Worker     }
119*cf78ab8cSAndroid Build Coastguard Worker     if process_error {
120*cf78ab8cSAndroid Build Coastguard Worker         return Err("Not all requests were processed successfully.".to_string());
121*cf78ab8cSAndroid Build Coastguard Worker     }
122*cf78ab8cSAndroid Build Coastguard Worker     Ok(())
123*cf78ab8cSAndroid Build Coastguard Worker }
124*cf78ab8cSAndroid Build Coastguard Worker 
find_id_for_remove(response: &[u8], cmd: &args::BeaconRemove) -> Result<u32, String>125*cf78ab8cSAndroid Build Coastguard Worker fn find_id_for_remove(response: &[u8], cmd: &args::BeaconRemove) -> Result<u32, String> {
126*cf78ab8cSAndroid Build Coastguard Worker     let devices = ListDeviceResponse::parse_from_bytes(response).unwrap().devices;
127*cf78ab8cSAndroid Build Coastguard Worker     let id = devices
128*cf78ab8cSAndroid Build Coastguard Worker         .iter()
129*cf78ab8cSAndroid Build Coastguard Worker         .find(|device| device.name == cmd.device_name)
130*cf78ab8cSAndroid Build Coastguard Worker         .and_then(|device| cmd.chip_name.as_ref().map_or(
131*cf78ab8cSAndroid Build Coastguard Worker             (device.chips.len() == 1).then_some(&device.chips[0]),
132*cf78ab8cSAndroid Build Coastguard Worker             |chip_name| device.chips.iter().find(|chip| &chip.name == chip_name)
133*cf78ab8cSAndroid Build Coastguard Worker         ))
134*cf78ab8cSAndroid Build Coastguard Worker         .ok_or(cmd.chip_name.as_ref().map_or(
135*cf78ab8cSAndroid Build Coastguard Worker             format!("failed to delete chip: device '{}' has multiple possible candidates, please specify a chip name", cmd.device_name),
136*cf78ab8cSAndroid Build Coastguard Worker             |chip_name| format!("failed to delete chip: could not find chip '{}' on device '{}'", chip_name, cmd.device_name))
137*cf78ab8cSAndroid Build Coastguard Worker         )?
138*cf78ab8cSAndroid Build Coastguard Worker         .id;
139*cf78ab8cSAndroid Build Coastguard Worker 
140*cf78ab8cSAndroid Build Coastguard Worker     Ok(id)
141*cf78ab8cSAndroid Build Coastguard Worker }
142*cf78ab8cSAndroid Build Coastguard Worker 
143*cf78ab8cSAndroid Build Coastguard Worker /// Check and handle the gRPC call result
continuous_perform_command( command: &args::Command, client: &cxx::UniquePtr<FrontendClient>, grpc_method: GrpcMethod, request: &Vec<u8>, verbose: bool, ) -> Result<UniquePtr<ClientResult>, String>144*cf78ab8cSAndroid Build Coastguard Worker fn continuous_perform_command(
145*cf78ab8cSAndroid Build Coastguard Worker     command: &args::Command,
146*cf78ab8cSAndroid Build Coastguard Worker     client: &cxx::UniquePtr<FrontendClient>,
147*cf78ab8cSAndroid Build Coastguard Worker     grpc_method: GrpcMethod,
148*cf78ab8cSAndroid Build Coastguard Worker     request: &Vec<u8>,
149*cf78ab8cSAndroid Build Coastguard Worker     verbose: bool,
150*cf78ab8cSAndroid Build Coastguard Worker ) -> Result<UniquePtr<ClientResult>, String> {
151*cf78ab8cSAndroid Build Coastguard Worker     loop {
152*cf78ab8cSAndroid Build Coastguard Worker         process_result(command, client.send_grpc(&grpc_method, request), verbose)?;
153*cf78ab8cSAndroid Build Coastguard Worker         std::thread::sleep(std::time::Duration::from_secs(1));
154*cf78ab8cSAndroid Build Coastguard Worker     }
155*cf78ab8cSAndroid Build Coastguard Worker }
156*cf78ab8cSAndroid Build Coastguard Worker /// Check and handle the gRPC call result
process_result( command: &args::Command, result: UniquePtr<ClientResult>, verbose: bool, ) -> Result<(), String>157*cf78ab8cSAndroid Build Coastguard Worker fn process_result(
158*cf78ab8cSAndroid Build Coastguard Worker     command: &args::Command,
159*cf78ab8cSAndroid Build Coastguard Worker     result: UniquePtr<ClientResult>,
160*cf78ab8cSAndroid Build Coastguard Worker     verbose: bool,
161*cf78ab8cSAndroid Build Coastguard Worker ) -> Result<(), String> {
162*cf78ab8cSAndroid Build Coastguard Worker     if result.is_ok() {
163*cf78ab8cSAndroid Build Coastguard Worker         command.print_response(result.byte_vec().as_slice(), verbose);
164*cf78ab8cSAndroid Build Coastguard Worker     } else {
165*cf78ab8cSAndroid Build Coastguard Worker         return Err(format!("Grpc call error: {}", result.err()));
166*cf78ab8cSAndroid Build Coastguard Worker     }
167*cf78ab8cSAndroid Build Coastguard Worker     Ok(())
168*cf78ab8cSAndroid Build Coastguard Worker }
169*cf78ab8cSAndroid Build Coastguard Worker #[no_mangle]
170*cf78ab8cSAndroid Build Coastguard Worker /// main Rust netsim CLI function to be called by C wrapper netsim.cc
rust_main()171*cf78ab8cSAndroid Build Coastguard Worker pub extern "C" fn rust_main() {
172*cf78ab8cSAndroid Build Coastguard Worker     let mut args = NetsimArgs::parse();
173*cf78ab8cSAndroid Build Coastguard Worker     netsim_logger::init("netsim", args.verbose);
174*cf78ab8cSAndroid Build Coastguard Worker     if matches!(args.command, args::Command::Gui) {
175*cf78ab8cSAndroid Build Coastguard Worker         println!("Opening netsim web UI on default web browser");
176*cf78ab8cSAndroid Build Coastguard Worker         browser::open("http://localhost:7681/");
177*cf78ab8cSAndroid Build Coastguard Worker         return;
178*cf78ab8cSAndroid Build Coastguard Worker     } else if matches!(args.command, args::Command::Artifact) {
179*cf78ab8cSAndroid Build Coastguard Worker         let artifact_dir = netsim_common::system::netsimd_temp_dir();
180*cf78ab8cSAndroid Build Coastguard Worker         println!("netsim artifact directory: {}", artifact_dir.display());
181*cf78ab8cSAndroid Build Coastguard Worker         browser::open(artifact_dir);
182*cf78ab8cSAndroid Build Coastguard Worker         return;
183*cf78ab8cSAndroid Build Coastguard Worker     } else if matches!(args.command, args::Command::Bumble) {
184*cf78ab8cSAndroid Build Coastguard Worker         println!("Opening Bumble Hive on default web browser");
185*cf78ab8cSAndroid Build Coastguard Worker         browser::open("https://google.github.io/bumble/hive/index.html");
186*cf78ab8cSAndroid Build Coastguard Worker         return;
187*cf78ab8cSAndroid Build Coastguard Worker     }
188*cf78ab8cSAndroid Build Coastguard Worker     let grpc_method = args.command.grpc_method();
189*cf78ab8cSAndroid Build Coastguard Worker     let server = match (args.vsock, args.port) {
190*cf78ab8cSAndroid Build Coastguard Worker         (Some(vsock), _) => format!("vsock:{vsock}"),
191*cf78ab8cSAndroid Build Coastguard Worker         (_, Some(port)) => format!("localhost:{port}"),
192*cf78ab8cSAndroid Build Coastguard Worker         _ => get_server_address(get_instance(args.instance)).unwrap_or_default(),
193*cf78ab8cSAndroid Build Coastguard Worker     };
194*cf78ab8cSAndroid Build Coastguard Worker     let_cxx_string!(server = server);
195*cf78ab8cSAndroid Build Coastguard Worker     let client = new_frontend_client(&server);
196*cf78ab8cSAndroid Build Coastguard Worker     if client.is_null() {
197*cf78ab8cSAndroid Build Coastguard Worker         if !server.is_empty() {
198*cf78ab8cSAndroid Build Coastguard Worker             error!("Unable to create frontend client. Please ensure netsimd is running and listening on {server:?}.");
199*cf78ab8cSAndroid Build Coastguard Worker         } else {
200*cf78ab8cSAndroid Build Coastguard Worker             error!("Unable to create frontend client. Please ensure netsimd is running.");
201*cf78ab8cSAndroid Build Coastguard Worker         }
202*cf78ab8cSAndroid Build Coastguard Worker         return;
203*cf78ab8cSAndroid Build Coastguard Worker     }
204*cf78ab8cSAndroid Build Coastguard Worker     if let Err(e) = perform_command(&mut args.command, client, grpc_method, args.verbose) {
205*cf78ab8cSAndroid Build Coastguard Worker         error!("{e}");
206*cf78ab8cSAndroid Build Coastguard Worker     }
207*cf78ab8cSAndroid Build Coastguard Worker }
208*cf78ab8cSAndroid Build Coastguard Worker 
209*cf78ab8cSAndroid Build Coastguard Worker #[cfg(test)]
210*cf78ab8cSAndroid Build Coastguard Worker mod tests {
211*cf78ab8cSAndroid Build Coastguard Worker     use crate::args::BeaconRemove;
212*cf78ab8cSAndroid Build Coastguard Worker     use netsim_proto::{
213*cf78ab8cSAndroid Build Coastguard Worker         frontend::ListDeviceResponse,
214*cf78ab8cSAndroid Build Coastguard Worker         model::{Chip as ChipProto, Device as DeviceProto},
215*cf78ab8cSAndroid Build Coastguard Worker     };
216*cf78ab8cSAndroid Build Coastguard Worker     use protobuf::Message;
217*cf78ab8cSAndroid Build Coastguard Worker 
218*cf78ab8cSAndroid Build Coastguard Worker     use crate::find_id_for_remove;
219*cf78ab8cSAndroid Build Coastguard Worker 
220*cf78ab8cSAndroid Build Coastguard Worker     #[test]
test_remove_device()221*cf78ab8cSAndroid Build Coastguard Worker     fn test_remove_device() {
222*cf78ab8cSAndroid Build Coastguard Worker         let device_name = String::from("a-device");
223*cf78ab8cSAndroid Build Coastguard Worker         let chip_id = 7;
224*cf78ab8cSAndroid Build Coastguard Worker 
225*cf78ab8cSAndroid Build Coastguard Worker         let cmd = &BeaconRemove { device_name: device_name.clone(), chip_name: None };
226*cf78ab8cSAndroid Build Coastguard Worker 
227*cf78ab8cSAndroid Build Coastguard Worker         let response = ListDeviceResponse {
228*cf78ab8cSAndroid Build Coastguard Worker             devices: vec![DeviceProto {
229*cf78ab8cSAndroid Build Coastguard Worker                 id: 0,
230*cf78ab8cSAndroid Build Coastguard Worker                 name: device_name,
231*cf78ab8cSAndroid Build Coastguard Worker                 chips: vec![ChipProto { id: chip_id, ..Default::default() }],
232*cf78ab8cSAndroid Build Coastguard Worker                 ..Default::default()
233*cf78ab8cSAndroid Build Coastguard Worker             }],
234*cf78ab8cSAndroid Build Coastguard Worker             ..Default::default()
235*cf78ab8cSAndroid Build Coastguard Worker         };
236*cf78ab8cSAndroid Build Coastguard Worker 
237*cf78ab8cSAndroid Build Coastguard Worker         let id = find_id_for_remove(response.write_to_bytes().unwrap().as_slice(), cmd);
238*cf78ab8cSAndroid Build Coastguard Worker         assert!(id.is_ok(), "{}", id.unwrap_err());
239*cf78ab8cSAndroid Build Coastguard Worker         let id = id.unwrap();
240*cf78ab8cSAndroid Build Coastguard Worker 
241*cf78ab8cSAndroid Build Coastguard Worker         assert_eq!(chip_id, id);
242*cf78ab8cSAndroid Build Coastguard Worker     }
243*cf78ab8cSAndroid Build Coastguard Worker 
244*cf78ab8cSAndroid Build Coastguard Worker     #[test]
test_remove_chip()245*cf78ab8cSAndroid Build Coastguard Worker     fn test_remove_chip() {
246*cf78ab8cSAndroid Build Coastguard Worker         let device_name = String::from("a-device");
247*cf78ab8cSAndroid Build Coastguard Worker         let chip_name = String::from("should-be-deleted");
248*cf78ab8cSAndroid Build Coastguard Worker         let device_id = 4;
249*cf78ab8cSAndroid Build Coastguard Worker         let chip_id = 2;
250*cf78ab8cSAndroid Build Coastguard Worker 
251*cf78ab8cSAndroid Build Coastguard Worker         let cmd =
252*cf78ab8cSAndroid Build Coastguard Worker             &BeaconRemove { device_name: device_name.clone(), chip_name: Some(chip_name.clone()) };
253*cf78ab8cSAndroid Build Coastguard Worker 
254*cf78ab8cSAndroid Build Coastguard Worker         let response = ListDeviceResponse {
255*cf78ab8cSAndroid Build Coastguard Worker             devices: vec![DeviceProto {
256*cf78ab8cSAndroid Build Coastguard Worker                 id: device_id,
257*cf78ab8cSAndroid Build Coastguard Worker                 name: device_name,
258*cf78ab8cSAndroid Build Coastguard Worker                 chips: vec![
259*cf78ab8cSAndroid Build Coastguard Worker                     ChipProto { id: chip_id, name: chip_name, ..Default::default() },
260*cf78ab8cSAndroid Build Coastguard Worker                     ChipProto {
261*cf78ab8cSAndroid Build Coastguard Worker                         id: chip_id + 1,
262*cf78ab8cSAndroid Build Coastguard Worker                         name: String::from("shouldnt-be-deleted"),
263*cf78ab8cSAndroid Build Coastguard Worker                         ..Default::default()
264*cf78ab8cSAndroid Build Coastguard Worker                     },
265*cf78ab8cSAndroid Build Coastguard Worker                 ],
266*cf78ab8cSAndroid Build Coastguard Worker                 ..Default::default()
267*cf78ab8cSAndroid Build Coastguard Worker             }],
268*cf78ab8cSAndroid Build Coastguard Worker             ..Default::default()
269*cf78ab8cSAndroid Build Coastguard Worker         };
270*cf78ab8cSAndroid Build Coastguard Worker 
271*cf78ab8cSAndroid Build Coastguard Worker         let id = find_id_for_remove(response.write_to_bytes().unwrap().as_slice(), cmd);
272*cf78ab8cSAndroid Build Coastguard Worker         assert!(id.is_ok(), "{}", id.unwrap_err());
273*cf78ab8cSAndroid Build Coastguard Worker         let id = id.unwrap();
274*cf78ab8cSAndroid Build Coastguard Worker 
275*cf78ab8cSAndroid Build Coastguard Worker         assert_eq!(chip_id, id);
276*cf78ab8cSAndroid Build Coastguard Worker     }
277*cf78ab8cSAndroid Build Coastguard Worker 
278*cf78ab8cSAndroid Build Coastguard Worker     #[test]
test_remove_multiple_chips_fails()279*cf78ab8cSAndroid Build Coastguard Worker     fn test_remove_multiple_chips_fails() {
280*cf78ab8cSAndroid Build Coastguard Worker         let device_name = String::from("a-device");
281*cf78ab8cSAndroid Build Coastguard Worker         let device_id = 3;
282*cf78ab8cSAndroid Build Coastguard Worker 
283*cf78ab8cSAndroid Build Coastguard Worker         let cmd = &BeaconRemove { device_name: device_name.clone(), chip_name: None };
284*cf78ab8cSAndroid Build Coastguard Worker 
285*cf78ab8cSAndroid Build Coastguard Worker         let response = ListDeviceResponse {
286*cf78ab8cSAndroid Build Coastguard Worker             devices: vec![DeviceProto {
287*cf78ab8cSAndroid Build Coastguard Worker                 id: device_id,
288*cf78ab8cSAndroid Build Coastguard Worker                 name: device_name,
289*cf78ab8cSAndroid Build Coastguard Worker                 chips: vec![
290*cf78ab8cSAndroid Build Coastguard Worker                     ChipProto { id: 1, name: String::from("chip-1"), ..Default::default() },
291*cf78ab8cSAndroid Build Coastguard Worker                     ChipProto { id: 2, name: String::from("chip-2"), ..Default::default() },
292*cf78ab8cSAndroid Build Coastguard Worker                 ],
293*cf78ab8cSAndroid Build Coastguard Worker                 ..Default::default()
294*cf78ab8cSAndroid Build Coastguard Worker             }],
295*cf78ab8cSAndroid Build Coastguard Worker             ..Default::default()
296*cf78ab8cSAndroid Build Coastguard Worker         };
297*cf78ab8cSAndroid Build Coastguard Worker 
298*cf78ab8cSAndroid Build Coastguard Worker         let id = find_id_for_remove(response.write_to_bytes().unwrap().as_slice(), cmd);
299*cf78ab8cSAndroid Build Coastguard Worker         assert!(id.is_err());
300*cf78ab8cSAndroid Build Coastguard Worker     }
301*cf78ab8cSAndroid Build Coastguard Worker 
302*cf78ab8cSAndroid Build Coastguard Worker     #[test]
test_remove_nonexistent_chip_fails()303*cf78ab8cSAndroid Build Coastguard Worker     fn test_remove_nonexistent_chip_fails() {
304*cf78ab8cSAndroid Build Coastguard Worker         let device_name = String::from("a-device");
305*cf78ab8cSAndroid Build Coastguard Worker         let device_id = 1;
306*cf78ab8cSAndroid Build Coastguard Worker 
307*cf78ab8cSAndroid Build Coastguard Worker         let cmd = &BeaconRemove {
308*cf78ab8cSAndroid Build Coastguard Worker             device_name: device_name.clone(),
309*cf78ab8cSAndroid Build Coastguard Worker             chip_name: Some(String::from("nonexistent-chip")),
310*cf78ab8cSAndroid Build Coastguard Worker         };
311*cf78ab8cSAndroid Build Coastguard Worker 
312*cf78ab8cSAndroid Build Coastguard Worker         let response = ListDeviceResponse {
313*cf78ab8cSAndroid Build Coastguard Worker             devices: vec![DeviceProto {
314*cf78ab8cSAndroid Build Coastguard Worker                 id: device_id,
315*cf78ab8cSAndroid Build Coastguard Worker                 name: device_name,
316*cf78ab8cSAndroid Build Coastguard Worker                 chips: vec![ChipProto {
317*cf78ab8cSAndroid Build Coastguard Worker                     id: 1,
318*cf78ab8cSAndroid Build Coastguard Worker                     name: String::from("this-chip-exists"),
319*cf78ab8cSAndroid Build Coastguard Worker                     ..Default::default()
320*cf78ab8cSAndroid Build Coastguard Worker                 }],
321*cf78ab8cSAndroid Build Coastguard Worker                 ..Default::default()
322*cf78ab8cSAndroid Build Coastguard Worker             }],
323*cf78ab8cSAndroid Build Coastguard Worker             ..Default::default()
324*cf78ab8cSAndroid Build Coastguard Worker         };
325*cf78ab8cSAndroid Build Coastguard Worker 
326*cf78ab8cSAndroid Build Coastguard Worker         let id = find_id_for_remove(response.write_to_bytes().unwrap().as_slice(), cmd);
327*cf78ab8cSAndroid Build Coastguard Worker         assert!(id.is_err());
328*cf78ab8cSAndroid Build Coastguard Worker     }
329*cf78ab8cSAndroid Build Coastguard Worker }
330