rust/bt-broadcast-assistant: Improve debug commands and internal APIs

Parse debug arguments using FromStr instead of requiring raw enum values.
Improve internal types for clarity.

Change-Id: I05b1380e59d544716cd0ec63cd64cdb8cce2c3da
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/2640
Reviewed-by: Marie Janssen <jamuraa@google.com>
Bluetooth-Auto-Submit: Dayeong Lee <dayeonglee@google.com>
Commit-Queue: Dayeong Lee <dayeonglee@google.com>
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs
index f7b3f5b..7519501 100644
--- a/rust/bt-bass/src/client.rs
+++ b/rust/bt-bass/src/client.rs
@@ -5,11 +5,11 @@
 pub mod error;
 pub mod event;
 
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::sync::Arc;
 
-use futures::Future;
 use futures::stream::{BoxStream, FusedStream, SelectAll, Stream, StreamExt};
+use futures::Future;
 use log::warn;
 use parking_lot::Mutex;
 
@@ -26,22 +26,6 @@
 
 const READ_CHARACTERISTIC_BUFFER_SIZE: usize = 255;
 
-/// Index into the vector of BIG subgroups. Valid value range is [0 to len of
-/// BIG vector).
-pub type SubgroupIndex = u8;
-
-/// Synchronization information for BIG groups to tell which
-/// BISes it should sync to.
-pub type BigToBisSync = HashSet<(SubgroupIndex, BisIndex)>;
-
-pub fn big_to_bis_sync_indices(info: &BigToBisSync) -> HashMap<SubgroupIndex, Vec<BisIndex>> {
-    let mut sync_map = HashMap::new();
-    for (ith_group, bis_index) in info.iter() {
-        sync_map.entry(*ith_group).or_insert(Vec::new()).push(*bis_index);
-    }
-    sync_map
-}
-
 /// Keeps track of Source_ID and Broadcast_ID that are associated together.
 /// Source_ID is assigned by the BASS server to a Broadcast Receive State
 /// characteristic. If the remote peer with the BASS server autonomously
@@ -321,7 +305,7 @@
         broadcast_id: BroadcastId,
         pa_sync: PaSync,
         pa_interval: Option<PaInterval>,
