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 use super::advertise_data::{AdvertiseData, AdvertiseDataBuilder};
16 use super::advertise_settings::{
17 AdvertiseMode, AdvertiseSettings, AdvertiseSettingsBuilder, TxPowerLevel,
18 };
19 use super::chip::{rust_bluetooth_add, RustBluetoothChipCallbacks};
20 use super::packets::link_layer::{
21 Address, AddressType, LeLegacyAdvertisingPduBuilder, LeScanResponseBuilder, PacketType,
22 };
23 use crate::devices::chip::{ChipIdentifier, FacadeIdentifier};
24 use crate::devices::device::{AddChipResult, DeviceIdentifier};
25 use crate::devices::devices_handler::add_chip;
26 use crate::ffi::ffi_bluetooth;
27 use crate::wireless;
28 use cxx::{let_cxx_string, UniquePtr};
29 use lazy_static::lazy_static;
30 use log::{error, info, warn};
31 use netsim_proto::common::ChipKind;
32 use netsim_proto::model::chip::Bluetooth;
33 use netsim_proto::model::chip::{
34 ble_beacon::AdvertiseData as AdvertiseDataProto,
35 ble_beacon::AdvertiseSettings as AdvertiseSettingsProto, BleBeacon as BleBeaconProto,
36 };
37 use netsim_proto::model::chip_create::{
38 BleBeaconCreate as BleBeaconCreateProto, Chip as BuiltinProto,
39 };
40 use netsim_proto::model::{ChipCreate as ChipCreateProto, DeviceCreate as DeviceCreateProto};
41 use pdl_runtime::Packet;
42 use protobuf::{Message, MessageField};
43 use std::alloc::System;
44 use std::sync::{Mutex, RwLock};
45 use std::time::{Duration, Instant};
46 use std::{collections::HashMap, ptr::null};
47
48 lazy_static! {
49 static ref EMPTY_ADDRESS: Address = Address::try_from(0u64).unwrap();
50 // A singleton that contains a hash map from chip id to RustBluetoothChip.
51 // It's used by `BeaconChip` to access `RustBluetoothChip` to call send_link_layer_packet().
52 static ref BT_CHIPS: RwLock<HashMap<ChipIdentifier, Mutex<UniquePtr<ffi_bluetooth::RustBluetoothChip>>>> =
53 RwLock::new(HashMap::new());
54 // Used to find beacon chip based on it's id from static methods.
55 pub(crate) static ref BEACON_CHIPS: RwLock<HashMap<ChipIdentifier, Mutex<BeaconChip>>> =
56 RwLock::new(HashMap::new());
57 }
58
59 /// BeaconChip class.
60 pub struct BeaconChip {
61 device_name: String,
62 chip_id: ChipIdentifier,
63 address: Address,
64 advertise_settings: AdvertiseSettings,
65 advertise_data: AdvertiseData,
66 scan_response_data: AdvertiseData,
67 advertise_last: Option<Instant>,
68 advertise_start: Option<Instant>,
69 }
70
71 impl BeaconChip {
new( device_name: String, chip_id: ChipIdentifier, address: String, ) -> Result<Self, String>72 pub fn new(
73 device_name: String,
74 chip_id: ChipIdentifier,
75 address: String,
76 ) -> Result<Self, String> {
77 Ok(BeaconChip {
78 chip_id,
79 device_name: device_name.clone(),
80 address: str_to_addr(&address)?,
81 advertise_settings: AdvertiseSettings::builder().build(),
82 advertise_data: AdvertiseData::builder(device_name.clone(), TxPowerLevel::default())
83 .build()
84 .unwrap(),
85 scan_response_data: AdvertiseData::builder(device_name, TxPowerLevel::default())
86 .build()
87 .unwrap(),
88 advertise_last: None,
89 advertise_start: None,
90 })
91 }
92
from_proto( device_name: String, chip_id: ChipIdentifier, beacon_proto: &BleBeaconCreateProto, ) -> Result<Self, String>93 pub fn from_proto(
94 device_name: String,
95 chip_id: ChipIdentifier,
96 beacon_proto: &BleBeaconCreateProto,
97 ) -> Result<Self, String> {
98 let advertise_settings = AdvertiseSettings::from_proto(&beacon_proto.settings)?;
99 let advertise_data = AdvertiseData::from_proto(
100 device_name.clone(),
101 beacon_proto
102 .settings
103 .tx_power
104 .as_ref()
105 .map(TxPowerLevel::try_from)
106 .transpose()?
107 .unwrap_or_default(),
108 &beacon_proto.adv_data,
109 )?;
110 let scan_response_data = AdvertiseData::from_proto(
111 device_name.clone(),
112 advertise_settings.tx_power_level,
113 &beacon_proto.scan_response,
114 )?;
115
116 let address = if beacon_proto.address == String::default() {
117 // Safe to unwrap here because chip_id is a u32 which is less than 6 bytes
118 u64::from(chip_id.0).try_into().unwrap()
119 } else {
120 str_to_addr(&beacon_proto.address)?
121 };
122
123 Ok(BeaconChip {
124 device_name,
125 chip_id,
126 address,
127 advertise_settings,
128 advertise_data,
129 scan_response_data,
130 advertise_last: None,
131 advertise_start: None,
132 })
133 }
134
send_link_layer_le_packet(&self, packet: &[u8], tx_power: i8)135 pub fn send_link_layer_le_packet(&self, packet: &[u8], tx_power: i8) {
136 let binding = BT_CHIPS.read().unwrap();
137 if let Some(rust_bluetooth_chip) = binding.get(&self.chip_id) {
138 rust_bluetooth_chip
139 .lock()
140 .expect("Failed to acquire lock on RustBluetoothChip")
141 .pin_mut()
142 .send_link_layer_le_packet(packet, tx_power);
143 } else {
144 warn!("Failed to get RustBluetoothChip for unknown chip id: {}", self.chip_id);
145 };
146 }
147 }
148
149 // BEACON_CHIPS has ownership of all the BeaconChips, so we need a separate class to hold the callbacks.
150 // This class will be owned by rootcanal.
151 pub struct BeaconChipCallbacks {
152 chip_id: ChipIdentifier,
153 }
154
155 impl RustBluetoothChipCallbacks for BeaconChipCallbacks {
tick(&mut self)156 fn tick(&mut self) {
157 let guard = BEACON_CHIPS.read().unwrap();
158 let mut beacon = guard.get(&self.chip_id);
159 if beacon.is_none() {
160 error!("could not find bluetooth beacon with chip id {}", self.chip_id);
161 return;
162 }
163 let mut beacon = beacon.unwrap().lock().expect("Failed to acquire lock on BeaconChip");
164
165 if let (Some(start), Some(timeout)) =
166 (beacon.advertise_start, beacon.advertise_settings.timeout)
167 {
168 if start.elapsed() > timeout {
169 return;
170 }
171 }
172
173 if let Some(last) = beacon.advertise_last {
174 if last.elapsed() <= beacon.advertise_settings.mode.interval {
175 return;
176 }
177 } else {
178 beacon.advertise_start = Some(Instant::now())
179 }
180
181 beacon.advertise_last = Some(Instant::now());
182
183 let packet = LeLegacyAdvertisingPduBuilder {
184 advertising_type: beacon.advertise_settings.get_packet_type(),
185 advertising_data: beacon.advertise_data.to_bytes(),
186 advertising_address_type: AddressType::Public,
187 target_address_type: AddressType::Public,
188 source_address: beacon.address,
189 destination_address: *EMPTY_ADDRESS,
190 }
191 .build()
192 .encode_to_vec()
193 .unwrap();
194
195 beacon.send_link_layer_le_packet(&packet, beacon.advertise_settings.tx_power_level.dbm);
196 }
197
receive_link_layer_packet( &mut self, source_address: String, destination_address: String, packet_type: u8, packet: &[u8], )198 fn receive_link_layer_packet(
199 &mut self,
200 source_address: String,
201 destination_address: String,
202 packet_type: u8,
203 packet: &[u8],
204 ) {
205 let guard = BEACON_CHIPS.read().unwrap();
206 let beacon = guard.get(&self.chip_id);
207 if beacon.is_none() {
208 error!("could not find bluetooth beacon with chip id {}", self.chip_id);
209 return;
210 }
211 let beacon = beacon.unwrap().lock().expect("Failed to acquire lock on BeaconChip");
212
213 if beacon.advertise_settings.scannable
214 && destination_address == addr_to_str(beacon.address)
215 && packet_type == u8::from(PacketType::LeScan)
216 {
217 let packet = LeScanResponseBuilder {
218 advertising_address_type: AddressType::Public,
219 source_address: beacon.address,
220 destination_address: beacon.address,
221 scan_response_data: beacon.scan_response_data.to_bytes(),
222 }
223 .build()
224 .encode_to_vec()
225 .unwrap();
226
227 beacon.send_link_layer_le_packet(&packet, beacon.advertise_settings.tx_power_level.dbm);
228 }
229 }
230 }
231
232 /// Add a beacon device in rootcanal.
233 ///
234 /// Called by `devices/chip.rs`.
235 ///
236 /// Similar to `bluetooth_add()`.
237 #[cfg(not(test))]
ble_beacon_add( device_name: String, chip_id: ChipIdentifier, chip_proto: &ChipCreateProto, ) -> Result<FacadeIdentifier, String>238 pub fn ble_beacon_add(
239 device_name: String,
240 chip_id: ChipIdentifier,
241 chip_proto: &ChipCreateProto,
242 ) -> Result<FacadeIdentifier, String> {
243 let beacon_proto = match &chip_proto.chip {
244 Some(BuiltinProto::BleBeacon(beacon_proto)) => beacon_proto,
245 _ => return Err(String::from("failed to create ble beacon: unexpected chip type")),
246 };
247
248 let beacon_chip = BeaconChip::from_proto(device_name, chip_id, beacon_proto)?;
249 if BEACON_CHIPS.write().unwrap().insert(chip_id, Mutex::new(beacon_chip)).is_some() {
250 return Err(format!(
251 "failed to create a bluetooth beacon chip with id {chip_id}: chip id already exists.",
252 ));
253 }
254
255 let callbacks: Box<dyn RustBluetoothChipCallbacks> = Box::new(BeaconChipCallbacks { chip_id });
256 let add_rust_device_result = rust_bluetooth_add(
257 chip_id,
258 callbacks,
259 String::from("beacon"),
260 beacon_proto.address.clone(),
261 );
262 let rust_chip = add_rust_device_result.rust_chip;
263 let facade_id = add_rust_device_result.facade_id;
264 info!("Creating HCI facade_id: {} for chip_id: {}", facade_id, chip_id);
265 BT_CHIPS.write().unwrap().insert(chip_id, Mutex::new(rust_chip));
266
267 Ok(FacadeIdentifier(facade_id))
268 }
269
270 #[cfg(not(test))]
ble_beacon_remove( chip_id: ChipIdentifier, facade_id: FacadeIdentifier, ) -> Result<(), String>271 pub fn ble_beacon_remove(
272 chip_id: ChipIdentifier,
273 facade_id: FacadeIdentifier,
274 ) -> Result<(), String> {
275 let removed_beacon = BEACON_CHIPS.write().unwrap().remove(&chip_id);
276 let removed_radio = BT_CHIPS.write().unwrap().remove(&chip_id);
277 if removed_beacon.is_none() || removed_radio.is_none() {
278 Err(format!("failed to delete ble beacon chip: chip with id {chip_id} does not exist"))
279 } else {
280 ffi_bluetooth::bluetooth_remove_rust_device(facade_id.0);
281 Ok(())
282 }
283 }
284
ble_beacon_patch( facade_id: FacadeIdentifier, chip_id: ChipIdentifier, patch: &BleBeaconProto, ) -> Result<(), String>285 pub fn ble_beacon_patch(
286 facade_id: FacadeIdentifier,
287 chip_id: ChipIdentifier,
288 patch: &BleBeaconProto,
289 ) -> Result<(), String> {
290 let mut guard = BEACON_CHIPS.write().unwrap();
291 let mut beacon = guard
292 .get_mut(&chip_id)
293 .ok_or(format!("could not find bluetooth beacon with chip id {chip_id} for patching"))?
294 .get_mut()
295 .unwrap();
296
297 if patch.address != String::default() {
298 beacon.address = str_to_addr(&patch.address)?;
299 #[cfg(not(test))]
300 ffi_bluetooth::bluetooth_set_rust_device_address(
301 facade_id.0,
302 u64::from(beacon.address).to_le_bytes()[..6].try_into().unwrap(),
303 );
304 }
305
306 if let Some(patch_settings) = patch.settings.as_ref() {
307 if let Some(interval) = patch_settings.interval.as_ref() {
308 beacon.advertise_settings.mode = interval.into();
309 }
310
311 if let Some(tx_power) = patch_settings.tx_power.as_ref() {
312 beacon.advertise_settings.tx_power_level = tx_power.try_into()?
313 }
314
315 beacon.advertise_settings.scannable =
316 patch_settings.scannable || beacon.advertise_settings.scannable;
317
318 if patch_settings.timeout != u64::default() {
319 beacon.advertise_settings.timeout = Some(Duration::from_millis(patch_settings.timeout));
320 }
321 }
322
323 if let Some(patch_adv_data) = patch.adv_data.as_ref() {
324 let mut builder = AdvertiseData::builder(
325 beacon.device_name.clone(),
326 beacon.advertise_settings.tx_power_level,
327 );
328
329 if patch_adv_data.include_device_name || beacon.advertise_data.include_device_name {
330 builder.include_device_name();
331 }
332
333 if patch_adv_data.include_tx_power_level || beacon.advertise_data.include_tx_power_level {
334 builder.include_tx_power_level();
335 }
336
337 if !patch_adv_data.manufacturer_data.is_empty() {
338 builder.manufacturer_data(patch_adv_data.manufacturer_data.clone());
339 } else if let Some(manufacturer_data) = beacon.advertise_data.manufacturer_data.as_ref() {
340 builder.manufacturer_data(manufacturer_data.clone());
341 }
342
343 beacon.advertise_data = builder.build()?;
344 }
345
346 Ok(())
347 }
348
ble_beacon_get( chip_id: ChipIdentifier, _facade_id: FacadeIdentifier, ) -> Result<BleBeaconProto, String>349 pub fn ble_beacon_get(
350 chip_id: ChipIdentifier,
351 _facade_id: FacadeIdentifier,
352 ) -> Result<BleBeaconProto, String> {
353 let guard = BEACON_CHIPS.read().unwrap();
354 let beacon = guard
355 .get(&chip_id)
356 .ok_or(format!("could not get bluetooth beacon with chip id {chip_id}"))?
357 .lock()
358 .expect("Failed to acquire lock on BeaconChip");
359 #[cfg(not(test))]
360 let bt = {
361 let bluetooth_bytes = ffi_bluetooth::bluetooth_get_cxx(_facade_id.0);
362 Some(Bluetooth::parse_from_bytes(&bluetooth_bytes).unwrap())
363 };
364 #[cfg(test)]
365 let bt = Some(netsim_proto::model::chip::Bluetooth::new());
366 Ok(BleBeaconProto {
367 bt: bt.into(),
368 address: addr_to_str(beacon.address),
369 settings: MessageField::some((&beacon.advertise_settings).try_into()?),
370 adv_data: MessageField::some((&beacon.advertise_data).into()),
371 ..Default::default()
372 })
373 }
374
addr_to_str(addr: Address) -> String375 fn addr_to_str(addr: Address) -> String {
376 let bytes = u64::from(addr).to_le_bytes();
377 bytes[..5]
378 .iter()
379 .rfold(format!("{:02x}", bytes[5]), |addr, byte| addr + &format!(":{:02x}", byte))
380 }
381
str_to_addr(addr: &str) -> Result<Address, String>382 fn str_to_addr(addr: &str) -> Result<Address, String> {
383 if addr == String::default() {
384 Ok(*EMPTY_ADDRESS)
385 } else {
386 if addr.len() != 17 {
387 return Err(String::from("failed to parse address: address was not the right length"));
388 }
389 let addr = addr.replace(':', "");
390 u64::from_str_radix(&addr, 16)
391 .map_err(|_| String::from("failed to parse address: invalid hex"))?
392 .try_into()
393 .map_err(|_| {
394 String::from("failed to parse address: address must be smaller than 6 bytes")
395 })
396 }
397 }
398
399 #[cfg(test)]
400 pub mod tests {
401 use std::ops::Add;
402 use std::sync::atomic::{AtomicU32, Ordering};
403 use std::thread;
404
405 use netsim_proto::model::chip::ble_beacon::{
406 advertise_settings::{AdvertiseTxPower as AdvertiseTxPowerProto, Tx_power as TxPowerProto},
407 AdvertiseData as AdvertiseDataProto,
408 };
409
410 use super::*;
411 // using ble_beacon_add from mocked.rs
412 use crate::bluetooth::ble_beacon_add;
413
414 lazy_static! {
415 static ref TEST_GUID_GENERATOR: AtomicU32 = AtomicU32::new(0);
416 }
417
next_id() -> ChipIdentifier418 fn next_id() -> ChipIdentifier {
419 ChipIdentifier(TEST_GUID_GENERATOR.fetch_add(1, Ordering::SeqCst))
420 }
421
new_test_beacon_with_settings(settings: AdvertiseSettingsProto) -> ChipIdentifier422 fn new_test_beacon_with_settings(settings: AdvertiseSettingsProto) -> ChipIdentifier {
423 let id = next_id();
424
425 let add_result = ble_beacon_add(
426 format!("test-device-{:?}", thread::current().id()),
427 id,
428 &ChipCreateProto {
429 name: format!("test-beacon-chip-{:?}", thread::current().id()),
430 chip: Some(BuiltinProto::BleBeacon(BleBeaconCreateProto {
431 address: String::from("00:00:00:00:00:00"),
432 settings: MessageField::some(settings),
433 ..Default::default()
434 })),
435 ..Default::default()
436 },
437 );
438 assert!(add_result.is_ok(), "{}", add_result.unwrap_err());
439
440 id
441 }
442
cleanup_beacon(chip_id: ChipIdentifier)443 fn cleanup_beacon(chip_id: ChipIdentifier) {
444 BEACON_CHIPS.write().unwrap().remove(&chip_id);
445 }
446
447 #[test]
test_beacon_get()448 fn test_beacon_get() {
449 let interval = Duration::from_millis(9999);
450 let settings = AdvertiseSettingsProto {
451 interval: Some(AdvertiseMode::new(interval).try_into().unwrap()),
452 ..Default::default()
453 };
454
455 let id = new_test_beacon_with_settings(settings);
456
457 let beacon = ble_beacon_get(id, FacadeIdentifier(0));
458 assert!(beacon.is_ok(), "{}", beacon.unwrap_err());
459 let beacon = beacon.unwrap();
460
461 let interval_after_get =
462 beacon.settings.interval.as_ref().map(AdvertiseMode::from).unwrap().interval;
463
464 assert_eq!(interval, interval_after_get);
465 cleanup_beacon(id);
466 }
467
468 #[test]
test_beacon_patch()469 fn test_beacon_patch() {
470 let settings = AdvertiseSettingsProto {
471 interval: Some(AdvertiseMode::new(Duration::from_millis(0)).try_into().unwrap()),
472 ..Default::default()
473 };
474
475 let id = new_test_beacon_with_settings(settings);
476
477 let interval = Duration::from_millis(33);
478 let tx_power = TxPowerProto::TxPowerLevel(AdvertiseTxPowerProto::MEDIUM.into());
479 let scannable = true;
480 let patch_result = ble_beacon_patch(
481 FacadeIdentifier(0),
482 id,
483 &BleBeaconProto {
484 settings: MessageField::some(AdvertiseSettingsProto {
485 interval: Some(
486 AdvertiseMode::new(Duration::from_millis(33)).try_into().unwrap(),
487 ),
488 scannable,
489 tx_power: Some(tx_power.clone()),
490 ..Default::default()
491 }),
492 ..Default::default()
493 },
494 );
495 assert!(patch_result.is_ok(), "{}", patch_result.unwrap_err());
496
497 let beacon_proto = ble_beacon_get(id, FacadeIdentifier(0));
498 assert!(beacon_proto.is_ok(), "{}", beacon_proto.unwrap_err());
499 let beacon_proto = beacon_proto.unwrap();
500 let interval_after_patch =
501 beacon_proto.settings.interval.as_ref().map(AdvertiseMode::from).unwrap().interval;
502
503 assert_eq!(interval, interval_after_patch);
504 assert_eq!(tx_power, *beacon_proto.settings.tx_power.as_ref().unwrap());
505 assert_eq!(scannable, beacon_proto.settings.scannable);
506 cleanup_beacon(id);
507 }
508
509 #[test]
test_beacon_patch_default()510 fn test_beacon_patch_default() {
511 let settings =
512 AdvertiseSettingsProto { timeout: 1234, scannable: true, ..Default::default() };
513
514 let id = new_test_beacon_with_settings(settings.clone());
515
516 let patch_result = ble_beacon_patch(FacadeIdentifier(0), id, &BleBeaconProto::default());
517 assert!(patch_result.is_ok(), "{}", patch_result.unwrap_err());
518
519 let beacon_proto = ble_beacon_get(id, FacadeIdentifier(0));
520 assert!(beacon_proto.is_ok(), "{}", beacon_proto.unwrap_err());
521 let beacon_proto = beacon_proto.unwrap();
522
523 let settings_after_patch = beacon_proto.settings.unwrap();
524 assert_eq!(settings.timeout, settings_after_patch.timeout);
525 assert_eq!(settings.scannable, settings_after_patch.scannable);
526 }
527
528 #[test]
test_str_to_addr_succeeds()529 fn test_str_to_addr_succeeds() {
530 let addr = str_to_addr("be:ac:12:34:00:0f");
531 assert_eq!(Address::try_from(0xbe_ac_12_34_00_0f).unwrap(), addr.unwrap());
532 }
533
534 #[test]
test_empty_str_to_addr_succeeds()535 fn test_empty_str_to_addr_succeeds() {
536 let addr = str_to_addr("00:00:00:00:00:00");
537 assert_eq!(Address::try_from(0).unwrap(), addr.unwrap());
538 }
539
540 #[test]
test_str_to_addr_fails()541 fn test_str_to_addr_fails() {
542 let addr = str_to_addr("hi mom!");
543 assert!(addr.is_err());
544 }
545
546 #[test]
test_invalid_str_to_addr_fails()547 fn test_invalid_str_to_addr_fails() {
548 let addr = str_to_addr("56:78:9a:bc:de:fg");
549 assert!(addr.is_err());
550 }
551
552 #[test]
test_long_str_to_addr_fails()553 fn test_long_str_to_addr_fails() {
554 let addr = str_to_addr("55:55:55:55:55:55:55:55");
555 assert!(addr.is_err());
556 }
557
558 #[test]
test_short_str_to_addr_fails()559 fn test_short_str_to_addr_fails() {
560 let addr = str_to_addr("ab:cd");
561 assert!(addr.is_err());
562 }
563
564 #[test]
test_addr_to_str_succeeds()565 fn test_addr_to_str_succeeds() {
566 let addr: u64 = 0xbe_ac_12_34_00_0f;
567 assert_eq!("be:ac:12:34:00:0f", addr_to_str(addr.try_into().unwrap()))
568 }
569
570 #[test]
test_empty_addr_to_str_succeeds()571 fn test_empty_addr_to_str_succeeds() {
572 let addr: u64 = 0;
573 assert_eq!("00:00:00:00:00:00", addr_to_str(addr.try_into().unwrap()))
574 }
575
576 #[test]
test_small_addr_to_str_succeeds()577 fn test_small_addr_to_str_succeeds() {
578 let addr: u64 = 123;
579 assert_eq!("00:00:00:00:00:7b", addr_to_str(addr.try_into().unwrap()))
580 }
581 }
582