1use bt_bap::types::BroadcastId;
6use bt_bass::client::error::Error as BassClientError;
7use bt_bass::client::event::Event as BassEvent;
8use bt_bass::types::{BisSync, PaSync, SubgroupIndex};
9
10#[cfg(any(test, feature = "debug"))]
11use bt_common::core::ltv::LtValue;
12#[cfg(any(test, feature = "debug"))]
13use bt_common::core::AddressType;
14use bt_common::core::AdvertisingSetId;
15use bt_common::debug_command::CommandRunner;
16use bt_common::debug_command::CommandSet;
17use bt_common::gen_commandset;
18#[cfg(any(test, feature = "debug"))]
19use bt_common::generic_audio::metadata_ltv::Metadata;
20use bt_common::PeerId;
21use bt_gatt::pii::GetPeerAddr;
22use std::collections::{HashMap, HashSet};
23
24use futures::stream::FusedStream;
25use futures::Future;
26use futures::Stream;
27use num::Num;
28use parking_lot::Mutex;
29use std::num::ParseIntError;
30use std::sync::Arc;
31
32use crate::assistant::event::*;
33use crate::assistant::peer::Peer;
34use crate::assistant::Error;
35use crate::*;
36
37gen_commandset! {
38 AssistantCmd {
39 Info = ("info", [], [], "Print information from broadcast assistant"),
40 Connect = ("connect", [], ["peer_id"], "Attempt connection to scan delegator"),
41 Disconnect = ("disconnect", [], [], "Disconnect from connected scan delegator"),
42 SendBroadcastCode = ("set-broadcast-code", [], ["broadcast_id", "broadcast_code"], "Attempt to send decryption key for a particular broadcast source to the scan delegator"),
43 AddBroadcastSource = ("add-broadcast-source", [], ["source_peer_id", "advertising_sid", "PaSyncOff|PaSyncPast|PaSyncNoPast", "[bis_sync]"], "Attempt to add a particular broadcast source to the scan delegator"),
44 UpdatePaSync = ("update-pa-sync", [], ["broadcast_id", "PaSyncOff|PaSyncPast|PaSyncNoPast", "[bis_sync]"], "Attempt to update the scan delegator's desired pa sync to a particular broadcast source"),
45 RemoveBroadcastSource = ("remove-broadcast-source", [], ["broadcast_id"], "Attempt to remove a particular broadcast source to the scan delegator"),
46 RemoteScanStarted = ("inform-scan-started", [], [], "Inform the scan delegator that we have started scanning on behalf of it"),
47 RemoteScanStopped = ("inform-scan-stopped", [], [], "Inform the scan delegator that we have stopped scanning on behalf of it"),
48 ForceDiscoverBroadcastSource = ("force-discover-broadcast-source", [], ["source_peer_id", "address", "Public|Random", "advertising_sid"], "Force the broadcast assistant to become aware of the provided broadcast source"),
50 ForceDiscoverSourceMetadata = ("force-discover-source-metadata", [], ["source_peer_id", "advertising_sid", "metadata_big1", "[metadata_big_n]..."], "Force the broadcast assistant to become aware of the provided metadata, each BIG's metadata is comma separated"),
51 ForceDiscoverEmptySourceMetadata = ("force-discover-empty-source-metadata", [], ["source_peer_id", "advertising_sid", "num_big"], "Force the broadcast assistant to become aware of the provided empty metadata, as many as # BIGs specified"),
52 }
53}
54
55pub struct AssistantDebug<T: bt_gatt::GattTypes, R: GetPeerAddr> {
56 assistant: BroadcastAssistant<T>,
57 connected_peer: Mutex<Option<Arc<Peer<T>>>>,
58 peer_addr_getter: R,
59}
60
61impl<T: bt_gatt::GattTypes + 'static, R: GetPeerAddr> AssistantDebug<T, R> {
62 pub fn new(central: T::Central, peer_addr_getter: R) -> Self
63 where
64 <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
65 {
66 Self {
67 assistant: BroadcastAssistant::<T>::new(central),
68 connected_peer: Mutex::new(None),
69 peer_addr_getter,
70 }
71 }
72
73 pub fn start(&mut self) -> Result<EventStream<T>, Error> {
74 let event_stream = self.assistant.start()?;
75 Ok(event_stream)
76 }
77
78 pub fn look_for_scan_delegators(&mut self) -> Result<T::ScanResultStream, Error> {
79 self.assistant.scan_for_scan_delegators()
80 }
81
82 pub fn take_connected_peer_event_stream(
83 &mut self,
84 ) -> Result<impl Stream<Item = Result<BassEvent, BassClientError>> + FusedStream, Error> {
85 let mut lock = self.connected_peer.lock();
86 let Some(peer_arc) = lock.as_mut() else {
87 return Err(Error::Generic(format!("not connected to any scan delegator peer")));
88 };
89 let Some(peer) = Arc::get_mut(peer_arc) else {
90 return Err(Error::Generic(format!(
91 "cannot get mutable peer reference, it is shared elsewhere"
92 )));
93 };
94 peer.take_event_stream().map_err(|e| Error::Generic(format!("{e:?}")))
95 }
96
97 async fn with_peer<F, Fut>(&self, f: F)
98 where
99 F: FnOnce(Arc<Peer<T>>) -> Fut,
100 Fut: Future<Output = Result<(), crate::assistant::peer::Error>>,
101 {
102 let Some(peer) = self.connected_peer.lock().clone() else {
103 eprintln!("not connected to a scan delegator");
104 return;
105 };
106 if let Err(e) = f(peer).await {
107 eprintln!("failed to perform operation: {e:?}");
108 }
109 }
110}
111
112pub(crate) fn parse_int<N>(input: &str) -> Result<N, ParseIntError>
115where
116 N: Num<FromStrRadixErr = ParseIntError>,
117{
118 if input.starts_with("0x") {
119 N::from_str_radix(&input[2..], 16)
120 } else {
121 N::from_str_radix(input, 10)
122 }
123}
124
125pub fn parse_peer_id(input: &str) -> Result<PeerId, String> {
126 let raw_id = match parse_int(input) {
127 Err(_) => return Err(format!("falied to parse int from {input}")),
128 Ok(i) => i,
129 };
130
131 Ok(PeerId(raw_id))
132}
133
134#[cfg(any(test, feature = "debug"))]
135pub fn parse_bd_addr(input: &str) -> Result<[u8; 6], String> {
137 let mut tokens: Vec<u8> =
138 input.split(':').map(|t| u8::from_str_radix(t, 16)).filter_map(Result::ok).collect();
139 if tokens.len() != 6 {
140 return Err(format!("failed to parse bd address from {input}"));
141 }
142 tokens.reverse();
143 tokens.try_into().map_err(|e| format!("{e:?}"))
144}
145
146fn parse_broadcast_id(input: &str) -> Result<BroadcastId, String> {
147 let raw_id: u32 = match parse_int(input) {
148 Err(_) => return Err(format!("falied to parse int from {input}")),
149 Ok(i) => i,
150 };
151 raw_id.try_into().map_err(|e| format!("{e:?}"))
152}
153
154fn parse_bis_sync(input: &str) -> HashMap<SubgroupIndex, BisSync> {
155 let mut map = HashMap::new();
156 for t in input.split(',') {
157 let parts: Vec<_> = t.split('-').collect();
158 if parts.len() != 2 {
159 eprintln!(
160 "invalid big-bis sync info {t}. should be in <Ith_BIG>-<BIS_INDEX> format, will be ignored"
161 );
162 continue;
163 }
164 let Ok(ith_big) = parse_int(parts[0]) else {
165 eprintln!("Failed to parse big index from '{}', ignoring.", parts[0]);
166 continue;
167 };
168 match parse_int::<u8>(parts[1]) {
169 Ok(bis_index) => {
170 let entry = map.entry(ith_big).or_insert(BisSync::no_sync());
171 if let Err(e) = entry.synchronize_to_index(bis_index) {
172 eprintln!("Failed to set sync to BIS index: {e:?}");
173 }
174 }
175 Err(_) if parts[1] == "OFF" => {
176 map.insert(ith_big, BisSync::no_sync());
177 }
178 Err(e) => {
179 eprintln!("{e:?} - BIS index should be a number from 1-31, ignoring {}", parts[1]);
180 }
181 }
182 }
183 map
184}
185
186fn passcode_to_broadcast_code(passcode: &str) -> Result<[u8; 16], String> {
191 if passcode.is_empty() {
192 return Err("invalid broadcast code: passcode cannot be empty".to_string());
193 }
194 let code = passcode.as_bytes();
195 if code.len() > 16 {
196 return Err(format!(
197 "invalid broadcast code: '{}'. should be at max length 16, but was {}",
198 passcode,
199 code.len()
200 ));
201 }
202 let mut broadcast_code = [0u8; 16];
203 broadcast_code[..code.len()].copy_from_slice(code);
204 Ok(broadcast_code)
205}
206
207impl<T: bt_gatt::GattTypes + 'static, R: GetPeerAddr> CommandRunner for AssistantDebug<T, R>
208where
209 <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
210{
211 type Set = AssistantCmd;
212
213 fn run(
214 &self,
215 cmd: Self::Set,
216 args: Vec<String>,
217 ) -> impl futures::Future<Output = Result<(), impl std::error::Error>> {
218 let help_subcommands: HashSet<&str> = HashSet::from(["help", "-h", "--help"]);
219 async move {
220 if args.len() >= 1 && help_subcommands.contains(args[0].as_str()) {
221 eprintln!("usage: {}", cmd.help_simple());
222 return Ok(());
223 }
224 match cmd {
225 AssistantCmd::Info => {
226 let known = self.assistant.known_broadcast_sources();
227 println!("Known Broadcast Sources:");
228 for (id, s) in known {
229 println!("({id:?}): {s:?}");
230 }
231 }
232 AssistantCmd::Connect => {
233 if self.connected_peer.lock().is_some() {
234 eprintln!(
235 "peer already connected. Call `disconnect` first: {}",
236 AssistantCmd::Disconnect.help_simple()
237 );
238 return Ok(());
239 }
240 if args.len() != 1 {
241 eprintln!("usage: {}", AssistantCmd::Connect.help_simple());
242 return Ok(());
243 }
244
245 let Ok(peer_id) = parse_peer_id(&args[0]) else {
246 eprintln!("invalid peer id: {}", args[0]);
247 return Ok(());
248 };
249
250 let peer = self.assistant.connect_to_scan_delegator(peer_id).await;
251 match peer {
252 Ok(peer) => {
253 *self.connected_peer.lock() = Some(Arc::new(peer));
254 }
255 Err(e) => {
256 eprintln!("failed to connect to scan delegator: {e:?}");
257 }
258 };
259 }
260 AssistantCmd::Disconnect => {
261 if self.connected_peer.lock().take().is_none() {
262 eprintln!("not connected to a scan delegator");
263 }
264 }
265 AssistantCmd::SendBroadcastCode => {
266 if args.len() != 2 {
267 eprintln!("usage: {}", AssistantCmd::SendBroadcastCode.help_simple());
268 return Ok(());
269 }
270
271 let Ok(broadcast_id) = parse_broadcast_id(&args[0]) else {
272 eprintln!("invalid broadcast id: {}", args[0]);
273 return Ok(());
274 };
275
276 let broadcast_code = match passcode_to_broadcast_code(&args[1]) {
277 Ok(code) => code,
278 Err(e) => {
279 eprintln!("{e:?}");
280 return Ok(());
281 }
282 };
283
284 self.with_peer(|peer| async move {
285 peer.send_broadcast_code(broadcast_id, broadcast_code).await
286 })
287 .await;
288 }
289 AssistantCmd::AddBroadcastSource => {
290 if args.len() < 3 {
291 eprintln!("usage: {}", AssistantCmd::AddBroadcastSource.help_simple());
292 return Ok(());
293 }
294
295 let Ok(source_peer_id) = parse_peer_id(&args[0]) else {
296 eprintln!("invalid peer id: {}", args[0]);
297 return Ok(());
298 };
299
300 let Ok(sid_val) = parse_int::<u8>(&args[1]) else {
301 eprintln!("invalid advertising sid: {}", args[1]);
302 return Ok(());
303 };
304 let advertising_sid = AdvertisingSetId(sid_val);
305
306 let pa_sync: PaSync = match args[2].parse() {
307 Ok(sync) => sync,
308 Err(e) => {
309 eprintln!("invalid pa_sync: {e:?}");
310 return Ok(());
311 }
312 };
313
314 let bis_sync =
315 if args.len() == 4 { parse_bis_sync(&args[3]) } else { HashMap::new() };
316
317 self.with_peer(|peer| async move {
318 peer.add_broadcast_source(
319 source_peer_id,
320 advertising_sid,
321 &self.peer_addr_getter,
322 pa_sync,
323 bis_sync,
324 )
325 .await
326 })
327 .await;
328 }
329 AssistantCmd::UpdatePaSync => {
330 if args.len() < 2 {
331 eprintln!("usage: {}", AssistantCmd::UpdatePaSync.help_simple());
332 return Ok(());
333 }
334
335 let Ok(broadcast_id) = parse_broadcast_id(&args[0]) else {
336 eprintln!("invalid broadcast id: {}", args[0]);
337 return Ok(());
338 };
339
340 let pa_sync: PaSync = match args[1].parse() {
341 Ok(sync) => sync,
342 Err(e) => {
343 eprintln!("invalid pa_sync: {e:?}");
344 return Ok(());
345 }
346 };
347
348 let bis_sync =
349 if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() };
350
351 self.with_peer(|peer| async move {
352 peer.update_broadcast_source_sync(broadcast_id, pa_sync, bis_sync).await
353 })
354 .await;
355 }
356 AssistantCmd::RemoveBroadcastSource => {
357 if args.len() != 1 {
358 eprintln!("usage: {}", AssistantCmd::RemoveBroadcastSource.help_simple());
359 return Ok(());
360 }
361
362 let Ok(broadcast_id) = parse_broadcast_id(&args[0]) else {
363 eprintln!("invalid broadcast id: {}", args[0]);
364 return Ok(());
365 };
366
367 self.with_peer(|peer| async move {
368 peer.remove_broadcast_source(broadcast_id).await
369 })
370 .await;
371 }
372 AssistantCmd::RemoteScanStarted => {
373 self.with_peer(|peer: Arc<Peer<T>>| async move {
374 peer.inform_remote_scan_started().await
375 })
376 .await;
377 }
378 AssistantCmd::RemoteScanStopped => {
379 self.with_peer(|peer| async move { peer.inform_remote_scan_stopped().await })
380 .await;
381 }
382 #[cfg(feature = "debug")]
383 AssistantCmd::ForceDiscoverBroadcastSource => {
384 if args.len() != 4 {
385 eprintln!(
386 "usage: {}",
387 AssistantCmd::ForceDiscoverBroadcastSource.help_simple()
388 );
389 return Ok(());
390 }
391
392 let Ok(source_peer_id) = parse_peer_id(&args[0]) else {
393 eprintln!("invalid peer id: {}", args[0]);
394 return Ok(());
395 };
396
397 let Ok(address) = parse_bd_addr(&args[1]) else {
398 eprintln!("invalid address: {}", args[1]);
399 return Ok(());
400 };
401
402 let address_type: AddressType = match args[2].parse() {
403 Ok(t) => t,
404 Err(e) => {
405 eprintln!("invalid address type: {e:?}");
406 return Ok(());
407 }
408 };
409
410 let Ok(raw_ad_sid) = parse_int::<u8>(&args[3]) else {
411 eprintln!("invalid advertising sid: {}", args[3]);
412 return Ok(());
413 };
414 let advertising_sid = AdvertisingSetId(raw_ad_sid);
415
416 match self.assistant.force_discover_broadcast_source(
417 source_peer_id,
418 address,
419 address_type,
420 advertising_sid,
421 ) {
422 Ok(source) => {
423 println!("broadcast source after additional info: {source:?}")
424 }
425 Err(e) => {
426 eprintln!("failed to enter in broadcast source information: {e:?}")
427 }
428 }
429 }
430 #[cfg(feature = "debug")]
431 AssistantCmd::ForceDiscoverSourceMetadata => {
432 if args.len() < 3 {
433 eprintln!(
434 "usage: {}",
435 AssistantCmd::ForceDiscoverSourceMetadata.help_simple()
436 );
437 return Ok(());
438 }
439
440 let Ok(source_peer_id) = parse_peer_id(&args[0]) else {
441 eprintln!("invalid peer id: {}", args[0]);
442 return Ok(());
443 };
444
445 let Ok(raw_ad_sid) = parse_int::<u8>(&args[1]) else {
446 eprintln!("invalid advertising sid: {}", args[1]);
447 return Ok(());
448 };
449 let advertising_sid = AdvertisingSetId(raw_ad_sid);
450
451 let mut all_big_metadata = Vec::new();
452 for i in 2..args.len() {
453 let raw_metadata: Vec<u8> = args[i]
454 .split(',')
455 .map(|t| parse_int(t))
456 .filter_map(Result::ok)
457 .collect();
458
459 if raw_metadata.len() > 0 {
460 let (decoded_metadata, consumed_len) =
461 Metadata::decode_all(raw_metadata.as_slice());
462 if consumed_len != raw_metadata.len() {
463 eprintln!("Metadata length is not valid");
464 return Ok(());
465 }
466 all_big_metadata.push(
467 decoded_metadata.into_iter().filter_map(Result::ok).collect(),
468 );
469 } else {
470 all_big_metadata.push(vec![]);
471 }
472 }
473
474 match self.assistant.force_discover_broadcast_source_metadata(
475 source_peer_id,
476 advertising_sid,
477 all_big_metadata,
478 ) {
479 Ok(source) => println!("broadcast source with metadata: {source:?}"),
480 Err(e) => eprintln!("failed to enter in broadcast source metadata: {e:?}"),
481 }
482 }
483 #[cfg(feature = "debug")]
484 AssistantCmd::ForceDiscoverEmptySourceMetadata => {
485 if args.len() != 3 {
486 eprintln!(
487 "usage: {}",
488 AssistantCmd::ForceDiscoverEmptySourceMetadata.help_simple()
489 );
490 return Ok(());
491 }
492
493 let Ok(source_peer_id) = parse_peer_id(&args[0]) else {
494 eprintln!("invalid peer id: {}", args[0]);
495 return Ok(());
496 };
497
498 let Ok(raw_ad_sid) = parse_int::<u8>(&args[1]) else {
499 eprintln!("invalid advertising sid: {}", args[1]);
500 return Ok(());
501 };
502 let advertising_sid = AdvertisingSetId(raw_ad_sid);
503
504 let Ok(num_big) = parse_int::<usize>(&args[2]) else {
505 eprintln!("invalid # of bigs: {}", args[2]);
506 return Ok(());
507 };
508
509 let mut all_big_metadata = Vec::new();
510 for _i in 0..num_big {
511 all_big_metadata.push(vec![]);
512 }
513
514 match self.assistant.force_discover_broadcast_source_metadata(
515 source_peer_id,
516 advertising_sid,
517 all_big_metadata,
518 ) {
519 Ok(source) => println!("broadcast source with metadata: {source:?}"),
520 Err(e) => {
521 eprintln!("failed to enter in empty broadcast source metadata: {e:?}")
522 }
523 }
524 }
525 #[cfg(not(feature = "debug"))]
526 c => eprintln!("unknown command: {c:?}"),
527 }
528 Ok::<(), Error>(())
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_parse_peer_id() {
539 assert_eq!(parse_peer_id("0x678abc").expect("should be ok"), PeerId(0x678abc));
541 assert_eq!(parse_peer_id("6785724").expect("should be ok"), PeerId(0x678abc));
543
544 let _ = parse_peer_id("0123zzz").expect_err("should fail");
546 }
547
548 #[test]
549 fn test_parse_bd_addr() {
550 assert_eq!(
551 parse_bd_addr("3c:80:f1:ed:32:2c").expect("should be ok"),
552 [0x2c, 0x32, 0xed, 0xf1, 0x80, 0x3c]
553 );
554 let _ = parse_bd_addr("3c:80:f1:ed:32").expect_err("should fail");
556 let _ = parse_bd_addr("3c:80:f1::32:2c").expect_err("should fail");
558 let _ = parse_bd_addr(":80:f1:ed:32:2c").expect_err("should fail");
559 let _ = parse_bd_addr("3c:80:f1:ed:32:").expect_err("should fail");
560 let _ = parse_bd_addr("3c.80.f1.ed.32.2c").expect_err("should fail");
562 }
563
564 #[test]
565 fn test_parse_broadcast_id() {
566 assert_eq!(parse_broadcast_id("0xABCD").expect("should work"), 0xABCD.try_into().unwrap());
567 assert_eq!(parse_broadcast_id("123456").expect("should work"), 123456.try_into().unwrap());
568
569 let _ = parse_broadcast_id("0xABYZ").expect_err("should fail");
571
572 let _ = parse_broadcast_id("16777216").expect_err("should fail");
574 }
575
576 #[test]
577 fn test_parse_bis_sync() {
578 let bis_sync = parse_bis_sync("0-1,0-2,1-1");
580 assert_eq!(bis_sync.len(), 2);
581 assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
582 assert_eq!(bis_sync.get(&1), Some(&BisSync::sync(vec![1]).unwrap()));
583
584 let bis_sync = parse_bis_sync("0-1,1-OFF,0-2");
586 assert_eq!(bis_sync.len(), 2);
587 assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
588 assert_eq!(bis_sync.get(&1), Some(&BisSync::no_sync()));
589
590 let bis_sync = parse_bis_sync("0-5,0-OFF");
592 assert_eq!(bis_sync.len(), 1);
593 assert_eq!(bis_sync.get(&0), Some(&BisSync::no_sync()));
594
595 let bis_sync = parse_bis_sync("0-1,0-2,1:1,1-1-1,");
597 assert_eq!(bis_sync.len(), 1);
598 assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
599
600 let bis_sync = parse_bis_sync("hellothisistoallynotvalid");
601 assert_eq!(bis_sync.len(), 0);
602 }
603
604 #[test]
605 fn test_passcode_to_broadcast_code() {
606 let code = "Børne House";
609 let expected = [
610 0x42, 0xc3, 0xb8, 0x72, 0x6e, 0x65, 0x20, 0x48, 0x6f, 0x75, 0x73, 0x65, 0x00, 0x00,
611 0x00, 0x00,
612 ];
613 let actual = passcode_to_broadcast_code(code).expect("should succeed");
614 assert_eq!(actual, expected);
615 assert_eq!(u128::from_le_bytes(actual), 0x00000000_6573756F_4820656E_72B8C342);
616
617 let code = "1234567890123456";
619 let expected = [
620 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34,
621 0x35, 0x36,
622 ];
623 assert_eq!(passcode_to_broadcast_code(code).unwrap(), expected);
624
625 let code = "12345678901234567";
627 assert!(passcode_to_broadcast_code(code).is_err());
628
629 let code = "";
631 assert!(passcode_to_broadcast_code(code).is_err());
632 }
633}