-        bis_sync: Option<BigToBisSync>,
+        bis_sync: Option<HashMap<SubgroupIndex, BisSync>>,
         metadata_map: Option<HashMap<SubgroupIndex, Vec<Metadata>>>,
     ) -> Result<(), Error> {
         let op = {
@@ -330,12 +314,10 @@
                 .ok_or(Error::UnknownBroadcastSource(broadcast_id))?;
 
             // Update BIS_Sync param for BIGs if applicable.
-            if let Some(m) = bis_sync {
-                let sync_map = big_to_bis_sync_indices(&m);
-
+            if let Some(sync_map) = bis_sync {
                 for (big_index, group) in state.subgroups.iter_mut().enumerate() {
-                    if let Some(bis_indices) = sync_map.get(&(big_index as u8)) {
-                        group.bis_sync.set_sync(bis_indices).map_err(|e| Error::Packet(e))?;
+                    if let Some(bis_sync) = sync_map.get(&(big_index as u8)) {
+                        group.bis_sync = bis_sync.clone();
                     }
                 }
             }
@@ -420,15 +402,15 @@
 
     use assert_matches::assert_matches;
     use futures::executor::block_on;
-    use futures::{FutureExt, pin_mut};
+    use futures::{pin_mut, FutureExt};
 
-    use bt_common::Uuid;
     use bt_common::core::AdvertisingSetId;
-    use bt_gatt::Characteristic;
+    use bt_common::Uuid;
     use bt_gatt::test_utils::*;
     use bt_gatt::types::{
         AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
     };
+    use bt_gatt::Characteristic;
 
     const RECEIVE_STATE_1_HANDLE: Handle = Handle(1);
     const RECEIVE_STATE_2_HANDLE: Handle = Handle(2);
@@ -833,7 +815,7 @@
             vec![
                 0x03, 0x11, 0x00,                    // opcode, source id, pa sync
                 0xFF, 0xFF, 0x02,                    // pa sync, pa interval, num of subgroups
-                0x17, 0x00, 0x00, 0x00,              // bis sync (0th subgroup)
+                0x15, 0x00, 0x00, 0x00,              // bis sync (0th subgroup)
                 0x02, 0x01, 0x09,                    // metadata len, metadata
                 0xFF, 0xFF, 0xFF, 0xFF,              // bis sync (1th subgroup)
                 0x05, 0x04, 0x04, 0x65, 0x6E, 0x67,  // metadata len, metadata
@@ -845,7 +827,7 @@
             BroadcastId::try_from(0x11).unwrap(),
             PaSync::DoNotSync,
             None,
-            Some(HashSet::from([(0, 1), (0, 2), (0, 3), (0, 5)])),
+            Some(HashMap::from([(0, BisSync::sync(vec![1, 3, 5]).unwrap())])),
             Some(HashMap::from([
                 (0, vec![Metadata::BroadcastAudioImmediateRenderingFlag]),
                 (1, vec![Metadata::Language("eng".to_string())]),
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs
index 22baa25..3f8e2d3 100644
--- a/rust/bt-bass/src/types.rs
+++ b/rust/bt-bass/src/types.rs
@@ -7,7 +7,8 @@
 use bt_common::core::{AddressType, AdvertisingSetId, PaInterval};
 use bt_common::generic_audio::metadata_ltv::*;
 use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
-use bt_common::{Uuid, decodable_enum};
+use bt_common::{decodable_enum, Uuid};
+use std::str::FromStr;
 
 pub const ADDRESS_BYTE_SIZE: usize = 6;
 const NUM_SUBGROUPS_BYTE_SIZE: usize = 1;
@@ -21,6 +22,10 @@
 
 pub type SourceId = u8;
 
+/// Index into the vector of BIG subgroups. Valid value range is [0 to len of
+/// BIG vector).
+pub type SubgroupIndex = u8;
+
 /// BIS index value of a particular BIS. Valid value range is [1 to len of BIS]
 pub type BisIndex = u8;
 
@@ -500,29 +505,46 @@
     }
 }
 
+impl FromStr for PaSync {
+    type Err = PacketError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "PaSyncOff" => Ok(PaSync::DoNotSync),
+            "PaSyncPast" => Ok(PaSync::SyncPastAvailable),
+            "PaSyncNoPast" => Ok(PaSync::SyncPastUnavailable),
+            _ => Err(PacketError::InvalidParameter(format!("invalid pa_sync: {s}"))),
+        }
+    }
+}
+
 /// 4-octet bitfield. Bit 0-30 = BIS_index[1-31]
 /// 0x00000000: 0b0 = Do not synchronize to BIS_index[x]
 /// 0xxxxxxxxx: 0b1 = Synchronize to BIS_index[x]
 /// 0xFFFFFFFF: means No preference if used in BroadcastAudioScanControlPoint,
 ///             Failed to sync if used in ReceiveState.
 #[derive(Clone, Debug, PartialEq)]
-pub struct BisSync(pub u32);
+pub struct BisSync(u32);
 
 impl BisSync {
     const BYTE_SIZE: usize = 4;
     const NO_PREFERENCE: u32 = 0xFFFFFFFF;
 
-    /// Updates whether or not a particular BIS should be set to synchronize or
-    /// not synchronize.
+    /// Creates a new BisSync that doens't synchronzie to any BISes.
+    pub fn no_sync() -> BisSync {
+        BisSync(0)
+    }
+
+    /// Updates the specified BIS index to be synchronized.
+    /// Doesn't touch the synchronize value of other BIS indices.
     ///
     /// # Arguments
     ///
     /// * `bis_index` - BIS index as defined in the spec. Range should be [1,
-    ///   31]
-    /// * `should_sync` - Whether or not to synchronize
-    fn set_sync_for_index(&mut self, bis_index: BisIndex) -> Result<(), PacketError> {
+    ///   31]. The specified BIS index will be set to synchronized (0b1).
+    pub fn synchronize_to_index(&mut self, bis_index: BisIndex) -> Result<(), PacketError> {
         if bis_index < 1 || bis_index > 31 {
-            return Err(PacketError::InvalidParameter(format!("Invalid BIS index ({bis_index})")));
+            return Err(PacketError::OutOfRange);
         }
         let bit_mask = 0b1 << (bis_index - 1);
 
@@ -532,24 +554,18 @@
         Ok(())
     }
 
-    /// Clears previous BIS_Sync params and synchronizes to specified BIS
-    /// indices. If the BIS index list is empty, no preference value is
-    /// used.
+    /// Creates a BisSync value with the specified BIS indices set to be
+    /// synchronized.
     ///
     /// # Arguments
     ///
-    /// * `sync_map` - Map of BIS index to whether or not it should be
-    ///   synchronized
-    pub fn set_sync(&mut self, bis_indices: &Vec<BisIndex>) -> Result<(), PacketError> {
-        if bis_indices.is_empty() {
-            self.0 = Self::NO_PREFERENCE;
-            return Ok(());
-        }
-        self.0 = 0;
+    /// * `bis_indices` - A vector of BIS indices to synchronize to.
+    pub fn sync(bis_indices: Vec<BisIndex>) -> Result<Self, PacketError> {
+        let mut new_sync = Self::no_sync();
         for bis_index in bis_indices {
-            self.set_sync_for_index(*bis_index)?;
+            new_sync.synchronize_to_index(bis_index)?;
         }
-        Ok(())
+        Ok(new_sync)
     }
 }
 
@@ -559,6 +575,12 @@
     }
 }
 
+impl From<BisSync> for u32 {
+    fn from(bis_sync: BisSync) -> u32 {
+        bis_sync.0
+    }
+}
+
 #[derive(Clone, Debug, PartialEq)]
 pub struct BigSubgroup {
     pub(crate) bis_sync: BisSync,
@@ -620,7 +642,7 @@
             return Err(PacketError::BufferTooSmall);
         }
 
-        buf[0..4].copy_from_slice(&self.bis_sync.0.to_le_bytes());
+        buf[0..4].copy_from_slice(&u32::from(self.bis_sync.clone()).to_le_bytes());
         let metadata_len = self
             .metadata
             .iter()
@@ -1049,24 +1071,44 @@
     }
 
     #[test]
-    fn bis_sync() {
-        let mut bis_sync = BisSync::default();
-        assert_eq!(bis_sync, BisSync(BisSync::NO_PREFERENCE));
+    fn bis_sync_sync() {
+        let bis_sync = BisSync::sync(vec![1, 6, 31]).expect("should succeed");
+        assert_eq!(u32::from(bis_sync), 0x40000021);
 
-        bis_sync.set_sync(&vec![1, 6, 31]).expect("should succeed");
-        assert_eq!(bis_sync, BisSync(0x40000021));
-
-        bis_sync.set_sync(&vec![]).expect("should succeed");
-        assert_eq!(bis_sync, BisSync::default());
+        let bis_sync_empty = BisSync::sync(vec![]).expect("should succeed");
+        assert_eq!(u32::from(bis_sync_empty), 0);
     }
 
     #[test]
     fn invalid_bis_sync() {
-        let mut bis_sync = BisSync::default();
+        BisSync::sync(vec![0]).expect_err("should fail");
+        BisSync::sync(vec![32]).expect_err("should fail");
+    }
 
-        bis_sync.set_sync(&vec![0]).expect_err("should fail");
+    #[test]
+    fn synchronize_to_index() {
+        let mut bis_sync = BisSync::no_sync();
+        assert_eq!(u32::from(bis_sync.clone()), 0);
 
-        bis_sync.set_sync(&vec![32]).expect_err("should fail");
+        bis_sync.synchronize_to_index(1).expect("should succeed");
+        assert_eq!(u32::from(bis_sync.clone()), 0x1);
+
+        bis_sync.synchronize_to_index(31).expect("should succeed");
+        assert_eq!(u32::from(bis_sync.clone()), 0x40000001);
+
+        bis_sync.synchronize_to_index(0).expect_err("should fail");
+        bis_sync.synchronize_to_index(32).expect_err("should fail");
+    }
+
+    #[test]
+    fn pa_sync_from_str() {
+        let sync = PaSync::from_str("PaSyncOff").expect("should succeed");
+        assert_eq!(sync, PaSync::DoNotSync);
+        let sync = PaSync::from_str("PaSyncPast").expect("should succeed");
+        assert_eq!(sync, PaSync::SyncPastAvailable);
+        let sync = PaSync::from_str("PaSyncNoPast").expect("should succeed");
+        assert_eq!(sync, PaSync::SyncPastUnavailable);
+        PaSync::from_str("invalid").expect_err("should fail");
     }
 
     #[test]
diff --git a/rust/bt-broadcast-assistant/src/assistant.rs b/rust/bt-broadcast-assistant/src/assistant.rs
index 3fd08cb..b8b50af 100644
--- a/rust/bt-broadcast-assistant/src/assistant.rs
+++ b/rust/bt-broadcast-assistant/src/assistant.rs
@@ -4,6 +4,7 @@
 
 use parking_lot::Mutex;
 use std::collections::HashMap;
+use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 use thiserror::Error;
 
@@ -101,7 +102,7 @@
 pub struct BroadcastAssistant<T: bt_gatt::GattTypes> {
     central: T::Central,
     broadcast_sources: Arc<DiscoveredBroadcastSources>,
-    scan_stream: Option<T::ScanResultStream>,
+    broadcast_source_scan_started: Arc<AtomicBool>,
 }
 
 impl<T: bt_gatt::GattTypes + 'static> BroadcastAssistant<T> {
@@ -109,11 +110,10 @@
     // for broadcast source scanning. Clients must use the `start`
     // method to poll the event stream for scan results.
     pub fn new(central: T::Central) -> Self {
-        let scan_result_stream = central.scan(&Self::scan_filters());
         Self {
             central,
             broadcast_sources: DiscoveredBroadcastSources::new(),
-            scan_stream: Some(scan_result_stream),
+            broadcast_source_scan_started: Arc::new(AtomicBool::new(false)),
         }
     }
 
@@ -130,17 +130,33 @@
     /// poll. Upper layer can call methods on BroadcastAssistant based on the
     /// events it sees.
     pub fn start(&mut self) -> Result<EventStream<T>, Error> {
-        if self.scan_stream.is_none() {
+        if self.is_started() {
             return Err(Error::AlreadyStarted);
         }
-        Ok(EventStream::<T>::new(self.scan_stream.take().unwrap(), self.broadcast_sources.clone()))
+        let scan_result_stream = self.central.scan(&Self::scan_filters());
+        self.broadcast_source_scan_started.store(true, Ordering::Relaxed);
+        Ok(EventStream::<T>::new(
+            scan_result_stream,
+            self.broadcast_sources.clone(),
+            self.broadcast_source_scan_started.clone(),
+        ))
     }
 
-    pub fn scan_for_scan_delegators(&mut self) -> T::ScanResultStream {
+    /// Returns whether or not Broadcast Assistant has started.
+    fn is_started(&self) -> bool {
+        self.broadcast_source_scan_started.load(Ordering::Relaxed)
+    }
+
+    pub fn scan_for_scan_delegators(&mut self) -> Result<T::ScanResultStream, Error> {
+        if self.is_started() {
+            return Err(Error::Generic(format!(
+                "Cannot scan for scan delegators while scanning for broadcast sources"
+            )));
+        }
         // Scan for service data with Broadcast Audio Scan Service UUID to look
         // for Broadcast Sink collocated with the Scan Delegator (see BAP spec v1.0.1
         // Section 3.9.2 for details).
-        self.central.scan(&vec![Filter::HasServiceData(BROADCAST_AUDIO_SCAN_SERVICE).into()])
+        Ok(self.central.scan(&vec![Filter::HasServiceData(BROADCAST_AUDIO_SCAN_SERVICE).into()]))
     }
 
     pub async fn connect_to_scan_delegator(&self, peer_id: PeerId) -> Result<Peer<T>, Error>
@@ -172,17 +188,13 @@
         &self,
         peer_id: PeerId,
         address: [u8; 6],
-        raw_address_type: u8,
-        raw_advertising_sid: u8,
+        address_type: bt_common::core::AddressType,
+        advertising_sid: bt_common::core::AdvertisingSetId,
     ) -> Result<BroadcastSource, Error> {
-        use bt_common::core::{AddressType, AdvertisingSetId};
         let broadcast_source = BroadcastSource {
             address: Some(address),
-            address_type: Some(
-                AddressType::try_from(raw_address_type)
-                    .map_err(|e| Error::Generic(e.to_string()))?,
-            ),
-            advertising_sid: Some(AdvertisingSetId(raw_advertising_sid)),
+            address_type: Some(address_type),
+            advertising_sid: Some(advertising_sid),
             broadcast_id: None,
             pa_interval: None,
             endpoint: None,
@@ -196,27 +208,13 @@
     pub fn force_discover_broadcast_source_metadata(
         &self,
         peer_id: PeerId,
-        raw_metadata: Vec<Vec<u8>>,
+        big_metadata: Vec<Vec<bt_common::generic_audio::metadata_ltv::Metadata>>,
     ) -> Result<BroadcastSource, Error> {
         use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastIsochronousGroup};
-        use bt_common::core::ltv::LtValue;
         use bt_common::core::CodecId;
-        use bt_common::generic_audio::metadata_ltv::Metadata;
 
         let mut big = Vec::new();
-        for bytes in raw_metadata {
-            let metadata = {
-                if bytes.len() > 0 {
-                    let (decoded_metadata, consumed_len) = Metadata::decode_all(bytes.as_slice());
-                    if consumed_len != bytes.len() {
-                        return Err(Error::Generic("Metadata length is not valid".to_string()));
-                    }
-                    decoded_metadata.into_iter().filter_map(Result::ok).collect()
-                } else {
-                    vec![]
-                }
-            };
-
+        for metadata in big_metadata {
             let group = BroadcastIsochronousGroup {
                 codec_id: CodecId::Assigned(bt_common::core::CodingFormat::ALawLog), // mock.
                 codec_specific_configs: vec![],
@@ -261,6 +259,7 @@
 
     use bt_bap::types::*;
     use bt_common::core::{AddressType, AdvertisingSetId};
+    use bt_common::generic_audio::metadata_ltv::Metadata;
     use bt_gatt::test_utils::{FakeCentral, FakeClient, FakeTypes};
 
     use crate::assistant::peer::tests::fake_bass_service;
@@ -324,10 +323,16 @@
     #[test]
     fn start_stream() {
         let mut assistant = BroadcastAssistant::<FakeTypes>::new(FakeCentral::new());
-        let _ = assistant.start().expect("can start stream");
+        let stream = assistant.start().expect("can start stream");
 
         // Stream can only be started once.
+        assert!(assistant.is_started());
         assert!(assistant.start().is_err());
+
+        // After the stream is dropped, it can be started again.
+        drop(stream);
+        assert!(!assistant.is_started());
+        assert!(assistant.start().is_ok());
     }
 
     #[test]
@@ -349,4 +354,34 @@
         };
         let _ = res.expect("should be ok");
     }
+
+    #[test]
+    fn force_discover_broadcast_source_test() {
+        let assistant = BroadcastAssistant::<FakeTypes>::new(FakeCentral::new());
+        let peer_id = PeerId(1);
+        let address = [1, 2, 3, 4, 5, 6];
+        let address_type = AddressType::Public;
+        let sid = AdvertisingSetId(1);
+
+        let source =
+            assistant.force_discover_broadcast_source(peer_id, address, address_type, sid).unwrap();
+
+        assert_eq!(source.address, Some(address));
+        assert_eq!(source.address_type, Some(address_type));
+        assert_eq!(source.advertising_sid, Some(sid));
+    }
+
+    #[test]
+    fn force_discover_broadcast_source_metadata_test() {
+        let assistant = BroadcastAssistant::<FakeTypes>::new(FakeCentral::new());
+        let peer_id = PeerId(1);
+        let metadata = vec![vec![Metadata::BroadcastAudioImmediateRenderingFlag]];
+
+        let source =
+            assistant.force_discover_broadcast_source_metadata(peer_id, metadata.clone()).unwrap();
+
+        let endpoint = source.endpoint.unwrap();
+        assert_eq!(endpoint.big.len(), 1);
+        assert_eq!(endpoint.big[0].metadata, metadata[0]);
+    }
 }
diff --git a/rust/bt-broadcast-assistant/src/assistant/event.rs b/rust/bt-broadcast-assistant/src/assistant/event.rs
index 7290e47..741c04c 100644
--- a/rust/bt-broadcast-assistant/src/assistant/event.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/event.rs
@@ -4,18 +4,20 @@
 
 use core::pin::Pin;
 use futures::stream::{FusedStream, Stream, StreamExt};
+use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 use std::task::Poll;
 
 use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastId};
-use bt_common::PeerId;
+use bt_common::core::AdvertisingSetId;
 use bt_common::packet_encoding::Decodable;
 use bt_common::packet_encoding::Error as PacketError;
+use bt_common::PeerId;
 use bt_gatt::central::{AdvertisingDatum, ScanResult};
 
 use crate::assistant::{
-    BASIC_AUDIO_ANNOUNCEMENT_SERVICE, BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE,
-    DiscoveredBroadcastSources, Error,
+    DiscoveredBroadcastSources, Error, BASIC_AUDIO_ANNOUNCEMENT_SERVICE,
+    BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE,
 };
 use crate::types::BroadcastSource;
 
@@ -33,17 +35,20 @@
     terminated: bool,
 
     broadcast_sources: Arc<DiscoveredBroadcastSources>,
+    broadcast_source_scan_started: Arc<AtomicBool>,
 }
 
 impl<T: bt_gatt::GattTypes> EventStream<T> {
     pub(crate) fn new(
         scan_result_stream: T::ScanResultStream,
         broadcast_sources: Arc<DiscoveredBroadcastSources>,
+        broadcast_source_scan_started: Arc<AtomicBool>,
     ) -> Self {
         Self {
             scan_result_stream: Box::pin(scan_result_stream),
             terminated: false,
             broadcast_sources,
+            broadcast_source_scan_started,
         }
     }
 
@@ -67,10 +72,19 @@
                 source.get_or_insert(BroadcastSource::default()).with_endpoint(base);
             }
         }
+        if let Some(src) = &mut source {
+            src.advertising_sid = Some(AdvertisingSetId(scan_result.advertising_sid));
+        }
         Ok(source)
     }
 }
 
+impl<T: bt_gatt::GattTypes> Drop for EventStream<T> {
+    fn drop(&mut self) {
+        self.broadcast_source_scan_started.store(false, Ordering::Relaxed);
+    }
+}
+
 impl<T: bt_gatt::GattTypes> FusedStream for EventStream<T> {
     fn is_terminated(&self) -> bool {
         self.terminated
@@ -122,6 +136,7 @@
             }
             None | Some(Err(_)) => {
                 self.terminated = true;
+                self.broadcast_source_scan_started.store(false, Ordering::Relaxed);
                 Poll::Ready(Some(Err(Error::CentralScanTerminated)))
             }
         }
@@ -143,9 +158,14 @@
     fn setup_stream() -> (EventStream<FakeTypes>, ScannedResultStream) {
         let fake_scan_result_stream = ScannedResultStream::new();
         let broadcast_sources = DiscoveredBroadcastSources::new();
+        let broadcast_source_scan_started = Arc::new(AtomicBool::new(false));
 
         (
-            EventStream::<FakeTypes>::new(fake_scan_result_stream.clone(), broadcast_sources),
+            EventStream::<FakeTypes>::new(
+                fake_scan_result_stream.clone(),
+                broadcast_sources,
+                broadcast_source_scan_started,
+            ),
             fake_scan_result_stream,
         )
     }
@@ -210,7 +230,7 @@
                 BASIC_AUDIO_ANNOUNCEMENT_SERVICE,
                 base_data.clone(),
             )],
