bt_broadcast_assistant/
debug.rs

1// Copyright 2024 Google LLC
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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        // TODO(http://b/433285146): Once PA scanning is implemented, remove bottom 3 commands.
48        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
111/// Attempt to parse a string into an integer.  If the string begins with 0x,
112/// treat the rest of the string as a hex value, otherwise treat it as decimal.
113pub(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"))]
134/// Returns the bd address in little endian ordering.
135pub 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        // In hex string.
489        assert_eq!(parse_peer_id("0x678abc").expect("should be ok"), PeerId(0x678abc));
490        // Decimal equivalent.
491        assert_eq!(parse_peer_id("6785724").expect("should be ok"), PeerId(0x678abc));
492
493        // Invalid peer id.
494        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        // Address with 5 parts is invalid.
504        let _ = parse_bd_addr("3c:80:f1:ed:32").expect_err("should fail");
505        // Address with 6 parts but one of them empty is invalid.
506        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        // Address not delimited by : is invalid.
510        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        // Invalid string cannot be parsed.
519        let _ = parse_broadcast_id("0xABYZ").expect_err("should fail");
520
521        // Broadcast ID is actually a 3 byte long number.
522        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        // Will ignore invalid values.
533        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}