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