-            advertising_sid: 0,
+            advertising_sid: 1,
         }));
 
         // Expect the stream to send out broadcast source found event since information
@@ -218,8 +238,9 @@
         let Poll::Ready(Some(Ok(event))) = stream.poll_next_unpin(&mut noop_cx) else {
             panic!("should have received event");
         };
-        assert_matches!(event, Event::FoundBroadcastSource{peer, ..} => {
-            assert_eq!(peer, broadcast_source_pid)
+        assert_matches!(event, Event::FoundBroadcastSource{peer, source} => {
+            assert_eq!(peer, broadcast_source_pid);
+            assert_eq!(source.advertising_sid, Some(AdvertisingSetId(1)));
         });
 
         assert!(stream.poll_next_unpin(&mut noop_cx).is_pending());
@@ -233,7 +254,7 @@
                 BASIC_AUDIO_ANNOUNCEMENT_SERVICE,
                 base_data.clone(),
             )],
-            advertising_sid: 0,
+            advertising_sid: 1,
         }));
 
         // Shouldn't have gotten the event again since the information remained the
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs
index 5c3b5b5..73029d8 100644
--- a/rust/bt-broadcast-assistant/src/assistant/peer.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -11,15 +11,16 @@
 use bt_bap::types::BroadcastId;
 use bt_bass::client::error::Error as BassClientError;
 use bt_bass::client::event::Event as BassEvent;
