• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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