// Copyright 2023 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 crate::bluetooth::advertise_settings as ble_advertise_settings; use crate::captures::captures_handler::clear_pcap_files; use crate::config::{set_dev, set_disable_wifi_p2p, set_pcap}; use crate::ffi::ffi_transport::{run_grpc_server_cxx, GrpcServer}; use crate::http_server::server::run_http_server; use crate::transport::socket::run_socket_transport; use crate::wireless; use cxx::UniquePtr; use log::{error, info, warn}; use netsim_common::util::ini_file::IniFile; use netsim_common::util::os_utils::get_netsim_ini_filepath; use netsim_common::util::zip_artifact::remove_zip_files; use std::env; use std::time::Duration; /// Module to control startup, run, and cleanup netsimd services. pub struct ServiceParams { fd_startup_str: String, no_cli_ui: bool, no_web_ui: bool, pcap: bool, hci_port: u16, instance_num: u16, dev: bool, disable_wifi_p2p: bool, vsock: u16, rust_grpc: bool, } impl ServiceParams { #[allow(clippy::too_many_arguments)] pub fn new( fd_startup_str: String, no_cli_ui: bool, no_web_ui: bool, pcap: bool, hci_port: u16, instance_num: u16, dev: bool, disable_wifi_p2p: bool, vsock: u16, rust_grpc: bool, ) -> Self { ServiceParams { fd_startup_str, no_cli_ui, no_web_ui, pcap, hci_port, instance_num, dev, disable_wifi_p2p, vsock, rust_grpc, } } } pub struct Service { // netsimd states, like device resource. service_params: ServiceParams, // grpc server grpc_server: UniquePtr<GrpcServer>, rust_grpc_server: Option<grpcio::Server>, } impl Service { /// # Safety /// /// The file descriptors in `service_params.fd_startup_str` must be valid and open, and must /// remain so for as long as the `Service` exists. pub unsafe fn new(service_params: ServiceParams) -> Service { Service { service_params, grpc_server: UniquePtr::null(), rust_grpc_server: None } } /// Sets up the states for netsimd. pub fn set_up(&self) { // Clear all zip files match remove_zip_files() { Ok(()) => info!("netsim generated zip files in temp directory has been removed."), Err(err) => error!("{err:?}"), } // Clear all pcap files if clear_pcap_files() { info!("netsim generated pcap files in temp directory has been removed."); } set_pcap(self.service_params.pcap); set_dev(self.service_params.dev); set_disable_wifi_p2p(self.service_params.disable_wifi_p2p); } /// Runs netsim gRPC server fn run_grpc_server(&mut self) -> Option<u32> { // If NETSIM_GRPC_PORT is set, use the fixed port for grpc server. let mut netsim_grpc_port = env::var("NETSIM_GRPC_PORT").map(|val| val.parse::<u32>().unwrap_or(0)).unwrap_or(0); if self.service_params.rust_grpc { // Run netsim gRPC server let (server, port) = crate::grpc_server::server::start(netsim_grpc_port); self.rust_grpc_server = Some(server); netsim_grpc_port = port.into(); } else { let grpc_server = run_grpc_server_cxx( netsim_grpc_port, self.service_params.no_cli_ui, self.service_params.vsock, ); match grpc_server.is_null() { true => return None, false => { self.grpc_server = grpc_server; netsim_grpc_port = self.grpc_server.get_grpc_port(); } } } Some(netsim_grpc_port) } /// Runs netsim web server fn run_web_server(&self) -> Option<u16> { // If NETSIM_NO_WEB_SERVER is set, don't start http server. let no_web_server = env::var("NETSIM_NO_WEB_SERVER").is_ok_and(|v| v == "1"); match !no_web_server && !self.service_params.no_web_ui { true => Some(run_http_server(self.service_params.instance_num)), false => None, } } /// Write ports to netsim.ini file fn write_ports_to_ini(&self, grpc_port: u32, web_port: Option<u16>) { let filepath = get_netsim_ini_filepath(self.service_params.instance_num); let mut ini_file = IniFile::new(filepath); if let Some(num) = web_port { ini_file.insert("web.port", &num.to_string()); } ini_file.insert("grpc.port", &grpc_port.to_string()); if let Err(err) = ini_file.write() { error!("{err:?}"); } } /// Runs the netsimd services. #[allow(unused_unsafe)] pub fn run(&mut self) { if !self.service_params.fd_startup_str.is_empty() { // SAFETY: When the `Service` was constructed by `Service::new` the caller guaranteed // that the file descriptors in `service_params.fd_startup_str` would remain valid and // open. unsafe { use crate::transport::fd::run_fd_transport; run_fd_transport(&self.service_params.fd_startup_str); } } let grpc_port = match self.run_grpc_server() { Some(port) => port, None => { error!("Failed to run netsimd because unable to start grpc server"); return; } }; // Run frontend web server let web_port = self.run_web_server(); // Write the port numbers to ini file self.write_ports_to_ini(grpc_port, web_port); // Run the socket server. run_socket_transport(self.service_params.hci_port); } /// Shut down the netsimd services pub fn shut_down(&mut self) { // TODO: shutdown other services in Rust if !self.grpc_server.is_null() { self.grpc_server.shut_down(); } self.rust_grpc_server.as_mut().map(|server| server.shutdown()); wireless::bluetooth::bluetooth_stop(); wireless::wifi::wifi_stop(); } } /// Constructing test beacons for dev mode pub fn new_test_beacon(idx: u32, interval: u64) { use crate::devices::devices_handler::create_device; use netsim_proto::common::ChipKind; use netsim_proto::frontend::CreateDeviceRequest; use netsim_proto::model::chip::ble_beacon::{ AdvertiseData as AdvertiseDataProto, AdvertiseSettings as AdvertiseSettingsProto, }; use netsim_proto::model::chip_create::{ BleBeaconCreate as BleBeaconCreateProto, Chip as ChipProto, }; use netsim_proto::model::ChipCreate as ChipCreateProto; use netsim_proto::model::DeviceCreate as DeviceCreateProto; use protobuf::MessageField; use protobuf_json_mapping::print_to_string; let beacon_proto = BleBeaconCreateProto { address: format!("be:ac:01:be:ef:{:02x}", idx), settings: MessageField::some(AdvertiseSettingsProto { interval: Some( ble_advertise_settings::AdvertiseMode::new(Duration::from_millis(interval)) .try_into() .unwrap(), ), scannable: true, ..Default::default() }), adv_data: MessageField::some(AdvertiseDataProto { include_device_name: true, ..Default::default() }), scan_response: MessageField::some(AdvertiseDataProto { manufacturer_data: vec![1u8, 2, 3, 4], ..Default::default() }), ..Default::default() }; let chip_proto = ChipCreateProto { name: format!("gDevice-bt-beacon-chip-{idx}"), kind: ChipKind::BLUETOOTH_BEACON.into(), chip: Some(ChipProto::BleBeacon(beacon_proto)), ..Default::default() }; let device_proto = DeviceCreateProto { name: format!("gDevice-beacon-{idx}"), chips: vec![chip_proto], ..Default::default() }; let request = CreateDeviceRequest { device: MessageField::some(device_proto), ..Default::default() }; if let Err(err) = create_device(&print_to_string(&request).unwrap()) { warn!("Failed to create beacon device {idx}: {err}"); } }