-use bt_bass::client::{BigToBisSync, BroadcastAudioScanServiceClient};
+use bt_bass::client::BroadcastAudioScanServiceClient;
 #[cfg(any(test, feature = "debug"))]
 use bt_bass::types::BroadcastReceiveState;
-use bt_bass::types::PaSync;
+use bt_bass::types::{BisSync, PaSync};
 use bt_common::core::PaInterval;
 use bt_common::packet_encoding::Error as PacketError;
 use bt_common::PeerId;
 #[cfg(any(test, feature = "debug"))]
 use bt_gatt::types::Handle;
+use std::collections::HashMap;
 
 use crate::assistant::DiscoveredBroadcastSources;
 
@@ -104,7 +105,7 @@
         source_peer_id: PeerId,
         address_lookup: &impl GetPeerAddr,
         pa_sync: PaSync,
-        bis_sync: BigToBisSync,
+        bis_sync: HashMap<u8, BisSync>,
     ) -> Result<(), Error> {
         let mut broadcast_source = self
             .broadcast_sources
@@ -148,7 +149,7 @@
         &self,
         broadcast_id: BroadcastId,
         pa_sync: PaSync,
-        bis_sync: BigToBisSync,
+        bis_sync: HashMap<u8, BisSync>,
     ) -> Result<(), Error> {
         let pa_interval = self
             .broadcast_sources
@@ -199,7 +200,7 @@
     use assert_matches::assert_matches;
     use bt_gatt::pii::StaticPeerAddr;
     use futures::{pin_mut, FutureExt};
-    use std::collections::HashSet;
+    use std::collections::HashMap;
     use std::task::Poll;
 
     use bt_common::core::{AddressType, AdvertisingSetId};
@@ -285,7 +286,7 @@
                 PeerId(1001),
                 &FakeGetPeerAddr,
                 PaSync::SyncPastUnavailable,
-                HashSet::new(),
+                HashMap::new(),
             );
             pin_mut!(fut);
             let polled = fut.poll_unpin(&mut noop_cx);
