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