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