@@ -307,7 +308,7 @@
                 PeerId(1001),
                 &address_lookup,
                 PaSync::SyncPastUnavailable,
-                HashSet::new(),
+                HashMap::new(),
             );
             pin_mut!(fut);
             let polled = fut.poll_unpin(&mut noop_cx);
@@ -322,7 +323,7 @@
                 PeerId(1001),
                 &address_lookup,
                 PaSync::SyncPastUnavailable,
-                HashSet::new(),
+                HashMap::new(),
             );
             pin_mut!(fut);
             let polled = fut.poll_unpin(&mut noop_cx);
diff --git a/rust/bt-broadcast-assistant/src/debug.rs b/rust/bt-broadcast-assistant/src/debug.rs
index 6a4f308..1fe4f9e 100644
--- a/rust/bt-broadcast-assistant/src/debug.rs
+++ b/rust/bt-broadcast-assistant/src/debug.rs
@@ -5,20 +5,26 @@
 use bt_bap::types::BroadcastId;
 use bt_bass::client::error::Error as BassClientError;
 use bt_bass::client::event::Event as BassEvent;
-use bt_bass::client::BigToBisSync;
-use bt_bass::types::PaSync;
+use bt_bass::types::{BisSync, PaSync, SubgroupIndex};
+
+#[cfg(any(test, feature = "debug"))]
+use bt_common::core::ltv::LtValue;
+#[cfg(any(test, feature = "debug"))]
+use bt_common::core::{AddressType, AdvertisingSetId};
 use bt_common::debug_command::CommandRunner;
 use bt_common::debug_command::CommandSet;
 use bt_common::gen_commandset;
+#[cfg(any(test, feature = "debug"))]
+use bt_common::generic_audio::metadata_ltv::Metadata;
 use bt_common::PeerId;
 use bt_gatt::pii::GetPeerAddr;
+use std::collections::HashMap;
 
 use futures::stream::FusedStream;
 use futures::Future;
 use futures::Stream;
 use num::Num;
 use parking_lot::Mutex;
-use std::collections::HashSet;
 use std::num::ParseIntError;
 use std::sync::Arc;
 
@@ -33,13 +39,13 @@
         Connect = ("connect", [], ["peer_id"], "Attempt connection to scan delegator"),
         Disconnect = ("disconnect", [], [], "Disconnect from connected scan delegator"),
         SendBroadcastCode = ("set-broadcast-code", [], ["broadcast_id", "broadcast_code"], "Attempt to send decryption key for a particular broadcast source to the scan delegator"),
-        AddBroadcastSource = ("add-broadcast-source", [], ["broadcast_source_pid", "pa_sync", "[bis_sync]"], "Attempt to add a particular broadcast source to the scan delegator"),
-        UpdatePaSync = ("update-pa-sync", [], ["broadcast_id", "pa_sync", "[bis_sync]"], "Attempt to update the scan delegator's desired pa sync to a particular broadcast source"),
+        AddBroadcastSource = ("add-broadcast-source", [], ["broadcast_source_pid", "PaSyncOff|PaSyncPast|PaSyncNoPast", "[bis_sync]"], "Attempt to add a particular broadcast source to the scan delegator"),
+        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"),
         RemoveBroadcastSource = ("remove-broadcast-source", [], ["broadcast_id"], "Attempt to remove a particular broadcast source to the scan delegator"),
         RemoteScanStarted = ("inform-scan-started", [], [], "Inform the scan delegator that we have started scanning on behalf of it"),
         RemoteScanStopped = ("inform-scan-stopped", [], [], "Inform the scan delegator that we have stopped scanning on behalf of it"),
         // TODO(http://b/433285146): Once PA scanning is implemented, remove bottom 3 commands.
