1 // Copyright 2023 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 // Device.rs 16 17 use crate::devices::chip; 18 use crate::devices::chip::Chip; 19 use crate::devices::chip::ChipIdentifier; 20 use crate::devices::devices_handler::PoseManager; 21 use crate::wireless::WirelessAdaptorImpl; 22 use netsim_proto::common::ChipKind as ProtoChipKind; 23 use netsim_proto::frontend::patch_device_request::PatchDeviceFields as ProtoPatchDeviceFields; 24 use netsim_proto::model::Device as ProtoDevice; 25 use netsim_proto::stats::NetsimRadioStats as ProtoRadioStats; 26 use std::collections::BTreeMap; 27 use std::fmt; 28 use std::sync::atomic::{AtomicBool, Ordering}; 29 use std::sync::{Arc, RwLock}; 30 31 #[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] 32 pub struct DeviceIdentifier(pub u32); 33 34 impl fmt::Display for DeviceIdentifier { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 write!(f, "{}", self.0) 37 } 38 } 39 40 impl fmt::Debug for DeviceIdentifier { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 write!(f, "{}", self.0) 43 } 44 } 45 46 pub struct Device { 47 pub id: DeviceIdentifier, 48 pub guid: String, 49 pub name: String, 50 pub visible: AtomicBool, 51 pub chips: RwLock<BTreeMap<ChipIdentifier, Arc<Chip>>>, 52 pub builtin: bool, 53 } 54 impl Device { new( id: DeviceIdentifier, guid: impl Into<String>, name: impl Into<String>, builtin: bool, ) -> Self55 pub fn new( 56 id: DeviceIdentifier, 57 guid: impl Into<String>, 58 name: impl Into<String>, 59 builtin: bool, 60 ) -> Self { 61 Device { 62 id, 63 guid: guid.into(), 64 name: name.into(), 65 visible: AtomicBool::new(true), 66 chips: RwLock::new(BTreeMap::new()), 67 builtin, 68 } 69 } 70 } 71 72 #[derive(Debug, Clone)] 73 pub struct AddChipResult { 74 pub device_id: DeviceIdentifier, 75 pub chip_id: ChipIdentifier, 76 } 77 78 impl Device { get(&self, pose_manager: Arc<PoseManager>) -> Result<ProtoDevice, String>79 pub fn get(&self, pose_manager: Arc<PoseManager>) -> Result<ProtoDevice, String> { 80 let mut device = ProtoDevice::new(); 81 device.id = self.id.0; 82 device.name.clone_from(&self.name); 83 device.visible = Some(self.visible.load(Ordering::SeqCst)); 84 device.position = protobuf::MessageField::from(pose_manager.get_position(&self.id)); 85 device.orientation = protobuf::MessageField::from(pose_manager.get_orientation(&self.id)); 86 for chip in self.chips.read().unwrap().values() { 87 device.chips.push(chip.get()?); 88 } 89 Ok(device) 90 } 91 92 /// Patch a device and its chips. patch( &self, patch: &ProtoPatchDeviceFields, pose_manager: Arc<PoseManager>, ) -> Result<(), String>93 pub fn patch( 94 &self, 95 patch: &ProtoPatchDeviceFields, 96 pose_manager: Arc<PoseManager>, 97 ) -> Result<(), String> { 98 if patch.visible.is_some() { 99 self.visible.store(patch.visible.unwrap(), Ordering::SeqCst); 100 } 101 if patch.position.is_some() { 102 pose_manager.set_position(self.id, &patch.position); 103 } 104 if patch.orientation.is_some() { 105 pose_manager.set_orientation(self.id, &patch.orientation); 106 } 107 // iterate over patched ProtoChip entries and patch matching chip 108 for patch_chip in patch.chips.iter() { 109 let mut patch_chip_kind = patch_chip.kind.enum_value_or_default(); 110 // Check if chip is given when kind is not given. 111 // TODO: Fix patch device request body in CLI to include ChipKind, and remove if block below. 112 if patch_chip_kind == ProtoChipKind::UNSPECIFIED { 113 if patch_chip.has_bt() { 114 patch_chip_kind = ProtoChipKind::BLUETOOTH; 115 } else if patch_chip.has_ble_beacon() { 116 patch_chip_kind = ProtoChipKind::BLUETOOTH_BEACON; 117 } else if patch_chip.has_wifi() { 118 patch_chip_kind = ProtoChipKind::WIFI; 119 } else if patch_chip.has_uwb() { 120 patch_chip_kind = ProtoChipKind::UWB; 121 } else { 122 break; 123 } 124 } 125 let patch_chip_name = &patch_chip.name; 126 // Find the matching chip and patch the proto chip 127 let target = self.match_target_chip(patch_chip_kind, patch_chip_name)?; 128 match target { 129 Some(chip) => chip.patch(patch_chip)?, 130 None => { 131 return Err(format!( 132 "Chip {} not found in device {}", 133 patch_chip_name, self.name 134 )) 135 } 136 } 137 } 138 Ok(()) 139 } 140 match_target_chip( &self, patch_chip_kind: ProtoChipKind, patch_chip_name: &str, ) -> Result<Option<Arc<Chip>>, String>141 fn match_target_chip( 142 &self, 143 patch_chip_kind: ProtoChipKind, 144 patch_chip_name: &str, 145 ) -> Result<Option<Arc<Chip>>, String> { 146 let mut multiple_matches = false; 147 let mut target: Option<Arc<Chip>> = None; 148 for chip in self.chips.read().unwrap().values() { 149 // Check for specified chip kind and matching chip name 150 if chip.kind == patch_chip_kind && chip.name.contains(patch_chip_name) { 151 // Check for exact match 152 if chip.name == patch_chip_name { 153 multiple_matches = false; 154 target = Some(Arc::clone(chip)); 155 break; 156 } 157 // Check for ambiguous match 158 if target.is_none() { 159 target = Some(Arc::clone(chip)); 160 } else { 161 // Return if no chip name is supplied but multiple chips of specified kind exist 162 if patch_chip_name.is_empty() { 163 return Err(format!( 164 "No chip name is supplied but multiple chips of chip kind {:?} exist.", 165 chip.kind 166 )); 167 } 168 // Multiple matches were found - continue to look for possible exact match 169 multiple_matches = true; 170 } 171 } 172 } 173 if multiple_matches { 174 return Err(format!( 175 "Multiple ambiguous matches were found with chip name {}", 176 patch_chip_name 177 )); 178 } 179 Ok(target) 180 } 181 182 /// Remove a chip from a device. remove_chip(&self, chip_id: &ChipIdentifier) -> Result<Vec<ProtoRadioStats>, String>183 pub fn remove_chip(&self, chip_id: &ChipIdentifier) -> Result<Vec<ProtoRadioStats>, String> { 184 let radio_stats = self 185 .chips 186 .read() 187 .unwrap() 188 .get(chip_id) 189 .ok_or(format!("RemoveChip chip id {chip_id} not found"))? 190 .get_stats(); 191 // Chip and emulated chip will be dropped 192 self.chips.write().unwrap().remove(chip_id); 193 chip::remove_chip(chip_id); 194 Ok(radio_stats) 195 } 196 add_chip( &mut self, chip_create_params: &chip::CreateParams, chip_id: ChipIdentifier, wireless_adaptor: WirelessAdaptorImpl, ) -> Result<(DeviceIdentifier, ChipIdentifier), String>197 pub fn add_chip( 198 &mut self, 199 chip_create_params: &chip::CreateParams, 200 chip_id: ChipIdentifier, 201 wireless_adaptor: WirelessAdaptorImpl, 202 ) -> Result<(DeviceIdentifier, ChipIdentifier), String> { 203 for chip in self.chips.read().unwrap().values() { 204 if chip.kind == chip_create_params.kind 205 && chip_create_params.name.clone().is_some_and(|name| name == chip.name) 206 { 207 return Err(format!("Device::AddChip - duplicate at id {}, skipping.", chip.id)); 208 } 209 } 210 let device_id = self.id; 211 let chip = chip::new(chip_id, device_id, &self.name, chip_create_params, wireless_adaptor)?; 212 self.chips.write().unwrap().insert(chip_id, chip); 213 214 Ok((device_id, chip_id)) 215 } 216 217 /// Reset a device to its default state. reset(&self) -> Result<(), String>218 pub fn reset(&self) -> Result<(), String> { 219 self.visible.store(true, Ordering::SeqCst); 220 for chip in self.chips.read().unwrap().values() { 221 chip.reset()?; 222 } 223 Ok(()) 224 } 225 } 226 227 #[cfg(test)] 228 mod tests { 229 use super::*; 230 use crate::wireless::mocked; 231 use std::sync::atomic::{AtomicU32, Ordering}; 232 static PATCH_CHIP_KIND: ProtoChipKind = ProtoChipKind::BLUETOOTH; 233 static TEST_DEVICE_NAME: &str = "test_device"; 234 static TEST_CHIP_NAME_1: &str = "test-bt-chip-1"; 235 static TEST_CHIP_NAME_2: &str = "test-bt-chip-2"; 236 static IDS: AtomicU32 = AtomicU32::new(1000); 237 create_test_device() -> Result<Device, String>238 fn create_test_device() -> Result<Device, String> { 239 let mut device = Device::new(DeviceIdentifier(0), "0", TEST_DEVICE_NAME, false); 240 let chip_id_1 = ChipIdentifier(IDS.fetch_add(1, Ordering::SeqCst)); 241 let chip_id_2 = ChipIdentifier(IDS.fetch_add(1, Ordering::SeqCst)); 242 device.add_chip( 243 &chip::CreateParams { 244 kind: ProtoChipKind::BLUETOOTH, 245 address: "".to_string(), 246 name: Some(TEST_CHIP_NAME_1.to_string()), 247 manufacturer: "test_manufacturer".to_string(), 248 product_name: "test_product_name".to_string(), 249 }, 250 chip_id_1, 251 mocked::new(&mocked::CreateParams { chip_kind: ProtoChipKind::UNSPECIFIED }, chip_id_1), 252 )?; 253 device.add_chip( 254 &chip::CreateParams { 255 kind: ProtoChipKind::BLUETOOTH, 256 address: "".to_string(), 257 name: Some(TEST_CHIP_NAME_2.to_string()), 258 manufacturer: "test_manufacturer".to_string(), 259 product_name: "test_product_name".to_string(), 260 }, 261 chip_id_2, 262 mocked::new(&mocked::CreateParams { chip_kind: ProtoChipKind::UNSPECIFIED }, chip_id_1), 263 )?; 264 Ok(device) 265 } 266 267 #[ignore = "TODO: include thread_id in names and ids"] 268 #[test] test_exact_target_match()269 fn test_exact_target_match() { 270 let device = create_test_device().unwrap(); 271 let result = device.match_target_chip(PATCH_CHIP_KIND, TEST_CHIP_NAME_1); 272 assert!(result.is_ok()); 273 let target = result.unwrap(); 274 assert!(target.is_some()); 275 assert_eq!(target.unwrap().name, TEST_CHIP_NAME_1); 276 assert_eq!(device.name, TEST_DEVICE_NAME); 277 } 278 279 #[ignore = "TODO: include thread_id in names and ids"] 280 #[test] test_substring_target_match()281 fn test_substring_target_match() { 282 let device = create_test_device().unwrap(); 283 let result = device.match_target_chip(PATCH_CHIP_KIND, "chip-1"); 284 assert!(result.is_ok()); 285 let target = result.unwrap(); 286 assert!(target.is_some()); 287 assert_eq!(target.unwrap().name, TEST_CHIP_NAME_1); 288 assert_eq!(device.name, TEST_DEVICE_NAME); 289 } 290 291 #[ignore = "TODO: include thread_id in names and ids"] 292 #[test] test_ambiguous_target_match()293 fn test_ambiguous_target_match() { 294 let device = create_test_device().unwrap(); 295 let result = device.match_target_chip(PATCH_CHIP_KIND, "chip"); 296 assert!(result.is_err()); 297 assert_eq!( 298 result.err(), 299 Some("Multiple ambiguous matches were found with chip name chip".to_string()) 300 ); 301 } 302 303 #[ignore = "TODO: include thread_id in names and ids"] 304 #[test] test_ambiguous_empty_target_match()305 fn test_ambiguous_empty_target_match() { 306 let device = create_test_device().unwrap(); 307 let result = device.match_target_chip(PATCH_CHIP_KIND, ""); 308 assert!(result.is_err()); 309 assert_eq!( 310 result.err(), 311 Some(format!( 312 "No chip name is supplied but multiple chips of chip kind {:?} exist.", 313 PATCH_CHIP_KIND 314 )) 315 ); 316 } 317 318 #[ignore = "TODO: include thread_id in names and ids"] 319 #[test] test_no_target_match()320 fn test_no_target_match() { 321 let device = create_test_device().unwrap(); 322 let invalid_chip_name = "invalid-chip"; 323 let result = device.match_target_chip(PATCH_CHIP_KIND, invalid_chip_name); 324 assert!(result.is_ok()); 325 let target = result.unwrap(); 326 assert!(target.is_none()); 327 } 328 } 329