Skip to main content

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, HashSet};
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 operation: {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        match parse_int::<u8>(parts[1]) {
168            Ok(bis_index) => {
169                let entry = map.entry(ith_big).or_insert(BisSync::no_sync());
170                if let Err(e) = entry.synchronize_to_index(bis_index) {
171                    eprintln!("Failed to set sync to BIS index: {e:?}");
172                }
173            }
174            Err(_) if parts[1] == "OFF" => {
175                map.insert(ith_big, BisSync::no_sync());
176            }
177            Err(e) => {
178                eprintln!("{e:?} - BIS index should be a number from 1-31, ignoring {}", parts[1]);
179            }
180        }
181    }
182    map
183}
184
185/// Converts a passcode string into a 16-byte broadcast code.
186/// The string is UTF-8 encoded and then padded with zeros on the right to a
187/// total length of 16 bytes. This result is a little-endian byte array
188/// equivalent to a 128-bit value.
189fn passcode_to_broadcast_code(passcode: &str) -> Result<[u8; 16], String> {
190    if passcode.is_empty() {
191        return Err("invalid broadcast code: passcode cannot be empty".to_string());
192    }
193    let code = passcode.as_bytes();
194    if code.len() > 16 {
195        return Err(format!(
196            "invalid broadcast code: '{}'. should be at max length 16, but was {}",
197            passcode,
198            code.len()
199        ));
200    }
201    let mut broadcast_code = [0u8; 16];
202    broadcast_code[..code.len()].copy_from_slice(code);
203    Ok(broadcast_code)
204}
205
206impl<T: bt_gatt::GattTypes + 'static, R: GetPeerAddr> CommandRunner for AssistantDebug<T, R>
207where
208    <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
209{
210    type Set = AssistantCmd;
211
212    fn run(
213        &self,
214        cmd: Self::Set,
215        args: Vec<String>,
216    ) -> impl futures::Future<Output = Result<(), impl std::error::Error>> {
217        let help_subcommands: HashSet<&str> = HashSet::from(["help", "-h", "--help"]);
218        async move {
219            if args.len() >= 1 && help_subcommands.contains(args[0].as_str()) {
220                eprintln!("usage: {}", cmd.help_simple());
221                return Ok(());
222            }
223            match cmd {
224                AssistantCmd::Info => {
225                    let known = self.assistant.known_broadcast_sources();
226                    println!("Known Broadcast Sources:");
227                    for (id, s) in known {
228                        println!("PeerId ({id}), source: {s:?}");
229                    }
230                }
231                AssistantCmd::Connect => {
232                    if self.connected_peer.lock().is_some() {
233                        eprintln!(
234                            "peer already connected. Call `disconnect` first: {}",
235                            AssistantCmd::Disconnect.help_simple()
236                        );
237                        return Ok(());
238                    }
239                    if args.len() != 1 {
240                        eprintln!("usage: {}", AssistantCmd::Connect.help_simple());
241                        return Ok(());
242                    }
243
244                    let Ok(peer_id) = parse_peer_id(&args[0]) else {
245                        eprintln!("invalid peer id: {}", args[0]);
246                        return Ok(());
247                    };
248
249                    let peer = self.assistant.connect_to_scan_delegator(peer_id).await;
250                    match peer {
251                        Ok(peer) => {
252                            *self.connected_peer.lock() = Some(Arc::new(peer));
253                        }
254                        Err(e) => {
255                            eprintln!("failed to connect to scan delegator: {e:?}");
256                        }
257                    };
258                }
259                AssistantCmd::Disconnect => {
260                    if self.connected_peer.lock().take().is_none() {
261                        eprintln!("not connected to a scan delegator");
262                    }
263                }
264                AssistantCmd::SendBroadcastCode => {
265                    if args.len() != 2 {
266                        eprintln!("usage: {}", AssistantCmd::SendBroadcastCode.help_simple());
267                        return Ok(());
268                    }
269
270                    let Ok(broadcast_id) = parse_broadcast_id(&args[0]) else {
271                        eprintln!("invalid broadcast id: {}", args[0]);
272                        return Ok(());
273                    };
274
275                    let broadcast_code = match passcode_to_broadcast_code(&args[1]) {
276                        Ok(code) => code,
277                        Err(e) => {
278                            eprintln!("{e:?}");
279                            return Ok(());
280                        }
281                    };
282
283                    self.with_peer(|peer| async move {
284                        peer.send_broadcast_code(broadcast_id, broadcast_code).await
285                    })
286                    .await;
287                }
288                AssistantCmd::AddBroadcastSource => {
289                    if args.len() < 2 {
290                        eprintln!("usage: {}", AssistantCmd::AddBroadcastSource.help_simple());
291                        return Ok(());
292                    }
293
294                    let Ok(broadcast_source_pid) = parse_peer_id(&args[0]) else {
295                        eprintln!("invalid broadcast id: {}", args[0]);
296                        return Ok(());
297                    };
298
299                    let pa_sync: PaSync = match args[1].parse() {
300                        Ok(sync) => sync,
301                        Err(e) => {
302                            eprintln!("invalid pa_sync: {e:?}");
303                            return Ok(());
304                        }
305                    };
306
307                    let bis_sync =
308                        if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() };
309
310                    self.with_peer(|peer| async move {
311                        peer.add_broadcast_source(
312                            broadcast_source_pid,
313                            &self.peer_addr_getter,
314                            pa_sync,
315                            bis_sync,
316                        )
317                        .await
318                    })
319                    .await;
320                }
321                AssistantCmd::UpdatePaSync => {
322                    if args.len() < 2 {
323                        eprintln!("usage: {}", AssistantCmd::UpdatePaSync.help_simple());
324                        return Ok(());
325                    }
326
327                    let Ok(broadcast_id) = parse_broadcast_id(&args[0]) else {
328                        eprintln!("invalid broadcast id: {}", args[0]);
329                        return Ok(());
330                    };
331
332                    let pa_sync: PaSync = match args[1].parse() {
333                        Ok(sync) => sync,
334                        Err(e) => {
335                            eprintln!("invalid pa_sync: {e:?}");
336                            return Ok(());
337                        }
338                    };
339
340                    let bis_sync =
341                        if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() };
342
343                    self.with_peer(|peer| async move {
344                        peer.update_broadcast_source_sync(broadcast_id, pa_sync, bis_sync).await
345                    })
346                    .await;
347                }
348                AssistantCmd::RemoveBroadcastSource => {
349                    if args.len() != 1 {
350                        eprintln!("usage: {}", AssistantCmd::RemoveBroadcastSource.help_simple());
351                        return Ok(());
352                    }
353
354                    let Ok(broadcast_id) = parse_broadcast_id(&args[0]) else {
355                        eprintln!("invalid broadcast id: {}", args[0]);
356                        return Ok(());
357                    };
358
359                    self.with_peer(|peer| async move {
360                        peer.remove_broadcast_source(broadcast_id).await
361                    })
362                    .await;
363                }
364                AssistantCmd::RemoteScanStarted => {
365                    self.with_peer(|peer: Arc<Peer<T>>| async move {
366                        peer.inform_remote_scan_started().await
367                    })
368                    .await;
369                }
370                AssistantCmd::RemoteScanStopped => {
371                    self.with_peer(|peer| async move { peer.inform_remote_scan_stopped().await })
372                        .await;
373                }
374                #[cfg(feature = "debug")]
375                AssistantCmd::ForceDiscoverBroadcastSource => {
376                    if args.len() != 4 {
377                        eprintln!(
378                            "usage: {}",
379                            AssistantCmd::ForceDiscoverBroadcastSource.help_simple()
380                        );
381                        return Ok(());
382                    }
383
384                    let Ok(peer_id) = parse_peer_id(&args[0]) else {
385                        eprintln!("invalid peer id: {}", args[0]);
386                        return Ok(());
387                    };
388
389                    let Ok(address) = parse_bd_addr(&args[1]) else {
390                        eprintln!("invalid address: {}", args[1]);
391                        return Ok(());
392                    };
393
394                    let address_type: AddressType = match args[2].parse() {
395                        Ok(t) => t,
396                        Err(e) => {
397                            eprintln!("invalid address type: {e:?}");
398                            return Ok(());
399                        }
400                    };
401
402                    let Ok(raw_ad_sid) = parse_int::<u8>(&args[3]) else {
403                        eprintln!("invalid advertising sid: {}", args[3]);
404                        return Ok(());
405                    };
406                    let advertising_sid = AdvertisingSetId(raw_ad_sid);
407
408                    match self.assistant.force_discover_broadcast_source(
409                        peer_id,
410                        address,
411                        address_type,
412                        advertising_sid,
413                    ) {
414                        Ok(source) => {
415                            println!("broadcast source after additional info: {source:?}")
416                        }
417                        Err(e) => {
418                            eprintln!("failed to enter in broadcast source information: {e:?}")
419                        }
420                    }
421                }
422                #[cfg(feature = "debug")]
423                AssistantCmd::ForceDiscoverSourceMetadata => {
424                    if args.len() < 2 {
425                        eprintln!(
426                            "usage: {}",
427                            AssistantCmd::ForceDiscoverSourceMetadata.help_simple()
428                        );
429                        return Ok(());
430                    }
431
432                    let Ok(peer_id) = parse_peer_id(&args[0]) else {
433                        eprintln!("invalid peer id: {}", args[0]);
434                        return Ok(());
435                    };
436
437                    let mut all_big_metadata = Vec::new();
438                    for i in 1..args.len() {
439                        let raw_metadata: Vec<u8> = args[i]
440                            .split(',')
441                            .map(|t| parse_int(t))
442                            .filter_map(Result::ok)
443                            .collect();
444
445                        if raw_metadata.len() > 0 {
446                            let (decoded_metadata, consumed_len) =
447                                Metadata::decode_all(raw_metadata.as_slice());
448                            if consumed_len != raw_metadata.len() {
449                                eprintln!("Metadata length is not valid");
450                                return Ok(());
451                            }
452                            all_big_metadata.push(
453                                decoded_metadata.into_iter().filter_map(Result::ok).collect(),
454                            );
455                        } else {
456                            all_big_metadata.push(vec![]);
457                        }
458                    }
459
460                    match self
461                        .assistant
462                        .force_discover_broadcast_source_metadata(peer_id, all_big_metadata)
463                    {
464                        Ok(source) => println!("broadcast source with metadata: {source:?}"),
465                        Err(e) => eprintln!("failed to enter in broadcast source metadata: {e:?}"),
466                    }
467                }
468                #[cfg(feature = "debug")]
469                AssistantCmd::ForceDiscoverEmptySourceMetadata => {
470                    if args.len() != 2 {
471                        eprintln!(
472                            "usage: {}",
473                            AssistantCmd::ForceDiscoverEmptySourceMetadata.help_simple()
474                        );
475                        return Ok(());
476                    }
477
478                    let Ok(peer_id) = parse_peer_id(&args[0]) else {
479                        eprintln!("invalid peer id: {}", args[0]);
480                        return Ok(());
481                    };
482
483                    let Ok(num_big) = parse_int::<usize>(&args[1]) else {
484                        eprintln!("invalid # of bigs: {}", args[1]);
485                        return Ok(());
486                    };
487
488                    let mut all_big_metadata = Vec::new();
489                    for _i in 0..num_big {
490                        all_big_metadata.push(vec![]);
491                    }
492
493                    match self
494                        .assistant
495                        .force_discover_broadcast_source_metadata(peer_id, all_big_metadata)
496                    {
497                        Ok(source) => println!("broadcast source with metadata: {source:?}"),
498                        Err(e) => {
499                            eprintln!("failed to enter in empty broadcast source metadata: {e:?}")
500                        }
501                    }
502                }
503                #[cfg(not(feature = "debug"))]
504                c => eprintln!("unknown command: {c:?}"),
505            }
506            Ok::<(), Error>(())
507        }
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514
515    #[test]
516    fn test_parse_peer_id() {
517        // In hex string.
518        assert_eq!(parse_peer_id("0x678abc").expect("should be ok"), PeerId(0x678abc));
519        // Decimal equivalent.
520        assert_eq!(parse_peer_id("6785724").expect("should be ok"), PeerId(0x678abc));
521
522        // Invalid peer id.
523        let _ = parse_peer_id("0123zzz").expect_err("should fail");
524    }
525
526    #[test]
527    fn test_parse_bd_addr() {
528        assert_eq!(
529            parse_bd_addr("3c:80:f1:ed:32:2c").expect("should be ok"),
530            [0x2c, 0x32, 0xed, 0xf1, 0x80, 0x3c]
531        );
532        // Address with 5 parts is invalid.
533        let _ = parse_bd_addr("3c:80:f1:ed:32").expect_err("should fail");
534        // Address with 6 parts but one of them empty is invalid.
535        let _ = parse_bd_addr("3c:80:f1::32:2c").expect_err("should fail");
536        let _ = parse_bd_addr(":80:f1:ed:32:2c").expect_err("should fail");
537        let _ = parse_bd_addr("3c:80:f1:ed:32:").expect_err("should fail");
538        // Address not delimited by : is invalid.
539        let _ = parse_bd_addr("3c.80.f1.ed.32.2c").expect_err("should fail");
540    }
541
542    #[test]
543    fn test_parse_broadcast_id() {
544        assert_eq!(parse_broadcast_id("0xABCD").expect("should work"), 0xABCD.try_into().unwrap());
545        assert_eq!(parse_broadcast_id("123456").expect("should work"), 123456.try_into().unwrap());
546
547        // Invalid string cannot be parsed.
548        let _ = parse_broadcast_id("0xABYZ").expect_err("should fail");
549
550        // Broadcast ID is actually a 3 byte long number.
551        let _ = parse_broadcast_id("16777216").expect_err("should fail");
552    }
553
554    #[test]
555    fn test_parse_bis_sync() {
556        // Basic case with multiple BIGs and BIS indices.
557        let bis_sync = parse_bis_sync("0-1,0-2,1-1");
558        assert_eq!(bis_sync.len(), 2);
559        assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
560        assert_eq!(bis_sync.get(&1), Some(&BisSync::sync(vec![1]).unwrap()));
561
562        // Case with "OFF" to disable sync for a BIG.
563        let bis_sync = parse_bis_sync("0-1,1-OFF,0-2");
564        assert_eq!(bis_sync.len(), 2);
565        assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
566        assert_eq!(bis_sync.get(&1), Some(&BisSync::no_sync()));
567
568        // Case where sync is set and then turned off for the same BIG.
569        let bis_sync = parse_bis_sync("0-5,0-OFF");
570        assert_eq!(bis_sync.len(), 1);
571        assert_eq!(bis_sync.get(&0), Some(&BisSync::no_sync()));
572
573        // Will ignore invalid values.
574        let bis_sync = parse_bis_sync("0-1,0-2,1:1,1-1-1,");
575        assert_eq!(bis_sync.len(), 1);
576        assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
577
578        let bis_sync = parse_bis_sync("hellothisistoallynotvalid");
579        assert_eq!(bis_sync.len(), 0);
580    }
581
582    #[test]
583    fn test_passcode_to_broadcast_code() {
584        // UTF-8 string that is less than 16 bytes.
585        // Source of truth test case from Bluetooth Spec.
586        let code = "Børne House";
587        let expected = [
588            0x42, 0xc3, 0xb8, 0x72, 0x6e, 0x65, 0x20, 0x48, 0x6f, 0x75, 0x73, 0x65, 0x00, 0x00,
589            0x00, 0x00,
590        ];
591        let actual = passcode_to_broadcast_code(code).expect("should succeed");
592        assert_eq!(actual, expected);
593        assert_eq!(u128::from_le_bytes(actual), 0x00000000_6573756F_4820656E_72B8C342);
594
595        // Valid ASCII passcode, exactly 16 bytes.
596        let code = "1234567890123456";
597        let expected = [
598            0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34,
599            0x35, 0x36,
600        ];
601        assert_eq!(passcode_to_broadcast_code(code).unwrap(), expected);
602
603        // Invalid passcode, over 16 bytes.
604        let code = "12345678901234567";
605        assert!(passcode_to_broadcast_code(code).is_err());
606
607        // Empty passcode should be an error.
608        let code = "";
609        assert!(passcode_to_broadcast_code(code).is_err());
610    }
611}