-        ForceDiscoverBroadcastSource = ("force-discover-broadcast-source", [], ["broadcast_source_pid", "address", "address_type", "advertising_sid"], "Force the broadcast assistant to become aware of the provided broadcast source"),
+        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"),
         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"),
         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"),
     }
@@ -48,7 +54,6 @@
 pub struct AssistantDebug<T: bt_gatt::GattTypes, R: GetPeerAddr> {
     assistant: BroadcastAssistant<T>,
     connected_peer: Mutex<Option<Arc<Peer<T>>>>,
-    started: bool,
     peer_addr_getter: R,
 }
 
@@ -60,18 +65,16 @@
         Self {
             assistant: BroadcastAssistant::<T>::new(central),
             connected_peer: Mutex::new(None),
-            started: false,
             peer_addr_getter,
         }
     }
 
     pub fn start(&mut self) -> Result<EventStream<T>, Error> {
         let event_stream = self.assistant.start()?;
-        self.started = true;
         Ok(event_stream)
     }
 
-    pub fn look_for_scan_delegators(&mut self) -> T::ScanResultStream {
+    pub fn look_for_scan_delegators(&mut self) -> Result<T::ScanResultStream, Error> {
         self.assistant.scan_for_scan_delegators()
     }
 
@@ -118,7 +121,7 @@
     }
 }
 
