xref: /aosp_15_r20/tools/netsim/rust/daemon/src/devices/device.rs (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
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