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 // devices_handler.rs
16 //
17 // Provides the API for the frontend and backend to interact with devices.
18 //
19 // The DeviceManager struct is a singleton for the devices collection.
20 //
21 // Additional functions are
22 // -- inactivity instant
23 // -- vending device identifiers
24
25 use super::chip;
26 use super::chip::ChipIdentifier;
27 use super::device::DeviceIdentifier;
28 use crate::devices::device::{AddChipResult, Device};
29 use crate::events::{
30 ChipAdded, ChipRemoved, DeviceAdded, DevicePatched, DeviceRemoved, Event, Events, ShutDown,
31 };
32 use crate::http_server::server_response::ResponseWritable;
33 use crate::wireless;
34 use http::Request;
35 use log::{info, warn};
36 use netsim_proto::common::ChipKind as ProtoChipKind;
37 use netsim_proto::frontend::patch_device_request::PatchDeviceFields as ProtoPatchDeviceFields;
38 use netsim_proto::frontend::CreateDeviceRequest;
39 use netsim_proto::frontend::CreateDeviceResponse;
40 use netsim_proto::frontend::DeleteChipRequest;
41 use netsim_proto::frontend::ListDeviceResponse;
42 use netsim_proto::frontend::PatchDeviceRequest;
43 use netsim_proto::frontend::SubscribeDeviceRequest;
44 use netsim_proto::model::chip_create::Chip as ProtoBuiltin;
45 use netsim_proto::model::Chip as ProtoChip;
46 use netsim_proto::model::Device as ProtoDevice;
47 use netsim_proto::model::Orientation as ProtoOrientation;
48 use netsim_proto::model::Position as ProtoPosition;
49 use netsim_proto::startup::DeviceInfo as ProtoDeviceInfo;
50 use netsim_proto::stats::{NetsimDeviceStats as ProtoDeviceStats, NetsimRadioStats};
51 use protobuf::well_known_types::timestamp::Timestamp;
52 use protobuf::MessageField;
53 use protobuf_json_mapping::merge_from_str;
54 use protobuf_json_mapping::print_to_string;
55 use protobuf_json_mapping::print_to_string_with_options;
56 use protobuf_json_mapping::PrintOptions;
57 use std::collections::{BTreeMap, HashMap};
58 use std::sync::atomic::{AtomicU32, Ordering};
59 use std::sync::mpsc::Receiver;
60 use std::sync::Arc;
61 use std::sync::OnceLock;
62 use std::sync::RwLock;
63 use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
64
65 // The amount of seconds netsimd will wait until the first device has attached.
66 static IDLE_SECS_FOR_SHUTDOWN: u64 = 15;
67
68 const INITIAL_DEVICE_ID: u32 = 1;
69 const JSON_PRINT_OPTION: PrintOptions = PrintOptions {
70 enum_values_int: false,
71 proto_field_name: false,
72 always_output_default_values: true,
73 _future_options: (),
74 };
75
76 static POSE_MANAGER: OnceLock<Arc<PoseManager>> = OnceLock::new();
77
get_pose_manager() -> Arc<PoseManager>78 fn get_pose_manager() -> Arc<PoseManager> {
79 POSE_MANAGER.get_or_init(|| Arc::new(PoseManager::new())).clone()
80 }
81
82 pub struct PoseManager {
83 positions: RwLock<HashMap<DeviceIdentifier, ProtoPosition>>,
84 orientations: RwLock<HashMap<DeviceIdentifier, ProtoOrientation>>,
85 }
86
87 impl PoseManager {
new() -> Self88 pub fn new() -> Self {
89 PoseManager {
90 positions: RwLock::new(HashMap::new()),
91 orientations: RwLock::new(HashMap::new()),
92 }
93 }
94
add(&self, device_id: DeviceIdentifier)95 pub fn add(&self, device_id: DeviceIdentifier) {
96 self.positions.write().unwrap().insert(device_id, ProtoPosition::new());
97 self.orientations.write().unwrap().insert(device_id, ProtoOrientation::new());
98 }
99
remove(&self, device_id: &DeviceIdentifier)100 pub fn remove(&self, device_id: &DeviceIdentifier) {
101 self.positions.write().unwrap().remove(device_id);
102 self.orientations.write().unwrap().remove(device_id);
103 }
104
reset(&self, device_id: DeviceIdentifier)105 pub fn reset(&self, device_id: DeviceIdentifier) {
106 self.positions.write().unwrap().insert(device_id, ProtoPosition::new());
107 self.orientations.write().unwrap().insert(device_id, ProtoOrientation::new());
108 }
109
set_position(&self, device_id: DeviceIdentifier, position: &ProtoPosition)110 pub fn set_position(&self, device_id: DeviceIdentifier, position: &ProtoPosition) {
111 self.positions.write().unwrap().insert(device_id, position.clone());
112 }
get_position(&self, device_id: &DeviceIdentifier) -> Option<ProtoPosition>113 pub fn get_position(&self, device_id: &DeviceIdentifier) -> Option<ProtoPosition> {
114 self.positions.read().unwrap().get(device_id).cloned()
115 }
set_orientation(&self, device_id: DeviceIdentifier, orientation: &ProtoOrientation)116 pub fn set_orientation(&self, device_id: DeviceIdentifier, orientation: &ProtoOrientation) {
117 self.orientations.write().unwrap().insert(device_id, orientation.clone());
118 }
get_orientation(&self, device_id: &DeviceIdentifier) -> Option<ProtoOrientation>119 pub fn get_orientation(&self, device_id: &DeviceIdentifier) -> Option<ProtoOrientation> {
120 self.orientations.read().unwrap().get(device_id).cloned()
121 }
122 }
123
124 static DEVICE_MANAGER: OnceLock<Arc<DeviceManager>> = OnceLock::new();
125
get_manager() -> Arc<DeviceManager>126 fn get_manager() -> Arc<DeviceManager> {
127 DEVICE_MANAGER.get().unwrap().clone()
128 }
129
130 // TODO: last_modified atomic
131 /// The Device resource is a singleton that manages all devices.
132 pub struct DeviceManager {
133 // BTreeMap allows ListDevice to output devices in order of identifiers.
134 devices: RwLock<BTreeMap<DeviceIdentifier, Device>>,
135 events: Arc<Events>,
136 ids: AtomicU32,
137 last_modified: RwLock<Duration>,
138 }
139
140 impl DeviceManager {
init(events: Arc<Events>) -> Arc<DeviceManager>141 pub fn init(events: Arc<Events>) -> Arc<DeviceManager> {
142 let manager = Arc::new(Self::new(events));
143 if let Err(_e) = DEVICE_MANAGER.set(manager.clone()) {
144 panic!("Error setting device manager");
145 }
146 manager
147 }
148
new(events: Arc<Events>) -> Self149 fn new(events: Arc<Events>) -> Self {
150 DeviceManager {
151 devices: RwLock::new(BTreeMap::new()),
152 events,
153 ids: AtomicU32::new(INITIAL_DEVICE_ID),
154 last_modified: RwLock::new(
155 SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"),
156 ),
157 }
158 }
159
next_id(&self) -> DeviceIdentifier160 fn next_id(&self) -> DeviceIdentifier {
161 DeviceIdentifier(self.ids.fetch_add(1, Ordering::SeqCst))
162 }
163
update_timestamp(&self)164 fn update_timestamp(&self) {
165 *self.last_modified.write().unwrap() =
166 SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
167 }
168
169 /// Get or create a device.
170 /// Returns a (device_id, device_name) pair.
get_or_create_device( &self, guid: Option<&str>, name: Option<&str>, builtin: bool, device_info: ProtoDeviceInfo, ) -> (DeviceIdentifier, String)171 fn get_or_create_device(
172 &self,
173 guid: Option<&str>,
174 name: Option<&str>,
175 builtin: bool,
176 device_info: ProtoDeviceInfo,
177 ) -> (DeviceIdentifier, String) {
178 // Hold a lock while checking and updating devices.
179 let mut guard = self.devices.write().unwrap();
180 // Check if a device with the same guid already exists and if so, return it
181 if let Some(guid) = guid {
182 if let Some(existing_device) = guard.values().find(|d| d.guid == *guid) {
183 if existing_device.builtin != builtin {
184 warn!("builtin mismatch for device {} during add_chip", existing_device.name);
185 }
186 return (existing_device.id, existing_device.name.clone());
187 }
188 }
189 // A new device needs to be created and inserted
190 let id = self.next_id();
191 let default = format!("device-{}", id);
192 let name = name.unwrap_or(&default);
193 guard.insert(id, Device::new(id, guid.unwrap_or(&default), name, builtin));
194 drop(guard);
195 // Update last modified timestamp for devices
196 self.update_timestamp();
197 let device_stats = ProtoDeviceStats {
198 device_id: Some(id.0),
199 kind: Some(device_info.kind).filter(|s| !s.is_empty()),
200 version: Some(device_info.version).filter(|s| !s.is_empty()),
201 sdk_version: Some(device_info.sdk_version).filter(|s| !s.is_empty()),
202 variant: Some(device_info.variant).filter(|s| !s.is_empty()),
203 build_id: Some(device_info.build_id).filter(|s| !s.is_empty()),
204 arch: Some(device_info.arch).filter(|s| !s.is_empty()),
205 ..Default::default()
206 };
207 let event =
208 Event::DeviceAdded(DeviceAdded { id, name: String::from(name), builtin, device_stats });
209 self.events.publish(event);
210 (id, String::from(name))
211 }
212 }
213
214 /// Returns a Result<AddChipResult, String> after adding chip to resource.
215 /// add_chip is called by the transport layer when a new chip is attached.
216 ///
217 /// The guid is a transport layer identifier for the device (host:port)
218 /// that is adding the chip.
219 ///
220 /// TODO: Replace the parameter of add_chip with a single protobuf
add_chip( device_guid: &str, device_name: &str, chip_create_params: &chip::CreateParams, wireless_create_params: &wireless::CreateParam, device_info: ProtoDeviceInfo, ) -> Result<AddChipResult, String>221 pub fn add_chip(
222 device_guid: &str,
223 device_name: &str,
224 chip_create_params: &chip::CreateParams,
225 wireless_create_params: &wireless::CreateParam,
226 device_info: ProtoDeviceInfo,
227 ) -> Result<AddChipResult, String> {
228 let chip_kind = chip_create_params.kind;
229 let manager = get_manager();
230 let (device_id, _) = manager.get_or_create_device(
231 Some(device_guid),
232 Some(device_name),
233 chip_kind == ProtoChipKind::BLUETOOTH_BEACON,
234 device_info,
235 );
236 get_pose_manager().add(device_id);
237
238 // Create
239 let chip_id = chip::next_id();
240 let wireless_chip = wireless::add_chip(wireless_create_params, chip_id);
241
242 // This is infrequent, so we can afford to do another lookup for the device.
243 let _ = manager
244 .devices
245 .write()
246 .unwrap()
247 .get_mut(&device_id)
248 .ok_or(format!("Device not found for device_id: {}", device_id))?
249 .add_chip(chip_create_params, chip_id, wireless_chip);
250
251 // Update last modified timestamp for devices
252 manager.update_timestamp();
253
254 // Update Capture resource
255 let event = Event::ChipAdded(ChipAdded {
256 chip_id,
257 chip_kind,
258 device_name: device_name.to_string(),
259 builtin: chip_kind == ProtoChipKind::BLUETOOTH_BEACON,
260 });
261 manager.events.publish(event);
262 Ok(AddChipResult { device_id, chip_id })
263 }
264
265 /// Remove a chip from a device.
266 ///
267 /// Called when the packet transport for the chip shuts down.
remove_chip(device_id: DeviceIdentifier, chip_id: ChipIdentifier) -> Result<(), String>268 pub fn remove_chip(device_id: DeviceIdentifier, chip_id: ChipIdentifier) -> Result<(), String> {
269 let manager = get_manager();
270 let mut guard = manager.devices.write().unwrap();
271 let device =
272 guard.get(&device_id).ok_or(format!("RemoveChip device id {device_id} not found"))?;
273 let radio_stats = device.remove_chip(&chip_id)?;
274
275 let mut device_id_to_remove = None;
276 if device.chips.read().unwrap().is_empty() {
277 device_id_to_remove = Some(device_id);
278 let device = guard
279 .remove(&device_id)
280 .ok_or(format!("RemoveChip device id {device_id} not found"))?;
281 manager.events.publish(Event::DeviceRemoved(DeviceRemoved {
282 id: device.id,
283 name: device.name,
284 builtin: device.builtin,
285 }));
286 }
287
288 let remaining_nonbuiltin_devices = guard.values().filter(|device| !device.builtin).count();
289 drop(guard);
290
291 if let Some(device_id) = device_id_to_remove {
292 get_pose_manager().remove(&device_id);
293 }
294
295 manager.events.publish(Event::ChipRemoved(ChipRemoved {
296 chip_id,
297 device_id,
298 remaining_nonbuiltin_devices,
299 radio_stats,
300 }));
301
302 manager.update_timestamp();
303 Ok(())
304 }
305
delete_chip(request: &DeleteChipRequest) -> Result<(), String>306 pub fn delete_chip(request: &DeleteChipRequest) -> Result<(), String> {
307 let chip_id = ChipIdentifier(request.id);
308
309 let device_id = get_manager()
310 .devices
311 .read()
312 .unwrap()
313 .iter()
314 .find(|(_, device)| device.chips.read().unwrap().contains_key(&chip_id))
315 .map(|(id, _)| *id)
316 .ok_or(format!("failed to delete chip: could not find chip with id {}", request.id))?;
317
318 remove_chip(device_id, chip_id)
319 }
320
321 /// Create a device from a CreateDeviceRequest.
322 /// Uses a default name if none is provided.
323 /// Returns an error if the device already exists.
create_device(create_device_request: &CreateDeviceRequest) -> Result<ProtoDevice, String>324 pub fn create_device(create_device_request: &CreateDeviceRequest) -> Result<ProtoDevice, String> {
325 let new_device = &create_device_request.device;
326 let manager = get_manager();
327 // Check if specified device name is already mapped.
328 if new_device.name != String::default()
329 && manager.devices.read().unwrap().values().any(|d| d.guid == new_device.name)
330 {
331 return Err(String::from("failed to create device: device already exists"));
332 }
333
334 if new_device.chips.is_empty() {
335 return Err(String::from("failed to create device: device must contain at least 1 chip"));
336 }
337 new_device.chips.iter().try_for_each(|chip| match chip.chip {
338 Some(ProtoBuiltin::BleBeacon(_)) => Ok(()),
339 Some(_) => Err(format!("failed to create device: chip {} was not a built-in", chip.name)),
340 None => Err(format!("failed to create device: chip {} was missing a radio", chip.name)),
341 })?;
342
343 let device_name = (new_device.name != String::default()).then_some(new_device.name.as_str());
344 let device_info =
345 ProtoDeviceInfo { kind: "BLUETOOTH_BEACON".to_string(), ..Default::default() };
346 let (device_id, device_name) =
347 manager.get_or_create_device(device_name, device_name, true, device_info.clone());
348
349 new_device.chips.iter().try_for_each(|chip| {
350 {
351 let chip_create_params = chip::CreateParams {
352 kind: chip.kind.enum_value_or_default(),
353 address: chip.address.clone(),
354 name: if chip.name.is_empty() { None } else { Some(chip.name.to_string()) },
355 manufacturer: chip.manufacturer.clone(),
356 product_name: chip.product_name.clone(),
357 };
358 let wireless_create_params = wireless::wireless_manager::CreateParam::BleBeacon(
359 wireless::ble_beacon::CreateParams {
360 device_name: device_name.clone(),
361 chip_proto: chip.clone(),
362 },
363 );
364
365 add_chip(
366 &device_name,
367 &device_name,
368 &chip_create_params,
369 &wireless_create_params,
370 device_info.clone(),
371 )
372 }
373 .map(|_| ())
374 })?;
375
376 let manager = get_manager();
377 let guard = manager.devices.read().unwrap();
378 let device = guard.get(&device_id).expect("could not find test bluetooth beacon device");
379 let device_proto = device.get(get_pose_manager())?;
380 Ok(device_proto)
381 }
382
383 struct ProtoChipDisplay(ProtoChip);
384
385 // Due to the low readability of debug formatter for ProtoChip, we implemented our own fmt.
386 impl std::fmt::Display for ProtoChipDisplay {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result387 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
388 let chip = &self.0;
389 if let Ok(kind) = chip.kind.enum_value() {
390 match kind {
391 ProtoChipKind::BLUETOOTH => {
392 chip.bt().low_energy.clone().map(|v| {
393 write!(
394 f,
395 "{{ id: {}, kind: BLUETOOTH_LOW_ENERGY, state: {:?} }}",
396 self.0.id, v.state
397 )
398 });
399 chip.bt().classic.clone().map(|v| {
400 write!(
401 f,
402 "{{ id: {}, kind: BLUETOOTH_CLASSIC, state: {:?} }}",
403 chip.id, v.state
404 )
405 });
406 }
407 ProtoChipKind::BLUETOOTH_BEACON => {
408 chip.ble_beacon().bt.low_energy.clone().map(|v| {
409 write!(f, "{{ id: {}, kind: BLE_BEACON, state: {:?} }}", chip.id, v.state)
410 });
411 chip.ble_beacon().bt.classic.clone().map(|v| {
412 write!(
413 f,
414 "{{ id: {}, kind: BLUETOOTH_CLASSIC_BEACON, state: {:?} }}",
415 chip.id, v.state
416 )
417 });
418 }
419 ProtoChipKind::WIFI => {
420 write!(f, "{{ id: {}, kind: WIFI, state: {:?} }}", chip.id, chip.wifi().state)?
421 }
422 ProtoChipKind::UWB => {
423 write!(f, "{{ id: {}, kind: UWB, state: {:?} }}", chip.id, chip.uwb().state)?
424 }
425 _ => (),
426 }
427 }
428 Ok(())
429 }
430 }
431
432 struct PatchDeviceFieldsDisplay(DeviceIdentifier, ProtoPatchDeviceFields);
433
434 // Due to the low readability of debug formatter for ProtoPatchDeviceFields, we implemented our own fmt.
435 impl std::fmt::Display for PatchDeviceFieldsDisplay {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result436 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
437 write!(f, "PatchDevice: ")?;
438 let mut fields = Vec::<String>::new();
439 fields.push(format!("id: {}", self.0));
440 if let Some(name) = &self.1.name {
441 fields.push(format!("name: {}", name));
442 }
443 if let Some(visible) = &self.1.visible {
444 fields.push(format!("visible: {}", visible));
445 }
446 if let Some(position) = &self.1.position.0 {
447 fields.push(format!("position: {{ {} }}", position));
448 }
449 if let Some(orientation) = &self.1.orientation.0 {
450 fields.push(format!("orientation: {{ {} }}", orientation));
451 }
452 if !self.1.chips.is_empty() {
453 let mut chip_field = Vec::<String>::new();
454 for chip in &self.1.chips {
455 chip_field.push(format!("{}", ProtoChipDisplay(chip.clone())));
456 }
457 fields.push(format!("chips: {{ {} }}", chip_field.join(", ")));
458 }
459 write!(f, "{}", fields.join(", "))
460 }
461 }
462
463 // lock the devices, find the id and call the patch function
patch_device(patch_device_request: PatchDeviceRequest) -> Result<(), String>464 pub fn patch_device(patch_device_request: PatchDeviceRequest) -> Result<(), String> {
465 let manager = get_manager();
466 let proto_device = patch_device_request
467 .device
468 .into_option()
469 .ok_or("Missing PatchDevice in PatchDeviceRequest".to_string())?;
470 match (patch_device_request.id, proto_device.name.clone()) {
471 (Some(id), _) => {
472 let id = DeviceIdentifier(id);
473 match manager.devices.read().unwrap().get(&id) {
474 Some(device) => {
475 let result = device.patch(&proto_device, get_pose_manager());
476 let name = device.name.clone();
477 if result.is_ok() {
478 // Update last modified timestamp for manager
479 manager.update_timestamp();
480
481 // Log patched fields
482 log::info!("{}", PatchDeviceFieldsDisplay(id, proto_device));
483
484 // Publish Device Patched event
485 manager.events.publish(Event::DevicePatched(DevicePatched { id, name }));
486 }
487 result
488 }
489 None => Err(format!("No such device with id {id}")),
490 }
491 }
492 (_, Some(name)) => {
493 let mut multiple_matches = false;
494 let mut target: Option<&Device> = None;
495 let devices = manager.devices.read().unwrap();
496 for device in devices.values() {
497 if device.name.contains(&name) {
498 if device.name == name {
499 let result = device.patch(&proto_device, get_pose_manager());
500 let id = device.id;
501 let name = device.name.clone();
502 if result.is_ok() {
503 // Update last modified timestamp for manager
504 manager.update_timestamp();
505
506 // Log patched fields
507 log::info!("{}", PatchDeviceFieldsDisplay(id, proto_device));
508
509 // Publish Device Patched event
510 manager
511 .events
512 .publish(Event::DevicePatched(DevicePatched { id, name }));
513 }
514 return result;
515 }
516 multiple_matches = target.is_some();
517 target = Some(device);
518 }
519 }
520 if multiple_matches {
521 return Err(format!(
522 "Multiple ambiguous matches were found with substring {}",
523 name
524 ));
525 }
526 match target {
527 Some(device) => {
528 let result = device.patch(&proto_device, get_pose_manager());
529 let id = device.id;
530 let name = device.name.clone();
531 if result.is_ok() {
532 // Update last modified timestamp for devices
533 manager.update_timestamp();
534
535 // Log patched fields
536 log::info!("{}", PatchDeviceFieldsDisplay(id, proto_device));
537
538 // Publish Device Patched event
539 manager.events.publish(Event::DevicePatched(DevicePatched { id, name }));
540 }
541 result
542 }
543 None => Err(format!("No such device with name {}", name)),
544 }
545 }
546 (_, _) => Err("Both id and name are not provided".to_string()),
547 }
548 }
549
550 // Parse from input json string to proto
patch_device_json(id_option: Option<DeviceIdentifier>, patch_json: &str) -> Result<(), String>551 fn patch_device_json(id_option: Option<DeviceIdentifier>, patch_json: &str) -> Result<(), String> {
552 let mut patch_device_request = PatchDeviceRequest::new();
553 if merge_from_str(&mut patch_device_request, patch_json).is_ok() {
554 if patch_device_request.id.is_none() {
555 patch_device_request.id = id_option.map(|id| id.0);
556 }
557 patch_device(patch_device_request)
558 } else {
559 Err(format!("Incorrect format of patch json {}", patch_json))
560 }
561 }
562
distance(a: &ProtoPosition, b: &ProtoPosition) -> f32563 fn distance(a: &ProtoPosition, b: &ProtoPosition) -> f32 {
564 ((b.x - a.x).powf(2.0) + (b.y - a.y).powf(2.0) + (b.z - a.z).powf(2.0)).sqrt()
565 }
566
567 #[allow(dead_code)]
get_distance(id: &ChipIdentifier, other_id: &ChipIdentifier) -> Result<f32, String>568 fn get_distance(id: &ChipIdentifier, other_id: &ChipIdentifier) -> Result<f32, String> {
569 let device_id = crate::devices::chip::get_chip(id)
570 .ok_or(format!("No such device with chip_id {id}"))?
571 .device_id;
572 let other_device_id = crate::devices::chip::get_chip(other_id)
573 .ok_or(format!("No such device with chip_id {other_id}"))?
574 .device_id;
575
576 let pose_manager = get_pose_manager();
577 let a = pose_manager
578 .get_position(&device_id)
579 .ok_or(format!("no position for device {device_id}"))?;
580 let b = pose_manager
581 .get_position(&other_device_id)
582 .ok_or(format!("no position for device {other_device_id}"))?;
583 Ok(distance(&a, &b))
584 }
585
586 /// A GetDistance function for Rust Device API.
587 /// The backend gRPC code will be invoking this method.
get_distance_cxx(a: u32, b: u32) -> f32588 pub fn get_distance_cxx(a: u32, b: u32) -> f32 {
589 match get_distance(&ChipIdentifier(a), &ChipIdentifier(b)) {
590 Ok(distance) => distance,
591 Err(err) => {
592 warn!("get_distance Error: {err}");
593 0.0
594 }
595 }
596 }
597
598 /// Function to obtain ProtoDevice given a ChipIdentifier
get_device(chip_id: &ChipIdentifier) -> anyhow::Result<netsim_proto::model::Device>599 pub fn get_device(chip_id: &ChipIdentifier) -> anyhow::Result<netsim_proto::model::Device> {
600 let device_id = match chip::get_chip(chip_id) {
601 Some(chip) => chip.device_id,
602 None => return Err(anyhow::anyhow!("Can't find chip for chip_id: {chip_id}")),
603 };
604 get_manager()
605 .devices
606 .read()
607 .unwrap()
608 .get(&device_id)
609 .ok_or(anyhow::anyhow!("Can't find device for device_id: {device_id}"))?
610 .get(get_pose_manager())
611 .map_err(|e| anyhow::anyhow!("{e:?}"))
612 }
613
reset_all() -> Result<(), String>614 pub fn reset_all() -> Result<(), String> {
615 let manager = get_manager();
616 // Perform reset for all manager
617 let mut device_ids = Vec::new();
618 for device in manager.devices.read().unwrap().values() {
619 device.reset()?;
620 device_ids.push(device.id);
621 }
622 for device_id in device_ids {
623 get_pose_manager().reset(device_id);
624 }
625 // Update last modified timestamp for manager
626 manager.update_timestamp();
627 manager.events.publish(Event::DeviceReset);
628 Ok(())
629 }
630
handle_device_create(writer: ResponseWritable, create_json: &str)631 fn handle_device_create(writer: ResponseWritable, create_json: &str) {
632 let mut response = CreateDeviceResponse::new();
633
634 let mut get_result = || {
635 let mut create_device_request = CreateDeviceRequest::new();
636 merge_from_str(&mut create_device_request, create_json)
637 .map_err(|_| format!("create device: invalid json: {}", create_json))?;
638 let device_proto = create_device(&create_device_request)?;
639 response.device = MessageField::some(device_proto);
640 print_to_string(&response).map_err(|_| String::from("failed to convert device to json"))
641 };
642
643 match get_result() {
644 Ok(response) => writer.put_ok("text/json", &response, vec![]),
645 Err(err) => writer.put_error(404, err.as_str()),
646 }
647 }
648
649 /// Performs PatchDevice to patch a single device
handle_device_patch(writer: ResponseWritable, id: Option<DeviceIdentifier>, patch_json: &str)650 fn handle_device_patch(writer: ResponseWritable, id: Option<DeviceIdentifier>, patch_json: &str) {
651 match patch_device_json(id, patch_json) {
652 Ok(()) => writer.put_ok("text/plain", "Device Patch Success", vec![]),
653 Err(err) => writer.put_error(404, err.as_str()),
654 }
655 }
656
handle_chip_delete(writer: ResponseWritable, delete_json: &str)657 fn handle_chip_delete(writer: ResponseWritable, delete_json: &str) {
658 let get_result = || {
659 let mut delete_chip_request = DeleteChipRequest::new();
660 merge_from_str(&mut delete_chip_request, delete_json)
661 .map_err(|_| format!("delete chip: invalid json: {}", delete_json))?;
662 delete_chip(&delete_chip_request)
663 };
664
665 match get_result() {
666 Ok(()) => writer.put_ok("text/plain", "Chip Delete Success", vec![]),
667 Err(err) => writer.put_error(404, err.as_str()),
668 }
669 }
670
list_device() -> anyhow::Result<ListDeviceResponse, String>671 pub fn list_device() -> anyhow::Result<ListDeviceResponse, String> {
672 // Instantiate ListDeviceResponse and add DeviceManager
673 let mut response = ListDeviceResponse::new();
674 let manager = get_manager();
675
676 for device in manager.devices.read().unwrap().values() {
677 if let Ok(device_proto) = device.get(get_pose_manager()) {
678 response.devices.push(device_proto);
679 }
680 }
681
682 // Add Last Modified Timestamp into ListDeviceResponse
683 response.last_modified = Some(Timestamp {
684 seconds: manager.last_modified.read().unwrap().as_secs() as i64,
685 nanos: manager.last_modified.read().unwrap().subsec_nanos() as i32,
686 ..Default::default()
687 })
688 .into();
689 Ok(response)
690 }
691
692 /// Performs ListDevices to get the list of DeviceManager and write to writer.
handle_device_list(writer: ResponseWritable)693 fn handle_device_list(writer: ResponseWritable) {
694 let response = list_device().unwrap();
695 // Perform protobuf-json-mapping with the given protobuf
696 if let Ok(json_response) = print_to_string_with_options(&response, &JSON_PRINT_OPTION) {
697 writer.put_ok("text/json", &json_response, vec![])
698 } else {
699 writer.put_error(404, "proto to JSON mapping failure")
700 }
701 }
702
703 /// Performs ResetDevice for all devices
handle_device_reset(writer: ResponseWritable)704 fn handle_device_reset(writer: ResponseWritable) {
705 match reset_all() {
706 Ok(()) => writer.put_ok("text/plain", "Device Reset Success", vec![]),
707 Err(err) => writer.put_error(404, err.as_str()),
708 }
709 }
710
711 /// Performs SubscribeDevice
handle_device_subscribe(writer: ResponseWritable, subscribe_json: &str)712 fn handle_device_subscribe(writer: ResponseWritable, subscribe_json: &str) {
713 // Check if the provided last_modified timestamp is prior to the current last_modified
714 let mut subscribe_device_request = SubscribeDeviceRequest::new();
715 if merge_from_str(&mut subscribe_device_request, subscribe_json).is_ok() {
716 let timestamp_proto = subscribe_device_request.last_modified;
717 let provided_last_modified =
718 Duration::new(timestamp_proto.seconds as u64, timestamp_proto.nanos as u32);
719 if provided_last_modified < *get_manager().last_modified.read().unwrap() {
720 info!("Immediate return for SubscribeDevice");
721 handle_device_list(writer);
722 return;
723 }
724 }
725
726 let manager = get_manager();
727 let event_rx = manager.events.subscribe();
728 // Timeout after 15 seconds with no event received
729 match event_rx.recv_timeout(Duration::from_secs(15)) {
730 Ok(Event::DeviceAdded(_))
731 | Ok(Event::DeviceRemoved(_))
732 | Ok(Event::ChipAdded(_))
733 | Ok(Event::ChipRemoved(_))
734 | Ok(Event::DevicePatched(_))
735 | Ok(Event::DeviceReset) => handle_device_list(writer),
736 Err(err) => writer.put_error(404, format!("{err:?}").as_str()),
737 _ => writer.put_error(404, "disconnecting due to unrelated event"),
738 }
739 }
740
741 /// The Rust device handler used directly by Http frontend for LIST, GET, and PATCH
handle_device(request: &Request<Vec<u8>>, param: &str, writer: ResponseWritable)742 pub fn handle_device(request: &Request<Vec<u8>>, param: &str, writer: ResponseWritable) {
743 // Route handling
744 if request.uri() == "/v1/devices" {
745 // Routes with ID not specified
746 match request.method().as_str() {
747 "GET" => {
748 handle_device_list(writer);
749 }
750 "PUT" => {
751 handle_device_reset(writer);
752 }
753 "SUBSCRIBE" => {
754 let body = request.body();
755 let subscribe_json = String::from_utf8(body.to_vec()).unwrap();
756 handle_device_subscribe(writer, subscribe_json.as_str());
757 }
758 "PATCH" => {
759 let body = request.body();
760 let patch_json = String::from_utf8(body.to_vec()).unwrap();
761 handle_device_patch(writer, None, patch_json.as_str());
762 }
763 "POST" => {
764 let body = &request.body();
765 let create_json = String::from_utf8(body.to_vec()).unwrap();
766 handle_device_create(writer, create_json.as_str());
767 }
768 "DELETE" => {
769 let body = &request.body();
770 let delete_json = String::from_utf8(body.to_vec()).unwrap();
771 handle_chip_delete(writer, delete_json.as_str());
772 }
773 _ => writer.put_error(404, "Not found."),
774 }
775 } else {
776 // Routes with ID specified
777 match request.method().as_str() {
778 "PATCH" => {
779 let id = match param.parse::<u32>() {
780 Ok(num) => DeviceIdentifier(num),
781 Err(_) => {
782 writer.put_error(404, "Incorrect Id type for devices, ID should be u32.");
783 return;
784 }
785 };
786 let body = request.body();
787 let patch_json = String::from_utf8(body.to_vec()).unwrap();
788 handle_device_patch(writer, Some(id), patch_json.as_str());
789 }
790 _ => writer.put_error(404, "Not found."),
791 }
792 }
793 }
794
795 /// return enum type for check_device_event
796 #[derive(Debug, PartialEq)]
797 enum DeviceWaitStatus {
798 LastDeviceRemoved,
799 DeviceAdded,
800 Timeout,
801 IgnoreEvent,
802 }
803
804 /// listening to events
check_device_event( events_rx: &Receiver<Event>, timeout_time: Option<Instant>, ) -> DeviceWaitStatus805 fn check_device_event(
806 events_rx: &Receiver<Event>,
807 timeout_time: Option<Instant>,
808 ) -> DeviceWaitStatus {
809 let wait_time = timeout_time.map_or(Duration::from_secs(u64::MAX), |t| t - Instant::now());
810 match events_rx.recv_timeout(wait_time) {
811 Ok(Event::ChipRemoved(ChipRemoved { remaining_nonbuiltin_devices: 0, .. })) => {
812 DeviceWaitStatus::LastDeviceRemoved
813 }
814 // DeviceAdded (event from CreateDevice)
815 // ChipAdded (event from add_chip)
816 Ok(Event::DeviceAdded(DeviceAdded { builtin: false, .. }))
817 | Ok(Event::ChipAdded(ChipAdded { builtin: false, .. })) => DeviceWaitStatus::DeviceAdded,
818 Err(_) => DeviceWaitStatus::Timeout,
819 _ => DeviceWaitStatus::IgnoreEvent,
820 }
821 }
822
823 /// wait loop logic for devices
824 /// the function will publish a ShutDown event when
825 /// 1. Initial timeout before first device is added
826 /// 2. Last Chip Removed from netsimd
827 /// this function should NOT be invoked if running in no-shutdown mode
spawn_shutdown_publisher(events_rx: Receiver<Event>, events: Arc<Events>)828 pub fn spawn_shutdown_publisher(events_rx: Receiver<Event>, events: Arc<Events>) {
829 spawn_shutdown_publisher_with_timeout(events_rx, IDLE_SECS_FOR_SHUTDOWN, events);
830 }
831
832 // separate function for testability
spawn_shutdown_publisher_with_timeout( events_rx: Receiver<Event>, timeout_duration_s: u64, events: Arc<Events>, )833 fn spawn_shutdown_publisher_with_timeout(
834 events_rx: Receiver<Event>,
835 timeout_duration_s: u64,
836 events: Arc<Events>,
837 ) {
838 let _ =
839 std::thread::Builder::new().name("device_event_subscriber".to_string()).spawn(move || {
840 let mut timeout_time = Some(Instant::now() + Duration::from_secs(timeout_duration_s));
841 loop {
842 match check_device_event(&events_rx, timeout_time) {
843 DeviceWaitStatus::LastDeviceRemoved => {
844 events.publish(Event::ShutDown(ShutDown {
845 reason: "last device disconnected".to_string(),
846 }));
847 return;
848 }
849 DeviceWaitStatus::DeviceAdded => {
850 timeout_time = None;
851 }
852 DeviceWaitStatus::Timeout => {
853 events.publish(Event::ShutDown(ShutDown {
854 reason: format!(
855 "no devices connected within {IDLE_SECS_FOR_SHUTDOWN}s"
856 ),
857 }));
858 return;
859 }
860 DeviceWaitStatus::IgnoreEvent => continue,
861 }
862 }
863 });
864 }
865
866 /// Return vector containing current radio chip stats from all devices
get_radio_stats() -> Vec<NetsimRadioStats>867 pub fn get_radio_stats() -> Vec<NetsimRadioStats> {
868 let mut result: Vec<NetsimRadioStats> = Vec::new();
869 // TODO: b/309805437 - optimize logic using get_stats for WirelessChip
870 for (device_id, device) in get_manager().devices.read().unwrap().iter() {
871 for chip in device.chips.read().unwrap().values() {
872 for mut radio_stats in chip.get_stats() {
873 radio_stats.set_device_id(device_id.0);
874 result.push(radio_stats);
875 }
876 }
877 }
878 result
879 }
880
881 #[cfg(test)]
882 mod tests {
883 use http::Version;
884 use netsim_common::util::netsim_logger::init_for_test;
885 use netsim_proto::frontend::patch_device_request::PatchDeviceFields as ProtoPatchDeviceFields;
886 use netsim_proto::model::{DeviceCreate as ProtoDeviceCreate, Orientation as ProtoOrientation};
887 use protobuf_json_mapping::print_to_string;
888 use std::{sync::Once, thread};
889
890 use super::*;
891
892 static TEST_DEVICE_KIND: &str = "TEST_DEVICE";
893
894 // This allows Log init method to be invoked once when running all tests.
895 static INIT: Once = Once::new();
896
897 /// Module setup function that is only run once, even if called multiple times.
module_setup()898 fn module_setup() {
899 INIT.call_once(|| {
900 init_for_test();
901 DeviceManager::init(Events::new());
902 });
903 }
904
905 /// TestChipParameters struct to invoke add_chip
906 /// This struct contains parameters required to invoke add_chip.
907 /// This will eventually be invoked by the facades.
908 struct TestChipParameters {
909 device_guid: String,
910 device_name: String,
911 chip_kind: ProtoChipKind,
912 chip_name: String,
913 chip_manufacturer: String,
914 chip_product_name: String,
915 device_info: ProtoDeviceInfo,
916 }
917
918 impl TestChipParameters {
add_chip(&self) -> Result<AddChipResult, String>919 fn add_chip(&self) -> Result<AddChipResult, String> {
920 let chip_create_params = chip::CreateParams {
921 kind: self.chip_kind,
922 address: "".to_string(),
923 name: Some(self.chip_name.clone()),
924 manufacturer: self.chip_manufacturer.clone(),
925 product_name: self.chip_product_name.clone(),
926 };
927 let wireless_create_params =
928 wireless::CreateParam::Mock(wireless::mocked::CreateParams {
929 chip_kind: self.chip_kind,
930 });
931 super::add_chip(
932 &self.device_guid,
933 &self.device_name,
934 &chip_create_params,
935 &wireless_create_params,
936 self.device_info.clone(),
937 )
938 }
939
get_or_create_device(&self) -> DeviceIdentifier940 fn get_or_create_device(&self) -> DeviceIdentifier {
941 let manager = get_manager();
942 manager
943 .get_or_create_device(
944 Some(&self.device_guid),
945 Some(&self.device_name),
946 false,
947 self.device_info.clone(),
948 )
949 .0
950 }
951 }
952
953 /// helper function for test cases to instantiate ProtoPosition
new_position(x: f32, y: f32, z: f32) -> ProtoPosition954 fn new_position(x: f32, y: f32, z: f32) -> ProtoPosition {
955 ProtoPosition { x, y, z, ..Default::default() }
956 }
957
new_orientation(yaw: f32, pitch: f32, roll: f32) -> ProtoOrientation958 fn new_orientation(yaw: f32, pitch: f32, roll: f32) -> ProtoOrientation {
959 ProtoOrientation { yaw, pitch, roll, ..Default::default() }
960 }
961
test_chip_1_bt() -> TestChipParameters962 fn test_chip_1_bt() -> TestChipParameters {
963 TestChipParameters {
964 device_guid: format!("guid-fs-1-{:?}", thread::current().id()),
965 device_name: format!("test-device-name-1-{:?}", thread::current().id()),
966 chip_kind: ProtoChipKind::BLUETOOTH,
967 chip_name: "bt_chip_name".to_string(),
968 chip_manufacturer: "netsim".to_string(),
969 chip_product_name: "netsim_bt".to_string(),
970 device_info: ProtoDeviceInfo {
971 kind: TEST_DEVICE_KIND.to_string(),
972 ..Default::default()
973 },
974 }
975 }
976
test_chip_1_wifi() -> TestChipParameters977 fn test_chip_1_wifi() -> TestChipParameters {
978 TestChipParameters {
979 device_guid: format!("guid-fs-1-{:?}", thread::current().id()),
980 device_name: format!("test-device-name-1-{:?}", thread::current().id()),
981 chip_kind: ProtoChipKind::WIFI,
982 chip_name: "wifi_chip_name".to_string(),
983 chip_manufacturer: "netsim".to_string(),
984 chip_product_name: "netsim_wifi".to_string(),
985 device_info: ProtoDeviceInfo {
986 kind: TEST_DEVICE_KIND.to_string(),
987 ..Default::default()
988 },
989 }
990 }
991
test_chip_2_bt() -> TestChipParameters992 fn test_chip_2_bt() -> TestChipParameters {
993 TestChipParameters {
994 device_guid: format!("guid-fs-2-{:?}", thread::current().id()),
995 device_name: format!("test-device-name-2-{:?}", thread::current().id()),
996 chip_kind: ProtoChipKind::BLUETOOTH,
997 chip_name: "bt_chip_name".to_string(),
998 chip_manufacturer: "netsim".to_string(),
999 chip_product_name: "netsim_bt".to_string(),
1000 device_info: ProtoDeviceInfo {
1001 kind: TEST_DEVICE_KIND.to_string(),
1002 ..Default::default()
1003 },
1004 }
1005 }
1006
reset(id: DeviceIdentifier) -> Result<(), String>1007 fn reset(id: DeviceIdentifier) -> Result<(), String> {
1008 get_pose_manager().reset(id);
1009
1010 let manager = get_manager();
1011 let mut devices = manager.devices.write().unwrap();
1012 match devices.get_mut(&id) {
1013 Some(device) => device.reset(),
1014 None => Err(format!("No such device with id {id}")),
1015 }
1016 }
1017
spawn_shutdown_publisher_test_setup(timeout: u64) -> (Arc<Events>, Receiver<Event>)1018 fn spawn_shutdown_publisher_test_setup(timeout: u64) -> (Arc<Events>, Receiver<Event>) {
1019 let events = Events::new();
1020 let events_rx = events.subscribe();
1021 spawn_shutdown_publisher_with_timeout(events_rx, timeout, events.clone());
1022
1023 let events_rx2 = events.subscribe();
1024
1025 (events, events_rx2)
1026 }
1027
1028 #[test]
test_spawn_shutdown_publisher_last_chip_removed()1029 fn test_spawn_shutdown_publisher_last_chip_removed() {
1030 let (events, events_rx) = spawn_shutdown_publisher_test_setup(IDLE_SECS_FOR_SHUTDOWN);
1031
1032 events.publish(Event::ChipRemoved(ChipRemoved {
1033 remaining_nonbuiltin_devices: 0,
1034 ..Default::default()
1035 }));
1036
1037 // receive our own ChipRemoved
1038 assert!(matches!(events_rx.recv(), Ok(Event::ChipRemoved(ChipRemoved { .. }))));
1039 // receive the ShutDown emitted by the function under test
1040 assert!(matches!(events_rx.recv(), Ok(Event::ShutDown(ShutDown { .. }))));
1041 }
1042
1043 #[test]
test_spawn_shutdown_publisher_chip_removed_which_is_not_last_chip()1044 fn test_spawn_shutdown_publisher_chip_removed_which_is_not_last_chip() {
1045 let (events, events_rx) = spawn_shutdown_publisher_test_setup(IDLE_SECS_FOR_SHUTDOWN);
1046 events.publish(Event::ChipRemoved(ChipRemoved {
1047 chip_id: ChipIdentifier(1),
1048 remaining_nonbuiltin_devices: 1,
1049 ..Default::default()
1050 }));
1051
1052 // give other thread time to generate a ShutDown if it was going to
1053 std::thread::sleep(std::time::Duration::from_secs(1));
1054
1055 // only the 2nd ChipRemoved should generate a ShutDown as it is marked the last one
1056 events.publish(Event::ChipRemoved(ChipRemoved {
1057 chip_id: ChipIdentifier(0),
1058 remaining_nonbuiltin_devices: 0,
1059 ..Default::default()
1060 }));
1061
1062 // receive our own ChipRemoved
1063 assert!(matches!(events_rx.recv(), Ok(Event::ChipRemoved(ChipRemoved { .. }))));
1064 // receive our own ChipRemoved (with no shutdown)
1065 assert!(matches!(events_rx.recv(), Ok(Event::ChipRemoved(ChipRemoved { .. }))));
1066 // only then receive the ShutDown emitted by the function under test
1067 assert!(matches!(events_rx.recv(), Ok(Event::ShutDown(ShutDown { .. }))));
1068 }
1069
1070 #[test]
test_spawn_shutdown_publisher_last_chip_removed_with_duplicate_event()1071 fn test_spawn_shutdown_publisher_last_chip_removed_with_duplicate_event() {
1072 let (events, events_rx) = spawn_shutdown_publisher_test_setup(IDLE_SECS_FOR_SHUTDOWN);
1073 events.publish(Event::ChipRemoved(ChipRemoved {
1074 chip_id: ChipIdentifier(0),
1075 remaining_nonbuiltin_devices: 0,
1076 ..Default::default()
1077 }));
1078
1079 // give other thread time to generate a ShutDown if it was going to
1080 std::thread::sleep(std::time::Duration::from_secs(1));
1081
1082 // this is a duplicate event and we already sent that all chips were removed
1083 // this is for strict comparison with test_spawn_shutdown_publisher_chip_removed_which_is_not_last_chip
1084 // to validate that if the first event has remaining_nonbuiltin_devices 0
1085 // we would receive ChipRemoved, ShutDown, ChipRemoved
1086 // but if first ChipRemoved has remaining_nonbuiltin_devices,
1087 // we instead receive ChipRemoved, ChipRemoved, ShutDown
1088 events.publish(Event::ChipRemoved(ChipRemoved {
1089 chip_id: ChipIdentifier(0),
1090 remaining_nonbuiltin_devices: 0,
1091 ..Default::default()
1092 }));
1093
1094 // receive our own ChipRemoved
1095 assert!(matches!(events_rx.recv(), Ok(Event::ChipRemoved(_))));
1096 // receive the ShutDown emitted by the function under test
1097 assert!(matches!(events_rx.recv(), Ok(Event::ShutDown(_))));
1098 // receive our own erroneous ChipRemoved which occurs after we said all chips were removed
1099 // this is just for strict comparison with test_spawn_shutdown_publisher_chip_removed_which_is_not_last_chip
1100 assert!(matches!(events_rx.recv(), Ok(Event::ChipRemoved(_))));
1101 // should timeout now (no further events as we expect shutdown publisher thread to have stopped)
1102 assert!(events_rx.recv_timeout(Duration::from_secs(2)).is_err());
1103 }
1104
1105 #[test]
test_spawn_shutdown_publisher_timeout()1106 fn test_spawn_shutdown_publisher_timeout() {
1107 let (_, events_rx) = spawn_shutdown_publisher_test_setup(1u64);
1108
1109 // receive the ShutDown emitted by the function under test
1110 assert!(matches!(events_rx.recv_timeout(Duration::from_secs(2)), Ok(Event::ShutDown(_))));
1111 }
1112
1113 #[test]
test_spawn_shutdown_publisher_timeout_is_canceled_if_a_chip_is_added()1114 fn test_spawn_shutdown_publisher_timeout_is_canceled_if_a_chip_is_added() {
1115 let (events, events_rx) = spawn_shutdown_publisher_test_setup(1u64);
1116
1117 events.publish(Event::ChipAdded(ChipAdded {
1118 chip_id: ChipIdentifier(0),
1119 chip_kind: ProtoChipKind::BLUETOOTH,
1120 ..Default::default()
1121 }));
1122 assert!(matches!(events_rx.recv(), Ok(Event::ChipAdded(_))));
1123
1124 // should NO longer receive the ShutDown emitted by the function under test
1125 // based on timeout removed when chip added
1126 assert!(events_rx.recv_timeout(Duration::from_secs(2)).is_err());
1127
1128 events.publish(Event::ChipRemoved(ChipRemoved {
1129 chip_id: ChipIdentifier(0),
1130 remaining_nonbuiltin_devices: 0,
1131 ..Default::default()
1132 }));
1133 // receive our own ChipRemoved
1134 assert!(matches!(events_rx.recv(), Ok(Event::ChipRemoved(_))));
1135 // receive the ShutDown emitted by the function under test
1136 assert!(matches!(events_rx.recv(), Ok(Event::ShutDown(_))));
1137 }
1138
1139 #[test]
test_distance()1140 fn test_distance() {
1141 // Pythagorean quadruples
1142 let a = new_position(0.0, 0.0, 0.0);
1143 let mut b = new_position(1.0, 2.0, 2.0);
1144 assert_eq!(distance(&a, &b), 3.0);
1145 b = new_position(2.0, 3.0, 6.0);
1146 assert_eq!(distance(&a, &b), 7.0);
1147 }
1148
1149 #[test]
test_add_chip()1150 fn test_add_chip() {
1151 module_setup();
1152
1153 let manager = get_manager();
1154
1155 // Adding a chip
1156 let chip_params = test_chip_1_bt();
1157 let chip_result = chip_params.add_chip().unwrap();
1158 match manager.devices.read().unwrap().get(&chip_result.device_id) {
1159 Some(device) => {
1160 let chips = device.chips.read().unwrap();
1161 let chip = chips.get(&chip_result.chip_id).unwrap();
1162 assert_eq!(chip_params.chip_kind, chip.kind);
1163 assert_eq!(
1164 chip_params.chip_manufacturer,
1165 chip.manufacturer.read().unwrap().to_string()
1166 );
1167 assert_eq!(chip_params.chip_name, chip.name);
1168 assert_eq!(
1169 chip_params.chip_product_name,
1170 chip.product_name.read().unwrap().to_string()
1171 );
1172 assert_eq!(chip_params.device_name, device.name);
1173 }
1174 None => unreachable!(),
1175 };
1176 }
1177
1178 #[test]
test_get_or_create_device()1179 fn test_get_or_create_device() {
1180 module_setup();
1181
1182 let manager = get_manager();
1183
1184 // Creating a device and getting device
1185 let bt_chip_params = test_chip_1_bt();
1186 let device_id_1 = bt_chip_params.get_or_create_device();
1187 let wifi_chip_params = test_chip_1_wifi();
1188 let device_id_2 = wifi_chip_params.get_or_create_device();
1189 assert_eq!(device_id_1, device_id_2);
1190 assert!(manager.devices.read().unwrap().get(&device_id_1).is_some())
1191 }
1192
1193 #[test]
test_patch_device_json()1194 fn test_patch_device_json() {
1195 module_setup();
1196
1197 // Patching device position and orientation by id
1198 let chip_params = test_chip_1_bt();
1199 let chip_result = chip_params.add_chip().unwrap();
1200 let mut patch_device_request = PatchDeviceRequest::new();
1201 let mut proto_device = ProtoPatchDeviceFields::new();
1202 let request_position = new_position(1.1, 2.2, 3.3);
1203 let request_orientation = new_orientation(4.4, 5.5, 6.6);
1204 proto_device.name = Some(chip_params.device_name);
1205 proto_device.visible = Some(false);
1206 proto_device.position = Some(request_position.clone()).into();
1207 proto_device.orientation = Some(request_orientation.clone()).into();
1208 patch_device_request.device = Some(proto_device.clone()).into();
1209 let patch_json = print_to_string(&patch_device_request).unwrap();
1210 patch_device_json(Some(chip_result.device_id), patch_json.as_str()).unwrap();
1211 match get_manager().devices.read().unwrap().get(&chip_result.device_id) {
1212 Some(device) => {
1213 assert!(!device.visible.load(Ordering::SeqCst));
1214 }
1215 None => unreachable!(),
1216 }
1217
1218 match get_pose_manager().get_position(&chip_result.device_id) {
1219 Some(position) => {
1220 assert_eq!(position.x, request_position.x);
1221 assert_eq!(position.y, request_position.y);
1222 assert_eq!(position.z, request_position.z);
1223 }
1224 None => unreachable!(),
1225 }
1226
1227 match get_pose_manager().get_orientation(&chip_result.device_id) {
1228 Some(orientation) => {
1229 assert_eq!(orientation.yaw, request_orientation.yaw);
1230 assert_eq!(orientation.pitch, request_orientation.pitch);
1231 assert_eq!(orientation.roll, request_orientation.roll);
1232 }
1233 None => unreachable!(),
1234 }
1235
1236 // Patch device by name with substring match
1237 proto_device.name = format!("test-device-name-1-{:?}", thread::current().id()).into();
1238 patch_device_request.device = Some(proto_device).into();
1239 let patch_json = print_to_string(&patch_device_request).unwrap();
1240 assert!(patch_device_json(None, patch_json.as_str()).is_ok());
1241 }
1242
1243 #[test]
test_patch_error()1244 fn test_patch_error() {
1245 module_setup();
1246
1247 // Patch Error Testing
1248 let bt_chip_params = test_chip_1_bt();
1249 let bt_chip2_params = test_chip_2_bt();
1250 let bt_chip_result = bt_chip_params.add_chip().unwrap();
1251 bt_chip2_params.add_chip().unwrap();
1252
1253 // Incorrect value type
1254 let error_json = format!(
1255 "{{\"device\": {{\"name\": \"test-device-name-1-{:?}\", \"position\": 1.1}}}}",
1256 thread::current().id()
1257 );
1258 let patch_result = patch_device_json(Some(bt_chip_result.device_id), error_json.as_str());
1259 assert!(patch_result.is_err());
1260 assert_eq!(
1261 patch_result.unwrap_err(),
1262 format!("Incorrect format of patch json {}", error_json)
1263 );
1264
1265 // Incorrect key
1266 let error_json = format!(
1267 "{{\"device\": {{\"name\": \"test-device-name-1-{:?}\", \"hello\": \"world\"}}}}",
1268 thread::current().id()
1269 );
1270 let patch_result = patch_device_json(Some(bt_chip_result.device_id), error_json.as_str());
1271 assert!(patch_result.is_err());
1272 assert_eq!(
1273 patch_result.unwrap_err(),
1274 format!("Incorrect format of patch json {}", error_json)
1275 );
1276
1277 // Incorrect Id
1278 let error_json = r#"{"device": {"name": "test-device-name-1"}}"#;
1279 let patch_result =
1280 patch_device_json(Some(DeviceIdentifier(INITIAL_DEVICE_ID - 1)), error_json);
1281 assert!(patch_result.is_err());
1282 assert_eq!(
1283 patch_result.unwrap_err(),
1284 format!("No such device with id {}", INITIAL_DEVICE_ID - 1)
1285 );
1286
1287 // Incorrect name
1288 let error_json = r#"{"device": {"name": "wrong-name"}}"#;
1289 let patch_result = patch_device_json(None, error_json);
1290 assert!(patch_result.is_err());
1291 assert_eq!(patch_result.unwrap_err(), "No such device with name wrong-name");
1292
1293 // Multiple ambiguous matching
1294 let error_json = r#"{"device": {"name": "test-device"}}"#;
1295 let patch_result = patch_device_json(None, error_json);
1296 assert!(patch_result.is_err());
1297 assert_eq!(
1298 patch_result.unwrap_err(),
1299 "Multiple ambiguous matches were found with substring test-device"
1300 );
1301 }
1302
1303 #[test]
test_adding_two_chips()1304 fn test_adding_two_chips() {
1305 module_setup();
1306 let manager = get_manager();
1307
1308 // Adding two chips of the same device
1309 let bt_chip_params = test_chip_1_bt();
1310 let wifi_chip_params = test_chip_1_wifi();
1311 let bt_chip_result = bt_chip_params.add_chip().unwrap();
1312 let wifi_chip_result = wifi_chip_params.add_chip().unwrap();
1313 assert_eq!(bt_chip_result.device_id, wifi_chip_result.device_id);
1314 let devices = manager.devices.read().unwrap();
1315 let device = devices.get(&bt_chip_result.device_id).unwrap();
1316 assert_eq!(device.id, bt_chip_result.device_id);
1317 assert_eq!(device.name, bt_chip_params.device_name);
1318 assert_eq!(device.chips.read().unwrap().len(), 2);
1319 for chip in device.chips.read().unwrap().values() {
1320 assert!(chip.id == bt_chip_result.chip_id || chip.id == wifi_chip_result.chip_id);
1321 if chip.id == bt_chip_result.chip_id {
1322 assert_eq!(chip.kind, ProtoChipKind::BLUETOOTH);
1323 } else if chip.id == wifi_chip_result.chip_id {
1324 assert_eq!(chip.kind, ProtoChipKind::WIFI);
1325 } else {
1326 unreachable!();
1327 }
1328 }
1329 }
1330
1331 #[test]
test_reset()1332 fn test_reset() {
1333 module_setup();
1334 let manager = get_manager();
1335
1336 // Patching Device and Resetting scene
1337 let chip_params = test_chip_1_bt();
1338 let chip_result = chip_params.add_chip().unwrap();
1339 let mut patch_device_request = PatchDeviceRequest::new();
1340 let mut proto_device = ProtoPatchDeviceFields::new();
1341 let request_position = new_position(10.0, 20.0, 30.0);
1342 let request_orientation = new_orientation(1.0, 2.0, 3.0);
1343 proto_device.name = Some(chip_params.device_name);
1344 proto_device.visible = Some(false);
1345 proto_device.position = Some(request_position).into();
1346 proto_device.orientation = Some(request_orientation).into();
1347 patch_device_request.device = Some(proto_device).into();
1348 patch_device_json(
1349 Some(chip_result.device_id),
1350 print_to_string(&patch_device_request).unwrap().as_str(),
1351 )
1352 .unwrap();
1353 match manager.devices.read().unwrap().get(&chip_result.device_id) {
1354 Some(device) => {
1355 assert!(!device.visible.load(Ordering::SeqCst));
1356 }
1357 None => unreachable!(),
1358 }
1359
1360 match get_pose_manager().get_position(&chip_result.device_id) {
1361 Some(position) => {
1362 assert_eq!(position.x, 10.0);
1363 }
1364 None => unreachable!(),
1365 }
1366
1367 match get_pose_manager().get_orientation(&chip_result.device_id) {
1368 Some(orientation) => {
1369 assert_eq!(orientation.yaw, 1.0);
1370 }
1371 None => unreachable!(),
1372 }
1373
1374 reset(chip_result.device_id).unwrap();
1375 match manager.devices.read().unwrap().get(&chip_result.device_id) {
1376 Some(device) => {
1377 assert!(device.visible.load(Ordering::SeqCst));
1378 }
1379 None => unreachable!(),
1380 }
1381
1382 match get_pose_manager().get_position(&chip_result.device_id) {
1383 Some(position) => {
1384 assert_eq!(position.x, 0.0);
1385 assert_eq!(position.y, 0.0);
1386 assert_eq!(position.z, 0.0);
1387 }
1388 None => unreachable!(),
1389 }
1390
1391 match get_pose_manager().get_orientation(&chip_result.device_id) {
1392 Some(orientation) => {
1393 assert_eq!(orientation.yaw, 0.0);
1394 assert_eq!(orientation.pitch, 0.0);
1395 assert_eq!(orientation.roll, 0.0);
1396 }
1397 None => unreachable!(),
1398 }
1399 }
1400
1401 #[test]
test_remove_chip()1402 fn test_remove_chip() {
1403 module_setup();
1404 let manager = get_manager();
1405
1406 // Add 2 chips of same device and 1 chip of different device
1407 let bt_chip_params = test_chip_1_bt();
1408 let wifi_chip_params = test_chip_1_wifi();
1409 let bt_chip_2_params = test_chip_2_bt();
1410 let bt_chip_result = bt_chip_params.add_chip().unwrap();
1411 let wifi_chip_result = wifi_chip_params.add_chip().unwrap();
1412 let bt_chip_2_result = bt_chip_2_params.add_chip().unwrap();
1413
1414 // Remove a bt chip of first device
1415 remove_chip(bt_chip_result.device_id, bt_chip_result.chip_id).unwrap();
1416 match manager.devices.read().unwrap().get(&bt_chip_result.device_id) {
1417 Some(device) => {
1418 assert_eq!(device.chips.read().unwrap().len(), 1);
1419 assert_eq!(
1420 device.chips.read().unwrap().get(&wifi_chip_result.chip_id).unwrap().kind,
1421 ProtoChipKind::WIFI
1422 );
1423 }
1424 None => unreachable!(),
1425 }
1426
1427 // Remove a wifi chip of first device
1428 remove_chip(wifi_chip_result.device_id, wifi_chip_result.chip_id).unwrap();
1429 assert!(!manager.devices.read().unwrap().contains_key(&wifi_chip_result.device_id));
1430
1431 // Remove a bt chip of second device
1432 remove_chip(bt_chip_2_result.device_id, bt_chip_2_result.chip_id).unwrap();
1433 assert!(!manager.devices.read().unwrap().contains_key(&bt_chip_2_result.device_id));
1434 }
1435
1436 #[test]
test_remove_chip_error()1437 fn test_remove_chip_error() {
1438 module_setup();
1439 let manager = get_manager();
1440
1441 // Add 2 chips of same device and 1 chip of different device
1442 let bt_chip_params = test_chip_1_bt();
1443 let bt_chip_result = bt_chip_params.add_chip().unwrap();
1444
1445 // Invoke remove_chip with incorrect chip_id.
1446 match remove_chip(bt_chip_result.device_id, ChipIdentifier(9999)) {
1447 Ok(_) => unreachable!(),
1448 Err(err) => assert_eq!(err, "RemoveChip chip id 9999 not found"),
1449 }
1450
1451 // Invoke remove_chip with incorrect device_id
1452 match remove_chip(DeviceIdentifier(9999), bt_chip_result.chip_id) {
1453 Ok(_) => unreachable!(),
1454 Err(err) => assert_eq!(err, "RemoveChip device id 9999 not found"),
1455 }
1456 assert!(manager.devices.read().unwrap().contains_key(&bt_chip_result.device_id));
1457 }
1458
1459 #[test]
test_get_distance()1460 fn test_get_distance() {
1461 module_setup();
1462
1463 // Add 2 chips of different devices
1464 let bt_chip_params = test_chip_1_bt();
1465 let bt_chip_2_params = test_chip_2_bt();
1466 let bt_chip_result = bt_chip_params.add_chip().unwrap();
1467 let bt_chip_2_result = bt_chip_2_params.add_chip().unwrap();
1468
1469 // Patch the first chip
1470 let mut patch_device_request = PatchDeviceRequest::new();
1471 let mut proto_device = ProtoPatchDeviceFields::new();
1472 let request_position = new_position(1.0, 1.0, 1.0);
1473 proto_device.name = Some(bt_chip_params.device_name);
1474 proto_device.position = Some(request_position.clone()).into();
1475 patch_device_request.device = Some(proto_device.clone()).into();
1476 let patch_json = print_to_string(&patch_device_request).unwrap();
1477 patch_device_json(Some(bt_chip_result.device_id), patch_json.as_str()).unwrap();
1478
1479 // Patch the second chip
1480 let mut patch_device_request = PatchDeviceRequest::new();
1481 let mut proto_device = ProtoPatchDeviceFields::new();
1482 let request_position = new_position(1.0, 4.0, 5.0);
1483 proto_device.name = Some(bt_chip_2_params.device_name);
1484 proto_device.position = Some(request_position.clone()).into();
1485 patch_device_request.device = Some(proto_device.clone()).into();
1486 let patch_json = print_to_string(&patch_device_request).unwrap();
1487 patch_device_json(Some(bt_chip_2_result.device_id), patch_json.as_str()).unwrap();
1488
1489 // Verify the get_distance performs the correct computation of
1490 // sqrt((1-1)**2 + (4-1)**2 + (5-1)**2)
1491 assert_eq!(Ok(5.0), get_distance(&bt_chip_result.chip_id, &bt_chip_2_result.chip_id))
1492 }
1493
1494 #[allow(dead_code)]
list_request() -> Request<Vec<u8>>1495 fn list_request() -> Request<Vec<u8>> {
1496 Request::builder()
1497 .method("GET")
1498 .uri("/v1/devices")
1499 .version(Version::HTTP_11)
1500 .body(Vec::<u8>::new())
1501 .unwrap()
1502 }
1503
1504 use netsim_proto::model::chip::{
1505 ble_beacon::AdvertiseData, ble_beacon::AdvertiseSettings, BleBeacon, Chip,
1506 };
1507 use netsim_proto::model::chip_create::{BleBeaconCreate, Chip as BuiltChipProto};
1508 use netsim_proto::model::Chip as ChipProto;
1509 use netsim_proto::model::ChipCreate as ProtoChipCreate;
1510 use protobuf::{EnumOrUnknown, MessageField};
1511
get_test_create_device_request(device_name: Option<String>) -> CreateDeviceRequest1512 fn get_test_create_device_request(device_name: Option<String>) -> CreateDeviceRequest {
1513 let beacon_proto = BleBeaconCreate {
1514 settings: MessageField::some(AdvertiseSettings { ..Default::default() }),
1515 adv_data: MessageField::some(AdvertiseData { ..Default::default() }),
1516 ..Default::default()
1517 };
1518
1519 let chip_proto = ProtoChipCreate {
1520 name: String::from("test-beacon-chip"),
1521 kind: ProtoChipKind::BLUETOOTH_BEACON.into(),
1522 chip: Some(BuiltChipProto::BleBeacon(beacon_proto)),
1523 ..Default::default()
1524 };
1525
1526 let device_proto = ProtoDeviceCreate {
1527 name: device_name.unwrap_or_default(),
1528 chips: vec![chip_proto],
1529 ..Default::default()
1530 };
1531
1532 CreateDeviceRequest { device: MessageField::some(device_proto), ..Default::default() }
1533 }
1534
1535 #[test]
test_create_device_succeeds()1536 fn test_create_device_succeeds() {
1537 module_setup();
1538
1539 let request = get_test_create_device_request(Some(format!(
1540 "bob-the-beacon-{:?}",
1541 thread::current().id()
1542 )));
1543
1544 let device_proto = create_device(&request);
1545 assert!(device_proto.is_ok());
1546 let device_proto = device_proto.unwrap();
1547 assert_eq!(request.device.name, device_proto.name);
1548 assert_eq!(1, device_proto.chips.len());
1549 assert_eq!(request.device.chips[0].name, device_proto.chips[0].name);
1550 }
1551
1552 #[test]
test_create_chipless_device_fails()1553 fn test_create_chipless_device_fails() {
1554 module_setup();
1555
1556 let request = CreateDeviceRequest {
1557 device: MessageField::some(ProtoDeviceCreate { ..Default::default() }),
1558 ..Default::default()
1559 };
1560
1561 let device_proto = create_device(&request);
1562 assert!(device_proto.is_err(), "{}", device_proto.unwrap());
1563 }
1564
1565 #[test]
test_create_radioless_device_fails()1566 fn test_create_radioless_device_fails() {
1567 module_setup();
1568
1569 let request = CreateDeviceRequest {
1570 device: MessageField::some(ProtoDeviceCreate {
1571 chips: vec![ProtoChipCreate::default()],
1572 ..Default::default()
1573 }),
1574 ..Default::default()
1575 };
1576
1577 let device_proto = create_device(&request);
1578 assert!(device_proto.is_err(), "{}", device_proto.unwrap());
1579 }
1580
1581 #[test]
test_get_beacon_device()1582 fn test_get_beacon_device() {
1583 module_setup();
1584
1585 let request = get_test_create_device_request(Some(format!(
1586 "bob-the-beacon-{:?}",
1587 thread::current().id()
1588 )));
1589
1590 let device_proto = create_device(&request);
1591 assert!(device_proto.is_ok(), "{}", device_proto.unwrap_err());
1592 let device_proto = device_proto.unwrap();
1593 assert_eq!(1, device_proto.chips.len());
1594 assert!(device_proto.chips[0].chip.is_some());
1595 assert!(matches!(device_proto.chips[0].chip, Some(Chip::BleBeacon(_))));
1596 }
1597
1598 #[test]
test_create_device_default_name()1599 fn test_create_device_default_name() {
1600 module_setup();
1601
1602 let request = get_test_create_device_request(None);
1603
1604 let device_proto = create_device(&request);
1605 assert!(device_proto.is_ok(), "{}", device_proto.unwrap_err());
1606 let device_proto = device_proto.unwrap();
1607 assert_eq!(format!("device-{}", device_proto.id), device_proto.name);
1608 }
1609
1610 #[test]
test_create_existing_device_fails()1611 fn test_create_existing_device_fails() {
1612 module_setup();
1613
1614 let request = get_test_create_device_request(Some(format!(
1615 "existing-device-{:?}",
1616 thread::current().id()
1617 )));
1618
1619 let device_proto = create_device(&request);
1620 assert!(device_proto.is_ok(), "{}", device_proto.unwrap_err());
1621
1622 // Attempt to create the device again. This should fail because the devices have the same name.
1623 let device_proto = create_device(&request);
1624 assert!(device_proto.is_err());
1625 }
1626
1627 #[test]
test_patch_beacon_device()1628 fn test_patch_beacon_device() {
1629 module_setup();
1630 let manager = get_manager();
1631
1632 let request = get_test_create_device_request(Some(format!(
1633 "bob-the-beacon-{:?}",
1634 thread::current().id()
1635 )));
1636
1637 let device_proto = create_device(&request);
1638 assert!(device_proto.is_ok(), "{}", device_proto.unwrap_err());
1639 let device_proto = device_proto.unwrap();
1640 let mut devices = manager.devices.write().unwrap();
1641 let device = devices
1642 .get_mut(&DeviceIdentifier(device_proto.id))
1643 .expect("could not find test bluetooth beacon device");
1644 let patch_result = device.patch(
1645 &ProtoPatchDeviceFields {
1646 name: Some(device_proto.name.clone()),
1647 chips: vec![ChipProto {
1648 name: request.device.chips[0].name.clone(),
1649 kind: EnumOrUnknown::new(ProtoChipKind::BLUETOOTH_BEACON),
1650 chip: Some(Chip::BleBeacon(BleBeacon {
1651 bt: MessageField::some(Default::default()),
1652 ..Default::default()
1653 })),
1654 ..Default::default()
1655 }],
1656 ..Default::default()
1657 },
1658 get_pose_manager(),
1659 );
1660 assert!(patch_result.is_ok(), "{}", patch_result.unwrap_err());
1661 let patched_device = device.get(get_pose_manager());
1662 assert!(patched_device.is_ok(), "{}", patched_device.unwrap_err());
1663 let patched_device = patched_device.unwrap();
1664 assert_eq!(1, patched_device.chips.len());
1665 assert!(matches!(patched_device.chips[0].chip, Some(Chip::BleBeacon(_))));
1666 }
1667
1668 #[test]
test_remove_beacon_device_succeeds()1669 fn test_remove_beacon_device_succeeds() {
1670 module_setup();
1671 let manager = get_manager();
1672
1673 let create_request = get_test_create_device_request(None);
1674 let device_proto = create_device(&create_request);
1675 assert!(device_proto.is_ok(), "{}", device_proto.unwrap_err());
1676
1677 let device_proto = device_proto.unwrap();
1678 let chip_id = {
1679 let devices = manager.devices.read().unwrap();
1680 let device = devices.get(&DeviceIdentifier(device_proto.id)).unwrap();
1681 let chips = device.chips.read().unwrap();
1682 chips.first_key_value().map(|(id, _)| *id).unwrap()
1683 };
1684
1685 let delete_request = DeleteChipRequest { id: chip_id.0, ..Default::default() };
1686 let delete_result = delete_chip(&delete_request);
1687 assert!(delete_result.is_ok(), "{}", delete_result.unwrap_err());
1688
1689 assert!(!manager.devices.read().unwrap().contains_key(&DeviceIdentifier(device_proto.id)))
1690 }
1691
1692 #[test]
test_remove_beacon_device_fails()1693 fn test_remove_beacon_device_fails() {
1694 module_setup();
1695 let manager = get_manager();
1696
1697 let create_request = get_test_create_device_request(None);
1698 let device_proto = create_device(&create_request);
1699 assert!(device_proto.is_ok(), "{}", device_proto.unwrap_err());
1700
1701 let device_proto = device_proto.unwrap();
1702 let chip_id = manager
1703 .devices
1704 .read()
1705 .unwrap()
1706 .get(&DeviceIdentifier(device_proto.id))
1707 .unwrap()
1708 .chips
1709 .read()
1710 .unwrap()
1711 .first_key_value()
1712 .map(|(id, _)| *id)
1713 .unwrap();
1714
1715 let delete_request = DeleteChipRequest { id: chip_id.0, ..Default::default() };
1716 let delete_result = delete_chip(&delete_request);
1717 assert!(delete_result.is_ok(), "{}", delete_result.unwrap_err());
1718
1719 let delete_result = delete_chip(&delete_request);
1720 assert!(delete_result.is_err());
1721 }
1722
1723 #[test]
test_check_device_event_initial_timeout()1724 fn test_check_device_event_initial_timeout() {
1725 module_setup();
1726
1727 let events = get_manager().events.clone();
1728 let events_rx = events.subscribe();
1729 assert_eq!(
1730 check_device_event(&events_rx, Some(std::time::Instant::now())),
1731 DeviceWaitStatus::Timeout
1732 );
1733 }
1734
1735 #[test]
test_check_device_event_last_device_removed()1736 fn test_check_device_event_last_device_removed() {
1737 module_setup();
1738
1739 let events = Events::new();
1740 let events_rx = events.subscribe();
1741 events.publish(Event::ChipRemoved(ChipRemoved {
1742 remaining_nonbuiltin_devices: 0,
1743 ..Default::default()
1744 }));
1745 assert_eq!(check_device_event(&events_rx, None), DeviceWaitStatus::LastDeviceRemoved);
1746 }
1747
1748 #[test]
test_check_device_event_device_chip_added()1749 fn test_check_device_event_device_chip_added() {
1750 module_setup();
1751
1752 let events = Events::new();
1753 let events_rx = events.subscribe();
1754 events.publish(Event::DeviceAdded(DeviceAdded {
1755 id: DeviceIdentifier(0),
1756 name: "".to_string(),
1757 builtin: false,
1758 device_stats: ProtoDeviceStats::new(),
1759 }));
1760 assert_eq!(check_device_event(&events_rx, None), DeviceWaitStatus::DeviceAdded);
1761 events.publish(Event::ChipAdded(ChipAdded { builtin: false, ..Default::default() }));
1762 assert_eq!(check_device_event(&events_rx, None), DeviceWaitStatus::DeviceAdded);
1763 }
1764
1765 #[test]
test_check_device_event_ignore_event()1766 fn test_check_device_event_ignore_event() {
1767 module_setup();
1768
1769 let events = Events::new();
1770 let events_rx = events.subscribe();
1771 events.publish(Event::DevicePatched(DevicePatched {
1772 id: DeviceIdentifier(0),
1773 name: "".to_string(),
1774 }));
1775 assert_eq!(check_device_event(&events_rx, None), DeviceWaitStatus::IgnoreEvent);
1776 events.publish(Event::ChipRemoved(ChipRemoved {
1777 remaining_nonbuiltin_devices: 1,
1778 ..Default::default()
1779 }));
1780 assert_eq!(check_device_event(&events_rx, None), DeviceWaitStatus::IgnoreEvent);
1781 }
1782
1783 #[test]
test_check_device_event_ignore_chip_added_for_builtin()1784 fn test_check_device_event_ignore_chip_added_for_builtin() {
1785 module_setup();
1786
1787 let events = Events::new();
1788 let events_rx = events.subscribe();
1789 events.publish(Event::ChipAdded(ChipAdded { builtin: true, ..Default::default() }));
1790 assert_eq!(check_device_event(&events_rx, None), DeviceWaitStatus::IgnoreEvent);
1791 }
1792 }
1793