-fn parse_peer_id(input: &str) -> Result<PeerId, String> {
+pub fn parse_peer_id(input: &str) -> Result<PeerId, String> {
     let raw_id = match parse_int(input) {
         Err(_) => return Err(format!("falied to parse int from {input}")),
         Ok(i) => i,
@@ -128,12 +131,14 @@
 }
 
 #[cfg(any(test, feature = "debug"))]
-fn parse_bd_addr(input: &str) -> Result<[u8; 6], String> {
-    let tokens: Vec<u8> =
+/// Returns the bd address in little endian ordering.
+pub fn parse_bd_addr(input: &str) -> Result<[u8; 6], String> {
+    let mut tokens: Vec<u8> =
         input.split(':').map(|t| u8::from_str_radix(t, 16)).filter_map(Result::ok).collect();
     if tokens.len() != 6 {
         return Err(format!("failed to parse bd address from {input}"));
     }
+    tokens.reverse();
     tokens.try_into().map_err(|e| format!("{e:?}"))
 }
 
@@ -145,17 +150,30 @@
     raw_id.try_into().map_err(|e| format!("{e:?}"))
 }
 
-fn parse_bis_sync(input: &str) -> BigToBisSync {
-    input.split(',').filter_map(|t| {
+fn parse_bis_sync(input: &str) -> HashMap<SubgroupIndex, BisSync> {
+    let mut map = HashMap::new();
+    for t in input.split(',') {
         let parts: Vec<_> = t.split('-').collect();
         if parts.len() != 2 {
-            eprintln!("invalid big-bis sync info {t}. should be in <Ith_BIG>-<BIS_INDEX> format, will be ignored");
-            return None;
+            eprintln!(
+                "invalid big-bis sync info {t}. should be in <Ith_BIG>-<BIS_INDEX> format, will be ignored"
+            );
+            continue;
         }
-        let ith_big = parse_int(parts[0]).ok()?;
-        let bis_index = parse_int(parts[1]).ok()?;
-        Some((ith_big, bis_index))
-    }).collect()
+        let Ok(ith_big) = parse_int(parts[0]) else {
+            eprintln!("Failed to parse big index from '{}', ignoring.", parts[0]);
+            continue;
+        };
+        let Ok(bis_index) = parse_int::<u8>(parts[1]) else {
+            eprintln!("Failed to parse bis index from '{}', ignoring.", parts[1]);
+            continue;
+        };
+        let entry = map.entry(ith_big).or_insert(BisSync::no_sync());
+        if let Err(e) = entry.synchronize_to_index(bis_index) {
+            eprintln!("Invalid BIS index: {e:?}");
+        }
+    }
+    map
 }
 
 impl<T: bt_gatt::GattTypes + 'static, R: GetPeerAddr> CommandRunner for AssistantDebug<T, R>
@@ -249,18 +267,16 @@
                         return Ok(());
                     };
 
-                    let pa_sync = match parse_int::<u8>(&args[1]) {
-                        Ok(raw_val) if PaSync::try_from(raw_val).is_ok() => {
-                            PaSync::try_from(raw_val).unwrap()
-                        }
-                        _ => {
-                            eprintln!("invalid pa_sync: {}", args[1]);
+                    let pa_sync: PaSync = match args[1].parse() {
+                        Ok(sync) => sync,
+                        Err(e) => {
+                            eprintln!("invalid pa_sync: {e:?}");
                             return Ok(());
                         }
                     };
 
                     let bis_sync =
-                        if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashSet::new() };
+                        if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() };
 
                     self.with_peer(|peer| async move {
                         peer.add_broadcast_source(
@@ -284,18 +300,16 @@
                         return Ok(());
                     };
 
-                    let pa_sync = match parse_int::<u8>(&args[1]) {
-                        Ok(raw_val) if PaSync::try_from(raw_val).is_ok() => {
-                            PaSync::try_from(raw_val).unwrap()
-                        }
-                        _ => {
-                            eprintln!("invalid pa_sync: {}", args[1]);
+                    let pa_sync: PaSync = match args[1].parse() {
+                        Ok(sync) => sync,
+                        Err(e) => {
+                            eprintln!("invalid pa_sync: {e:?}");
                             return Ok(());
                         }
                     };
 
                     let bis_sync =
-                        if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashSet::new() };
+                        if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashMap::new() };
 
                     self.with_peer(|peer| async move {
                         peer.update_broadcast_source_sync(broadcast_id, pa_sync, bis_sync).await
@@ -319,8 +333,10 @@
                     .await;
                 }
                 AssistantCmd::RemoteScanStarted => {
-                    self.with_peer(|peer| async move { peer.inform_remote_scan_started().await })
-                        .await;
+                    self.with_peer(|peer: Arc<Peer<T>>| async move {
+                        peer.inform_remote_scan_started().await
+                    })
+                    .await;
                 }
                 AssistantCmd::RemoteScanStopped => {
                     self.with_peer(|peer| async move { peer.inform_remote_scan_stopped().await })
@@ -346,21 +362,25 @@
                         return Ok(());
                     };
 
-                    let Ok(raw_addr_type) = parse_int::<u8>(&args[2]) else {
-                        eprintln!("invalid address type: {}", args[2]);
-                        return Ok(());
+                    let address_type: AddressType = match args[2].parse() {
+                        Ok(t) => t,
+                        Err(e) => {
+                            eprintln!("invalid address type: {e:?}");
+                            return Ok(());
+                        }
                     };
 
                     let Ok(raw_ad_sid) = parse_int::<u8>(&args[3]) else {
                         eprintln!("invalid advertising sid: {}", args[3]);
                         return Ok(());
                     };
+                    let advertising_sid = AdvertisingSetId(raw_ad_sid);
 
                     match self.assistant.force_discover_broadcast_source(
                         peer_id,
                         address,
-                        raw_addr_type,
-                        raw_ad_sid,
+                        address_type,
+                        advertising_sid,
                     ) {
                         Ok(source) => {
                             eprintln!("broadcast source after additional info: {source:?}")
@@ -385,19 +405,32 @@
                         return Ok(());
                     };
 
-                    let mut raw_metadata = Vec::new();
+                    let mut all_big_metadata = Vec::new();
                     for i in 1..args.len() {
-                        let ith_metadata: Vec<u8> = args[i]
+                        let raw_metadata: Vec<u8> = args[i]
                             .split(',')
                             .map(|t| parse_int(t))
                             .filter_map(Result::ok)
                             .collect();
-                        raw_metadata.push(ith_metadata);
+
+                        if raw_metadata.len() > 0 {
+                            let (decoded_metadata, consumed_len) =
+                                Metadata::decode_all(raw_metadata.as_slice());
+                            if consumed_len != raw_metadata.len() {
+                                eprintln!("Metadata length is not valid");
+                                return Ok(());
+                            }
+                            all_big_metadata.push(
+                                decoded_metadata.into_iter().filter_map(Result::ok).collect(),
+                            );
+                        } else {
+                            all_big_metadata.push(vec![]);
+                        }
                     }
 
                     match self
                         .assistant
-                        .force_discover_broadcast_source_metadata(peer_id, raw_metadata)
+                        .force_discover_broadcast_source_metadata(peer_id, all_big_metadata)
                     {
                         Ok(source) => eprintln!("broadcast source with metadata: {source:?}"),
                         Err(e) => eprintln!("failed to enter in broadcast source metadata: {e:?}"),
@@ -423,14 +456,14 @@
                         return Ok(());
                     };
 
-                    let mut raw_metadata = Vec::new();
+                    let mut all_big_metadata = Vec::new();
                     for _i in 0..num_big {
-                        raw_metadata.push(vec![]);
+                        all_big_metadata.push(vec![]);
                     }
 
                     match self
                         .assistant
-                        .force_discover_broadcast_source_metadata(peer_id, raw_metadata)
+                        .force_discover_broadcast_source_metadata(peer_id, all_big_metadata)
                     {
                         Ok(source) => eprintln!("broadcast source with metadata: {source:?}"),
                         Err(e) => {
@@ -465,7 +498,7 @@
     fn test_parse_bd_addr() {
         assert_eq!(
             parse_bd_addr("3c:80:f1:ed:32:2c").expect("should be ok"),
-            [0x3c, 0x80, 0xf1, 0xed, 0x32, 0x2c]
+            [0x2c, 0x32, 0xed, 0xf1, 0x80, 0x3c]
         );
         // Address with 5 parts is invalid.
         let _ = parse_bd_addr("3c:80:f1:ed:32").expect_err("should fail");
@@ -492,16 +525,14 @@
     #[test]
     fn test_parse_bis_sync() {
         let bis_sync = parse_bis_sync("0-1,0-2,1-1");
-        assert_eq!(bis_sync.len(), 3);
-        bis_sync.contains(&(0, 1));
-        bis_sync.contains(&(0, 2));
-        bis_sync.contains(&(1, 1));
+        assert_eq!(bis_sync.len(), 2);
+        assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
+        assert_eq!(bis_sync.get(&1), Some(&BisSync::sync(vec![1]).unwrap()));
 
         // Will ignore invalid values.
         let bis_sync = parse_bis_sync("0-1,0-2,1:1,1-1-1,");
-        assert_eq!(bis_sync.len(), 2);
-        bis_sync.contains(&(0, 1));
-        bis_sync.contains(&(0, 2));
+        assert_eq!(bis_sync.len(), 1);
+        assert_eq!(bis_sync.get(&0), Some(&BisSync::sync(vec![1, 2]).unwrap()));
 
         let bis_sync = parse_bis_sync("hellothisistoallynotvalid");
         assert_eq!(bis_sync.len(), 0);
diff --git a/rust/bt-broadcast-assistant/src/types.rs b/rust/bt-broadcast-assistant/src/types.rs
index 35d6c6e..2cd8d96 100644
--- a/rust/bt-broadcast-assistant/src/types.rs
+++ b/rust/bt-broadcast-assistant/src/types.rs
@@ -3,11 +3,11 @@
 // found in the LICENSE file.
 
 use bt_bap::types::*;
-use bt_bass::client::BigToBisSync;
 use bt_bass::types::{BigSubgroup, BisSync};
 use bt_common::core::{Address, AddressType};
 use bt_common::core::{AdvertisingSetId, PaInterval};
 use bt_common::packet_encoding::Error as PacketError;
+use std::collections::HashMap;
 
 /// Broadcast source data as advertised through Basic Audio Announcement
 /// PA and Broadcast Audio Announcement.
@@ -27,12 +27,10 @@
     /// Returns whether or not this BroadcastSource has enough information
     /// to be added by the Broadcast Assistant.
     pub(crate) fn into_add_source(&self) -> bool {
-        // PA interval is not necessary since default value can be used.
-        self.address.is_some()
-            && self.address_type.is_some()
-            && self.advertising_sid.is_some()
-            && self.broadcast_id.is_some()
-            && self.endpoint.is_some()
+        // Address and PA interval information are not necessary since
+        // default value can be used for PA interval and Address is looked up
+        // when the add source operation is triggered.
+        self.advertising_sid.is_some() && self.broadcast_id.is_some() && self.endpoint.is_some()
     }
 
     pub fn with_address(&mut self, address: [u8; 6]) -> &mut Self {
@@ -95,7 +93,7 @@
     ///   preference value is used for all the BIGs
     pub(crate) fn endpoint_to_big_subgroups(
         &self,
-        bis_sync: BigToBisSync,
+        bis_sync: HashMap<u8, BisSync>,
     ) -> Result<Vec<BigSubgroup>, PacketError> {
         if self.endpoint.is_none() {
             return Err(PacketError::InvalidParameter(
@@ -104,17 +102,9 @@
             ));
         }
         let mut subgroups = Vec::new();
-        let sync_map = bt_bass::client::big_to_bis_sync_indices(&bis_sync);
 
         for (big_index, group) in self.endpoint.as_ref().unwrap().big.iter().enumerate() {
-            let bis_sync = match sync_map.get(&(big_index as u8)) {
-                Some(bis_indices) => {
-                    let mut bis_sync: BisSync = BisSync(0);
-                    bis_sync.set_sync(bis_indices)?;
-                    bis_sync
-                }
-                _ => BisSync::default(),
-            };
+            let bis_sync = bis_sync.get(&(big_index as u8)).cloned().unwrap_or_default();
             subgroups.push(BigSubgroup::new(Some(bis_sync)).with_metadata(group.metadata.clone()));
         }
         Ok(subgroups)
@@ -125,7 +115,7 @@
 mod tests {
     use super::*;
 
-    use std::collections::HashSet;
+    use std::collections::HashMap;
 
     use bt_common::core::CodecId;
     use bt_common::generic_audio::metadata_ltv::Metadata;
@@ -135,19 +125,16 @@
         let mut b = BroadcastSource::default();
         assert!(!b.into_add_source());
 
-        b.merge(
-            &BroadcastSource::default()
-                .with_address([0x1, 0x2, 0x3, 0x4, 0x5, 0x6])
-                .with_address_type(AddressType::Public)
-                .with_advertising_sid(AdvertisingSetId(0x1))
-                .with_broadcast_id(BroadcastId::try_from(0x010203).unwrap()),
-        );
-
+        b.with_advertising_sid(AdvertisingSetId(0x1));
         assert!(!b.into_add_source());
-        b.endpoint_to_big_subgroups(HashSet::from([(0, 1)]))
+
+        b.with_broadcast_id(BroadcastId::try_from(0x010203).unwrap());
+        assert!(!b.into_add_source());
+
+        b.endpoint_to_big_subgroups(HashMap::from([(0, BisSync::sync(vec![1]).unwrap())]))
             .expect_err("should fail no endpoint data");
 
-        b.merge(&BroadcastSource::default().with_endpoint(BroadcastAudioSourceEndpoint {
+        b.with_endpoint(BroadcastAudioSourceEndpoint {
             presentation_delay_ms: 0x010203,
             big: vec![BroadcastIsochronousGroup {
                 codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd),
@@ -158,15 +145,19 @@
                     codec_specific_config: vec![],
                 }],
             }],
-        }));
+        });
 
         assert!(b.into_add_source());
-        let subgroups =
-            b.endpoint_to_big_subgroups(HashSet::from([(0, 1), (1, 1)])).expect("should succeed");
+        let subgroups = b
+            .endpoint_to_big_subgroups(HashMap::from([
+                (0, BisSync::sync(vec![1]).unwrap()),
+                (1, BisSync::sync(vec![1]).unwrap()),
+            ]))
+            .expect("should succeed");
         assert_eq!(subgroups.len(), 1);
         assert_eq!(
             subgroups[0],
-            BigSubgroup::new(Some(BisSync(0x00000001)))
+            BigSubgroup::new(Some(BisSync::sync(vec![1]).unwrap()))
                 .with_metadata(vec![Metadata::BroadcastAudioImmediateRenderingFlag])
         );
     }
diff --git a/rust/bt-common/src/core.rs b/rust/bt-common/src/core.rs
index db4a1d9..fee3dc7 100644
--- a/rust/bt-common/src/core.rs
+++ b/rust/bt-common/src/core.rs
@@ -6,6 +6,7 @@
 pub mod ltv;
 
 use crate::packet_encoding::{Encodable, Error as PacketError};
+use std::str::FromStr;
 
 /// Bluetooth Device Address that uniquely identifies the device
 /// to another Bluetooth device.
@@ -36,6 +37,18 @@
     }
 }
 
+impl FromStr for AddressType {
+    type Err = PacketError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "Public" => Ok(AddressType::Public),
+            "Random" => Ok(AddressType::Random),
+            _ => Err(PacketError::InvalidParameter(format!("invalid address type: {s}"))),
+        }
+    }
+}
+
 /// Advertising Set ID which is 1 byte long.
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub struct AdvertisingSetId(pub u8);
@@ -218,6 +231,16 @@
     use crate::packet_encoding::Decodable;
 
     use super::*;
+    use std::str::FromStr;
+
+    #[test]
+    fn address_type_from_str() {
+        let addr_type = AddressType::from_str("Public").expect("should succeed");
+        assert_eq!(addr_type, AddressType::Public);
+        let addr_type = AddressType::from_str("Random").expect("should succeed");
+        assert_eq!(addr_type, AddressType::Random);
+        AddressType::from_str("invalid").expect_err("should fail");
+    }
 
     #[test]
     fn encode_pa_interval() {