rust/bt-bap: Define common data types in Basic Audio Profile

Define common data types that are used and referenced in various
services of Basic Audio Profiles for consistency.

Change-Id: Iaeb56f1dbfc937aec6db6f038a1d8f3e0ce0947a
Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1560
Reviewed-by: Marie Janssen <jamuraa@google.com>
Reviewed-by: Ani Ramakrishnan <aniramakri@google.com>
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index f4da3a7..8b01f6f 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -11,6 +11,7 @@
 [workspace.dependencies]
 
 ## Local path dependencies (keep sorted)
+bt-bap = { path = "bt-bap" }
 bt-bass = { path = "bt-bass" }
 bt-broadcast-assistant = { path = "bt-broadcast-assistant" }
 bt-common = { path = "bt-common" }
@@ -19,6 +20,7 @@
 
 ## External dependencies
 assert_matches = "1.5.0"
+bitfield = "0.14.0" # 0.14.0 in Fuchsia
 futures = "0.3.19" # 0.3.19 in Fuchsia
 lazy_static = "1"
 parking_lot = "0.12.0" # 0.12.0 in Fuchsia
diff --git a/rust/bt-bap/Cargo.toml b/rust/bt-bap/Cargo.toml
new file mode 100644
index 0000000..ca7566f
--- /dev/null
+++ b/rust/bt-bap/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "bt-bap"
+version = "0.0.1"
+edition.workspace = true
+license.workspace = true
+
+[dependencies]
+bt-common.workspace = true
diff --git a/rust/bt-bap/src/lib.rs b/rust/bt-bap/src/lib.rs
new file mode 100644
index 0000000..fd96ce3
--- /dev/null
+++ b/rust/bt-bap/src/lib.rs
@@ -0,0 +1,12 @@
+// Copyright 2023 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Broadcast Audio Profile Common Library
+//!
+//! Contains common structs and implenmentations for implementing:
+//!  - Broadcast Audio Scan Service
+//!  - Basic Audio Profile Scan Delegator
+//!  - Basic Audio Profile Broadcast Assistant
+
+pub mod types;
diff --git a/rust/bt-bap/src/types.rs b/rust/bt-bap/src/types.rs
new file mode 100644
index 0000000..cdb3c7e
--- /dev/null
+++ b/rust/bt-bap/src/types.rs
@@ -0,0 +1,483 @@
+// Copyright 2023 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use bt_common::core::ltv::LtValue;
+use bt_common::core::CodecId;
+use bt_common::generic_audio::codec_configuration::CodecConfiguration;
+use bt_common::generic_audio::metadata_ltv::Metadata;
+use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
+
+/// Broadcast_ID is a 3-byte data on the wire.
+/// Defined in BAP spec v1.0.1 section 3.7.2.1.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub struct BroadcastId(u32);
+
+impl BroadcastId {
+    // On the wire, Broadcast_ID is transported in 3 bytes.
+    pub const BYTE_SIZE: usize = 3;
+
+    pub fn new(raw_value: u32) -> Self {
+        Self(raw_value)
+    }
+}
+
+impl std::fmt::Display for BroadcastId {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "{:#08x}", self.0)
+    }
+}
+
+impl From<BroadcastId> for u32 {
+    fn from(value: BroadcastId) -> u32 {
+        value.0
+    }
+}
+
+impl TryFrom<u32> for BroadcastId {
+    type Error = PacketError;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        const MAX_VALUE: u32 = 0xFFFFFF;
+        if value > MAX_VALUE {
+            return Err(PacketError::InvalidParameter(format!(
+                "Broadcast ID cannot exceed 3 bytes"
+            )));
+        }
+        Ok(BroadcastId(value))
+    }
+}
+
+impl Decodable for BroadcastId {
+    type Error = PacketError;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() != Self::BYTE_SIZE {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+
+        let padded_bytes = [buf[0], buf[1], buf[2], 0x00];
+        Ok((BroadcastId(u32::from_le_bytes(padded_bytes)), Self::BYTE_SIZE))
+    }
+}
+
+impl Encodable for BroadcastId {
+    type Error = PacketError;
+
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < self.encoded_len() {
+            return Err(PacketError::BufferTooSmall);
+        }
+        // Since 3-byte value is being fit into u32, we ignore the most significant
+        // byte.
+        buf[0..3].copy_from_slice(&self.0.to_le_bytes()[0..3]);
+        Ok(())
+    }
+
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+}
+
+/// To associate a PA, used to expose broadcast Audio Stream parameters, with a
+/// broadcast Audio Stream, the Broadcast Source shall transmit EA PDUs that
+/// include the following data. This struct represents the AD data value
+/// excluding the 2-octet Service UUID. See BAP v1.0.1 Section 3.7.2.1 for more
+/// details.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct BroadcastAudioAnnouncement {
+    pub broadcast_id: BroadcastId,
+}
+
+impl BroadcastAudioAnnouncement {
+    const PACKET_SIZE: usize = BroadcastId::BYTE_SIZE;
+}
+
+impl Decodable for BroadcastAudioAnnouncement {
+    type Error = PacketError;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < Self::PACKET_SIZE {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+
+        let (broadcast_id, _) = BroadcastId::decode(&buf[0..3])?;
+        // According to the spec, broadcast audio announcement service data inlcudes
+        // broadcast id and any additional service data. We don't store any
+        // additional parameters, so for now we just "consume" all of data buffer
+        // without doing anything.
+        Ok((Self { broadcast_id }, buf.len()))
+    }
+}
+
+/// Parameters exposed as part of Basic Audio Announcement from Broadcast
+/// Sources. See BAP v1.0.1 Section 3.7.2.2 for more details.
+// TODO(b/308481381): Fill out the struct.
+#[derive(Clone, Debug, PartialEq)]
+pub struct BroadcastAudioSourceEndpoint {
+    // Actual value is 3 bytes long.
+    pub presentation_delay_ms: u32,
+    pub big: Vec<BroadcastIsochronousGroup>,
+}
+
+impl BroadcastAudioSourceEndpoint {
+    // Should contain presentation delay, num BIG, and at least one BIG praram.
+    const MIN_PACKET_SIZE: usize = 3 + 1 + BroadcastIsochronousGroup::MIN_PACKET_SIZE;
+}
+
+impl Decodable for BroadcastAudioSourceEndpoint {
+    type Error = PacketError;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < Self::MIN_PACKET_SIZE {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+
+        let mut idx = 0 as usize;
+        let presentation_delay = u32::from_le_bytes([buf[idx], buf[idx + 1], buf[idx + 2], 0x00]);
+        idx += 3;
+
+        let num_big: usize = buf[idx] as usize;
+        idx += 1;
+        if num_big < 1 {
+            return Err(PacketError::InvalidParameter(format!(
+                "num of subgroups shall be at least 1 got {num_big}"
+            )));
+        }
+
+        let mut big = Vec::new();
+        while big.len() < num_big {
+            let (group, len) = BroadcastIsochronousGroup::decode(&buf[idx..])
+                .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
+            big.push(group);
+            idx += len;
+        }
+
+        Ok((Self { presentation_delay_ms: presentation_delay, big }, idx))
+    }
+}
+
+/// A single subgroup in a Basic Audio Announcement as outlined in
+/// BAP spec v1.0.1 Section 3.7.2.2. Each subgroup is used to
+/// group BISes present in the broadcast isochronous group.
+#[derive(Clone, Debug, PartialEq)]
+pub struct BroadcastIsochronousGroup {
+    pub codec_id: CodecId,
+    pub codec_specific_configs: Vec<CodecConfiguration>,
+    pub metadata: Vec<Metadata>,
+    pub bis: Vec<BroadcastIsochronousStream>,
+}
+
+impl BroadcastIsochronousGroup {
+    // Should contain num BIS, codec id, codec specific config len, metadata len,
+    // and at least one BIS praram.
+    const MIN_PACKET_SIZE: usize =
+        1 + CodecId::BYTE_SIZE + 1 + 1 + BroadcastIsochronousStream::MIN_PACKET_SIZE;
+}
+
+impl Decodable for BroadcastIsochronousGroup {
+    type Error = PacketError;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < BroadcastIsochronousGroup::MIN_PACKET_SIZE {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+
+        let mut idx = 0;
+        let num_bis = buf[idx] as usize;
+        idx += 1;
+        if num_bis < 1 {
+            return Err(PacketError::InvalidParameter(format!(
+                "num of BIS shall be at least 1 got {num_bis}"
+            )));
+        }
+
+        let (codec_id, read_bytes) = CodecId::decode(&buf[idx..])?;
+        idx += read_bytes;
+
+        let codec_config_len = buf[idx] as usize;
+        idx += 1;
+        if idx + codec_config_len > buf.len() {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
+        if consumed != codec_config_len {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+
+        let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
+        idx += codec_config_len;
+
+        let metadata_len = buf[idx] as usize;
+        idx += 1;
+        if idx + metadata_len > buf.len() {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+
+        let (results_metadata, consumed_len) = Metadata::decode_all(&buf[idx..idx + metadata_len]);
+        if consumed_len != metadata_len {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+        // Ignore any undecodable metadata types
+        let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
+        idx += consumed_len;
+
+        let mut bis = Vec::new();
+        while bis.len() < num_bis {
+            let (stream, len) = BroadcastIsochronousStream::decode(&buf[idx..])
+                .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
+            bis.push(stream);
+            idx += len;
+        }
+
+        Ok((BroadcastIsochronousGroup { codec_id, codec_specific_configs, metadata, bis }, idx))
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct BroadcastIsochronousStream {
+    pub bis_index: u8,
+    pub codec_specific_config: Vec<CodecConfiguration>,
+}
+
+impl BroadcastIsochronousStream {
+    const MIN_PACKET_SIZE: usize = 1 + 1;
+}
+
+impl Decodable for BroadcastIsochronousStream {
+    type Error = PacketError;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < BroadcastIsochronousStream::MIN_PACKET_SIZE {
+            return Err(PacketError::UnexpectedDataLength);
+        }
+
+        let mut idx = 0;
+
+        let bis_index = buf[idx];
+        idx += 1;
+
+        let codec_config_len = buf[idx] as usize;
+        idx += 1;
+
+        let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
+        if consumed != codec_config_len {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
+        idx += codec_config_len;
+
+        Ok((
+            BroadcastIsochronousStream { bis_index, codec_specific_config: codec_specific_configs },
+            idx,
+        ))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use std::collections::HashSet;
+
+    use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency};
+    use bt_common::generic_audio::AudioLocation;
+
+    #[test]
+    fn broadcast_id() {
+        // Value bigger than 3 bytes is not a valid broadcast ID.
+        let _ = BroadcastId::try_from(0x010A0B0C).expect_err("should fail");
+
+        let id = BroadcastId::try_from(0x000A0B0C).expect("should succeed");
+
+        assert_eq!(id.encoded_len(), 3);
+        let mut buf = vec![0; id.encoded_len()];
+        let _ = id.encode(&mut buf[..]).expect("should have succeeded");
+
+        let bytes = vec![0x0C, 0x0B, 0x0A];
+        assert_eq!(buf, bytes);
+
+        let (got, bytes) = BroadcastId::decode(&bytes).expect("should succeed");
+        assert_eq!(got, id);
+        assert_eq!(bytes, BroadcastId::BYTE_SIZE);
+        let got = BroadcastId::try_from(u32::from_le_bytes([0x0C, 0x0B, 0x0A, 0x00]))
+            .expect("should succeed");
+        assert_eq!(got, id);
+    }
+
+    #[test]
+    fn broadcast_audio_announcement() {
+        let bytes = vec![0x0C, 0x0B, 0x0A];
+        let broadcast_id = BroadcastId::try_from(0x000A0B0C).unwrap();
+
+        let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes).expect("should succeed");
+        assert_eq!(got, BroadcastAudioAnnouncement { broadcast_id });
+        assert_eq!(consumed, 3);
+
+        let bytes = vec![
+            0x0C, 0x0B, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, /* some other additional data */
+        ];
+        let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes).expect("should succeed");
+        assert_eq!(got, BroadcastAudioAnnouncement { broadcast_id });
+        assert_eq!(consumed, 8);
+    }
+
+    #[test]
+    fn decode_bis() {
+        #[rustfmt::skip]
+        let buf = [
+            0x01, 0x09,                          // bis index and codec specific config len
+            0x02, 0x01, 0x06,                    // sampling frequency LTV
+            0x05, 0x03, 0x03, 0x00, 0x00, 0x0C,  // audio location LTV
+        ];
+
+        let (bis, _read_bytes) =
+            BroadcastIsochronousStream::decode(&buf[..]).expect("should not fail");
+        assert_eq!(
+            bis,
+            BroadcastIsochronousStream {
+                bis_index: 0x01,
+                codec_specific_config: vec![
+                    CodecConfiguration::SamplingFrequency(SamplingFrequency::F32000Hz),
+                    CodecConfiguration::AudioChannelAllocation(HashSet::from([
+                        AudioLocation::FrontLeft,
+                        AudioLocation::FrontRight,
+                        AudioLocation::LeftSurround,
+                        AudioLocation::RightSurround
+                    ])),
+                ],
+            }
+        );
+    }
+
+    #[test]
+    fn decode_big() {
+        #[rustfmt::skip]
+        let buf = [
+            0x02,  0x03, 0x00, 0x00, 0x00, 0x00,  // num of bis, codec id
+            0x04, 0x03, 0x04, 0x04, 0x10,         // codec specific config len, octets per codec frame LTV
+            0x03, 0x02, 0x08, 0x01,               // metadata len, audio active state LTV
+            0x01, 0x00,                           // bis index, codec specific config len (bis #1)
+            0x02, 0x03, 0x02, 0x02, 0x01,         // bis index, codec specific config len, frame duration LTV (bis #2)
+        ];
+
+        let (big, _read_bytes) =
+            BroadcastIsochronousGroup::decode(&buf[..]).expect("should not fail");
+        assert_eq!(
+            big,
+            BroadcastIsochronousGroup {
+                codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
+                codec_specific_configs: vec![CodecConfiguration::OctetsPerCodecFrame(0x1004),],
+                metadata: vec![Metadata::AudioActiveState(true)],
+                bis: vec![
+                    BroadcastIsochronousStream { bis_index: 0x01, codec_specific_config: vec![] },
+                    BroadcastIsochronousStream {
+                        bis_index: 0x02,
+                        codec_specific_config: vec![CodecConfiguration::FrameDuration(
+                            FrameDuration::TenMs
+                        )],
+                    },
+                ],
+            }
+        );
+    }
+
+    #[test]
+    fn decode_base() {
+        #[rustfmt::skip]
+        let buf = [
+            0x10, 0x20, 0x30, 0x02,               // presentation delay, num of subgroups
+            0x01, 0x03, 0x00, 0x00, 0x00, 0x00,   // num of bis, codec id (big #1)
+            0x00,                                 // codec specific config len
+            0x00,                                 // metadata len,
+            0x01, 0x00,                           // bis index, codec specific config len (big #1 / bis #1)
+            0x01, 0x02, 0x00, 0x00, 0x00, 0x00,   // num of bis, codec id (big #2)
+            0x00,                                 // codec specific config len
+            0x00,                                 // metadata len,
+            0x01, 0x03, 0x02, 0x05, 0x08,         // bis index, codec specific config len, codec frame blocks LTV (big #2 / bis #2)
+        ];
+
+        let (base, _read_bytes) =
+            BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+        assert_eq!(base.presentation_delay_ms, 0x00302010);
+        assert_eq!(base.big.len(), 2);
+        assert_eq!(
+            base.big[0],
+            BroadcastIsochronousGroup {
+                codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
+                codec_specific_configs: vec![],
+                metadata: vec![],
+                bis: vec![BroadcastIsochronousStream {
+                    bis_index: 0x01,
+                    codec_specific_config: vec![],
+                },],
+            }
+        );
+        assert_eq!(
+            base.big[1],
+            BroadcastIsochronousGroup {
+                codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd),
+                codec_specific_configs: vec![],
+                metadata: vec![],
+                bis: vec![BroadcastIsochronousStream {
+                    bis_index: 0x01,
+                    codec_specific_config: vec![CodecConfiguration::CodecFramesPerSdu(0x08)],
+                },],
+            }
+        );
+    }
+
+    #[test]
+    fn decode_base_complex() {
+        #[rustfmt::skip]
+        let buf = [
+            0x20, 0x4e, 0x00,  // presentation_delay_ms: 20000 (little-endian)
+            0x01,  // # of subgroups
+            0x02,  // # of BIS in group 1
+            0x06, 0x00, 0x00, 0x00, 0x00,  // Codec ID (Lc3)
+            0x10,  // codec_specific_configuration_length of group 1
+            0x02, 0x01, 0x05,  // sampling frequency 24 kHz
+            0x02, 0x02, 0x01,  // 10 ms frame duration
+            0x05, 0x03, 0x03, 0x00, 0x00, 0x00,  // front left audio channel
+            0x03, 0x04, 0x3c, 0x00,  // 60 octets per codec frame
+            0x04,  // metadata_length of group 1
+            0x03, 0x02, 0x04, 0x00,  // media streaming audio context
+            0x01,  // BIS index of the 1st BIS in group 1
+            0x06,  // codec_specific_configuration_length of the 1st BIS
+            0x05, 0x03, 0x01, 0x00, 0x00, 0x00,  // front left audio channel
+            0x02,  // BIS index of the 2nd BIS in group 1
+            0x06,  // codec_specific_configuration_length of the 2nd BIS
+            0x05, 0x03, 0x02, 0x00, 0x00, 0x00,  // front right audio channel
+        ];
+        let (base, read_bytes) =
+            BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+        assert_eq!(read_bytes, buf.len());
+        assert_eq!(base.presentation_delay_ms, 20000);
+        assert_eq!(base.big.len(), 1);
+        assert_eq!(base.big[0].bis.len(), 2);
+
+        #[rustfmt::skip]
+        let buf = [
+            0x20, 0x4e, 0x00,  // presentation_delay_ms: 20000 (little-endian)
+            0x01,  // # of subgroups
+            0x01,  // # of BIS in group 1
+            0x06, 0x00, 0x00, 0x00, 0x00,  // Codec ID (Lc3)
+            0x10,  // codec_specific_configuration_length of group 1
+            0x02, 0x01, 0x05,  // sampling frequency 24 kHz
+            0x02, 0x02, 0x01,  // 10 ms frame duration
+            0x05, 0x03, 0x01, 0x00, 0x00, 0x00,  // front left audio channel
+            0x03, 0x04, 0x3c, 0x00,  // 60 octets per codec frame
+            0x02,  // metadata_length of group 1
+            0x01, 0x09,  // broadcast audio immediate rendering flag
+            0x01,  // BIS index of the 1st BIS in group 1
+            0x03,  // codec_specific_configuration_length of the 1st BIS
+            0x02, 0x01, 0x05,  // sampling frequency 24 kHz
+        ];
+        let (base, read_bytes) =
+            BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+        assert_eq!(read_bytes, buf.len());
+        assert_eq!(base.presentation_delay_ms, 20000);
+        assert_eq!(base.big.len(), 1);
+        assert_eq!(base.big[0].bis.len(), 1);
+    }
+}
diff --git a/rust/bt-bass/.gitignore b/rust/bt-bass/.gitignore
deleted file mode 100644
index 75e03aa..0000000
--- a/rust/bt-bass/.gitignore
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Cargo
-# will have compiled files and executables
-debug/
-target/
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
-Cargo.lock
-
-# These are backup files generated by rustfmt
-**/*.rs.bk
-
-# MSVC Windows builds of rustc generate these, which store debugging information
-*.pdb
-
-# Vim swap files
-*.swp
diff --git a/rust/bt-bass/Cargo.toml b/rust/bt-bass/Cargo.toml
index 2240624..555d6ea 100644
--- a/rust/bt-bass/Cargo.toml
+++ b/rust/bt-bass/Cargo.toml
@@ -9,6 +9,7 @@
 test-utils = []
 
 [dependencies]
+bt-bap.workspace = true
 bt-common.workspace = true
 bt-gatt = { workspace = true, features = ["test-utils"] }
 futures.workspace = true
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs
index 3479c66..f7e4038 100644
--- a/rust/bt-bass/src/client.rs
+++ b/rust/bt-bass/src/client.rs
@@ -13,6 +13,7 @@
 use parking_lot::Mutex;
 use tracing::warn;
 
+use bt_bap::types::BroadcastId;
 use bt_common::core::{AddressType, AdvertisingSetId, PaInterval};
 use bt_common::generic_audio::metadata_ltv::Metadata;
 use bt_common::packet_encoding::Decodable;
@@ -663,7 +664,7 @@
         assert_eq!(
             event,
             Event::AddedBroadcastSource(
-                BroadcastId(0x040302),
+                BroadcastId::try_from(0x040302).unwrap(),
                 PaSyncState::Synced,
                 EncryptionStatus::BroadcastCodeRequired
             )
@@ -702,7 +703,7 @@
         assert_eq!(
             event,
             Event::AddedBroadcastSource(
-                BroadcastId(0x050403),
+                BroadcastId::try_from(0x050403).unwrap(),
                 PaSyncState::SyncInfoRequest,
                 EncryptionStatus::NotEncrypted
             )
@@ -752,7 +753,7 @@
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
         let op_fut = client.add_broadcast_source(
-            BroadcastId(0x11),
+            BroadcastId::try_from(0x11).unwrap(),
             AddressType::Public,
             [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
             AdvertisingSetId(1),
@@ -778,7 +779,7 @@
                 source_address_type: AddressType::Public,
                 source_address: [1, 2, 3, 4, 5, 6],
                 source_adv_sid: AdvertisingSetId(1),
-                broadcast_id: BroadcastId(0x11),
+                broadcast_id: BroadcastId::try_from(0x11).unwrap(),
                 pa_sync_state: PaSyncState::Synced,
                 big_encryption: EncryptionStatus::BroadcastCodeRequired,
                 subgroups: vec![],
@@ -796,7 +797,7 @@
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
         let op_fut = client.modify_broadcast_source(
-            BroadcastId(0x11),
+            BroadcastId::try_from(0x11).unwrap(),
             PaSync::DoNotSync,
             Some(PaInterval(0xAAAA)),
             None,
@@ -820,7 +821,7 @@
                 source_address_type: AddressType::Public,
                 source_address: [1, 2, 3, 4, 5, 6],
                 source_adv_sid: AdvertisingSetId(1),
-                broadcast_id: BroadcastId(0x11),
+                broadcast_id: BroadcastId::try_from(0x11).unwrap(),
                 pa_sync_state: PaSyncState::Synced,
                 big_encryption: EncryptionStatus::BroadcastCodeRequired,
                 subgroups: vec![BigSubgroup::new(None)],
@@ -844,7 +845,7 @@
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
         let op_fut = client.modify_broadcast_source(
-            BroadcastId(0x11),
+            BroadcastId::try_from(0x11).unwrap(),
             PaSync::DoNotSync,
             None,
             Some(HashSet::from([(0, 1), (0, 2), (0, 3), (0, 5)])),
@@ -865,8 +866,13 @@
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
         // Broadcast source wasn't previously added.
-        let op_fut =
-            client.modify_broadcast_source(BroadcastId(0x11), PaSync::DoNotSync, None, None, None);
+        let op_fut = client.modify_broadcast_source(
+            BroadcastId::try_from(0x11).unwrap(),
+            PaSync::DoNotSync,
+            None,
+            None,
+            None,
+        );
         pin_mut!(op_fut);
         let polled = op_fut.poll_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Err(_)));
@@ -875,6 +881,7 @@
     #[test]
     fn remove_broadcast_source() {
         let (mut client, mut fake_peer_service) = setup_client();
+        let bid = BroadcastId::try_from(0x11).expect("should not fail");
 
         // Manually update the broadcast source tracker for testing purposes.
         // In practice, this would have been updated from BRS value change notification.
@@ -885,7 +892,7 @@
                 source_address_type: AddressType::Public,
                 source_address: [1, 2, 3, 4, 5, 6],
                 source_adv_sid: AdvertisingSetId(1),
-                broadcast_id: BroadcastId(0x11),
+                broadcast_id: bid,
                 pa_sync_state: PaSyncState::Synced,
                 big_encryption: EncryptionStatus::BroadcastCodeRequired,
                 subgroups: vec![],
@@ -897,7 +904,7 @@
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
         // Broadcast source wasn't previously added.
-        let op_fut = client.remove_broadcast_source(BroadcastId(0x11));
+        let op_fut = client.remove_broadcast_source(bid);
         pin_mut!(op_fut);
         let polled = op_fut.poll_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Ok(_)));
@@ -909,7 +916,7 @@
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
         // Broadcast source wasn't previously added.
-        let op_fut = client.remove_broadcast_source(BroadcastId(0x11));
+        let op_fut = client.remove_broadcast_source(BroadcastId::try_from(0x11).unwrap());
         pin_mut!(op_fut);
         let polled = op_fut.poll_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Err(_)));
@@ -928,7 +935,7 @@
                 source_address_type: AddressType::Public,
                 source_address: [1, 2, 3, 4, 5, 6],
                 source_adv_sid: AdvertisingSetId(1),
-                broadcast_id: BroadcastId(0x030201),
+                broadcast_id: BroadcastId::try_from(0x030201).unwrap(),
                 pa_sync_state: PaSyncState::Synced,
                 big_encryption: EncryptionStatus::BroadcastCodeRequired,
                 subgroups: vec![],
@@ -941,7 +948,8 @@
         );
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
-        let set_code_fut = client.set_broadcast_code(BroadcastId(0x030201), [1; 16]);
+        let set_code_fut =
+            client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
         pin_mut!(set_code_fut);
         let polled = set_code_fut.poll_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Ok(_)));
@@ -952,7 +960,8 @@
         let (mut client, _) = setup_client();
 
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
-        let set_code_fut = client.set_broadcast_code(BroadcastId(0x030201), [1; 16]);
+        let set_code_fut =
+            client.set_broadcast_code(BroadcastId::try_from(0x030201).unwrap(), [1; 16]);
         pin_mut!(set_code_fut);
         let polled = set_code_fut.poll_unpin(&mut noop_cx);
 
diff --git a/rust/bt-bass/src/client/error.rs b/rust/bt-bass/src/client/error.rs
index 1f6e4bd..1173f4f 100644
--- a/rust/bt-bass/src/client/error.rs
+++ b/rust/bt-bass/src/client/error.rs
@@ -4,11 +4,10 @@
 
 use thiserror::Error;
 
+use bt_bap::types::BroadcastId;
 use bt_common::packet_encoding::Error as PacketError;
 use bt_gatt::types::{Error as BTGattError, Handle};
 
-use crate::types::BroadcastId;
-
 #[derive(Debug, Error)]
 pub enum Error {
     #[error(
diff --git a/rust/bt-bass/src/client/event.rs b/rust/bt-bass/src/client/event.rs
index 4077373..ca48fa8 100644
--- a/rust/bt-bass/src/client/event.rs
+++ b/rust/bt-bass/src/client/event.rs
@@ -10,6 +10,7 @@
 use futures::{Stream, StreamExt};
 use parking_lot::Mutex;
 
+use bt_bap::types::BroadcastId;
 use bt_common::packet_encoding::Decodable;
 use bt_gatt::client::CharacteristicNotification;
 use bt_gatt::types::Error as BtGattError;
@@ -273,15 +274,14 @@
 
         // Events should have been generated from notifications.
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
-
         let polled = event_streams.poll_next_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Some(Ok(event))) => {
-            assert_eq!(event, Event::AddedBroadcastSource(BroadcastId(0x030201), PaSyncState::FailedToSync, EncryptionStatus::BadCode([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])));
+            assert_eq!(event, Event::AddedBroadcastSource(BroadcastId::try_from(0x030201).unwrap(), PaSyncState::FailedToSync, EncryptionStatus::BadCode([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])));
         });
 
         let polled = event_streams.poll_next_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Some(Ok(event))) => {
-            assert_eq!(event, Event::AddedBroadcastSource(BroadcastId(0x040302), PaSyncState::NoPast, EncryptionStatus::NotEncrypted));
+            assert_eq!(event, Event::AddedBroadcastSource(BroadcastId::try_from(0x040302).unwrap(), PaSyncState::NoPast, EncryptionStatus::NotEncrypted));
         });
 
         // Should be pending because no more events generated from notifications.
@@ -306,7 +306,7 @@
 
         // Event should have been generated from notification.
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
-        assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::SyncedToPa(BroadcastId(0x040302))) });
+        assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::SyncedToPa(BroadcastId::try_from(0x040302).unwrap())) });
 
         // Should be pending because no more events generated from notifications.
         assert!(event_streams.poll_next_unpin(&mut noop_cx).is_pending());
@@ -348,7 +348,7 @@
 
         let polled = event_streams.poll_next_unpin(&mut noop_cx);
         assert_matches!(polled, Poll::Ready(Some(Ok(event))) => {
-            assert_eq!(event, Event::AddedBroadcastSource(BroadcastId(0x040302), PaSyncState::Synced, EncryptionStatus::NotEncrypted));
+            assert_eq!(event, Event::AddedBroadcastSource(BroadcastId::try_from(0x040302).unwrap(), PaSyncState::Synced, EncryptionStatus::NotEncrypted));
         });
 
         // Should be pending because no more events generated from notifications.
@@ -366,7 +366,7 @@
 
         // Event should have been generated from notification.
         let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
-        assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::RemovedBroadcastSource(BroadcastId(0x040302))) });
+        assert_matches!(event_streams.poll_next_unpin(&mut noop_cx), Poll::Ready(Some(Ok(event))) => { assert_eq!(event, Event::RemovedBroadcastSource(BroadcastId::try_from(0x040302).unwrap())) });
 
         // Should be pending because no more events generated from notifications.
         assert!(event_streams.poll_next_unpin(&mut noop_cx).is_pending());
diff --git a/rust/bt-bass/src/tests.rs b/rust/bt-bass/src/tests.rs
index 64d9107..4e3c0ab 100644
--- a/rust/bt-bass/src/tests.rs
+++ b/rust/bt-bass/src/tests.rs
@@ -11,7 +11,7 @@
 use crate::client::error::Error;
 use crate::client::event::*;
 use crate::test_utils::*;
-use crate::types::BroadcastId;
+use bt_bap::types::BroadcastId;
 
 #[test]
 fn fake_bass_event_stream() {
@@ -22,9 +22,11 @@
     assert!(stream.poll_next_unpin(&mut noop_cx).is_pending());
 
     // Add events.
-    sender.unbounded_send(Ok(Event::SyncedToPa(BroadcastId(1)))).expect("should succeed");
     sender
-        .unbounded_send(Ok(Event::BroadcastCodeRequired(BroadcastId(1))))
+        .unbounded_send(Ok(Event::SyncedToPa(BroadcastId::try_from(1).unwrap())))
+        .expect("should succeed");
+    sender
+        .unbounded_send(Ok(Event::BroadcastCodeRequired(BroadcastId::try_from(1).unwrap())))
         .expect("should succeed");
 
     let polled = stream.poll_next_unpin(&mut noop_cx);
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs
index 31c8b2d..0ca916a 100644
--- a/rust/bt-bass/src/types.rs
+++ b/rust/bt-bass/src/types.rs
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use bt_bap::types::BroadcastId;
 use bt_common::core::ltv::LtValue;
 use bt_common::core::{AddressType, AdvertisingSetId, PaInterval};
 use bt_common::generic_audio::metadata_ltv::*;
@@ -23,75 +24,6 @@
 /// BIS index value of a particular BIS. Valid value range is [1 to len of BIS]
 pub type BisIndex = u8;
 
-/// Broadcast_ID is a 3-byte data on the wire.
-/// See Broadcast Audio Scan Service (BASS) spec v1.0 Table 3.5 for details.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
-pub struct BroadcastId(pub u32);
-
-impl BroadcastId {
-    // On the wire, Broadcast_ID is transported in 3 bytes.
-    pub const BYTE_SIZE: usize = 3;
-
-    pub fn new(raw_value: u32) -> Self {
-        Self(raw_value)
-    }
-
-    pub fn raw_value(&self) -> u32 {
-        self.0
-    }
-}
-
-impl std::fmt::Display for BroadcastId {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        write!(f, "{:#08x}", self.0)
-    }
-}
-
-impl TryFrom<u32> for BroadcastId {
-    type Error = PacketError;
-
-    fn try_from(value: u32) -> Result<Self, Self::Error> {
-        const MAX_VALUE: u32 = 0xFFFFFF;
-        if value > MAX_VALUE {
-            return Err(PacketError::InvalidParameter(format!(
-                "Broadcast ID cannot exceed 3 bytes"
-            )));
-        }
-        Ok(BroadcastId(value))
-    }
-}
-
-impl Decodable for BroadcastId {
-    type Error = PacketError;
-
-    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
-        if buf.len() != Self::BYTE_SIZE {
-            return Err(PacketError::UnexpectedDataLength);
-        }
-
-        let padded_bytes = [buf[0], buf[1], buf[2], 0x00];
-        Ok((BroadcastId(u32::from_le_bytes(padded_bytes)), Self::BYTE_SIZE))
-    }
-}
-
-impl Encodable for BroadcastId {
-    type Error = PacketError;
-
-    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
-        if buf.len() < self.encoded_len() {
-            return Err(PacketError::BufferTooSmall);
-        }
-        // Since 3-byte value is being fit into u32, we ignore the most significant
-        // byte.
-        buf[0..3].copy_from_slice(&self.0.to_le_bytes()[0..3]);
-        Ok(())
-    }
-
-    fn encoded_len(&self) -> core::primitive::usize {
-        Self::BYTE_SIZE
-    }
-}
-
 decodable_enum! {
     pub enum ControlPointOpcode<u8, bt_common::packet_encoding::Error, OutOfRange> {
         RemoteScanStopped = 0x00,
@@ -614,8 +546,9 @@
         Self { bis_sync: bis_sync.unwrap_or_default(), metadata: vec![] }
     }
 
-    pub fn with_metadata(self, metadata: Vec<Metadata>) -> Self {
-        Self { bis_sync: self.bis_sync, metadata }
+    pub fn with_metadata(mut self, metadata: Vec<Metadata>) -> Self {
+        self.metadata = metadata;
+        self
     }
 }
 
@@ -771,7 +704,7 @@
         source_address_type: AddressType,
         source_address: [u8; ADDRESS_BYTE_SIZE],
         source_adv_sid: u8,
-        broadcast_id: u32,
+        broadcast_id: BroadcastId,
         pa_sync_state: PaSyncState,
         big_encryption: EncryptionStatus,
         subgroups: Vec<BigSubgroup>,
@@ -781,7 +714,7 @@
             source_address_type,
             source_address,
             source_adv_sid: AdvertisingSetId(source_adv_sid),
-            broadcast_id: BroadcastId(broadcast_id),
+            broadcast_id,
             pa_sync_state,
             big_encryption,
             subgroups,
@@ -990,25 +923,6 @@
     use bt_common::generic_audio::ContextType;
 
     #[test]
-    fn broadcast_id() {
-        let id = BroadcastId(0x000A0B0C);
-
-        assert_eq!(id.encoded_len(), 3);
-        let mut buf = vec![0; id.encoded_len()];
-        let _ = id.encode(&mut buf[..]).expect("should have succeeded");
-
-        let bytes = vec![0x0C, 0x0B, 0x0A];
-        assert_eq!(buf, bytes);
-
-        let (got, bytes) = BroadcastId::decode(&bytes).expect("should succeed");
-        assert_eq!(got, id);
-        assert_eq!(bytes, BroadcastId::BYTE_SIZE);
-        let got = BroadcastId::try_from(u32::from_le_bytes([0x0C, 0x0B, 0x0A, 0x00]))
-            .expect("should succeed");
-        assert_eq!(got, id);
-    }
-
-    #[test]
     fn encryption_status_enum() {
         let not_encrypted = EncryptionStatus::NotEncrypted;
         let encrypted = EncryptionStatus::BroadcastCodeRequired;
@@ -1147,7 +1061,7 @@
             AddressType::Public,
             [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
             AdvertisingSetId(1),
-            BroadcastId(0x11),
+            BroadcastId::try_from(0x11).unwrap(),
             PaSync::DoNotSync,
             PaInterval::unknown(),
             vec![],
@@ -1179,7 +1093,7 @@
             AddressType::Random,
             [0x04, 0x10, 0x00, 0x00, 0x00, 0x00],
             AdvertisingSetId(1),
-            BroadcastId(0x11),
+            BroadcastId::try_from(0x11).unwrap(),
             PaSync::SyncPastAvailable,
             PaInterval::unknown(),
             subgroups,
@@ -1298,7 +1212,7 @@
             source_address_type: AddressType::Public,
             source_address: [0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A],
             source_adv_sid: AdvertisingSetId(0x01),
-            broadcast_id: BroadcastId(0x00010203),
+            broadcast_id: BroadcastId::try_from(0x00010203).unwrap(),
             pa_sync_state: PaSyncState::Synced,
             big_encryption: EncryptionStatus::BadCode([
                 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x7, 0x06, 0x05, 0x04, 0x03,
@@ -1332,7 +1246,7 @@
             source_address_type: AddressType::Random,
             source_address: [0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A],
             source_adv_sid: AdvertisingSetId(0x01),
-            broadcast_id: BroadcastId(0x00010203),
+            broadcast_id: BroadcastId::try_from(0x00010203).unwrap(),
             pa_sync_state: PaSyncState::NotSynced,
             big_encryption: EncryptionStatus::NotEncrypted,
             subgroups: vec![
diff --git a/rust/bt-broadcast-assistant/Cargo.toml b/rust/bt-broadcast-assistant/Cargo.toml
index a3ccfa6..824bb18 100644
--- a/rust/bt-broadcast-assistant/Cargo.toml
+++ b/rust/bt-broadcast-assistant/Cargo.toml
@@ -5,6 +5,7 @@
 license.workspace = true
 
 [dependencies]
+bt-bap.workspace = true
 bt-bass.workspace = true
 bt-common.workspace = true
 bt-gatt.workspace = true
diff --git a/rust/bt-broadcast-assistant/src/assistant.rs b/rust/bt-broadcast-assistant/src/assistant.rs
index 3d3e61e..19102c7 100644
--- a/rust/bt-broadcast-assistant/src/assistant.rs
+++ b/rust/bt-broadcast-assistant/src/assistant.rs
@@ -7,9 +7,9 @@
 use std::sync::Arc;
 use thiserror::Error;
 
+use bt_bap::types::BroadcastId;
 use bt_bass::client::error::Error as BassClientError;
 use bt_bass::client::BroadcastAudioScanServiceClient;
-use bt_bass::types::BroadcastId;
 use bt_common::{PeerId, Uuid};
 use bt_gatt::central::*;
 use bt_gatt::client::PeerServiceHandle;
@@ -180,6 +180,7 @@
     use futures::{pin_mut, FutureExt};
     use std::task::Poll;
 
+    use bt_bap::types::*;
     use bt_common::core::{AddressType, AdvertisingSetId};
     use bt_gatt::test_utils::{FakeCentral, FakeClient, FakeTypes};
 
@@ -188,14 +189,14 @@
     #[test]
     fn merge_broadcast_source() {
         let discovered = DiscoveredBroadcastSources::new();
-
+        let bid = BroadcastId::try_from(1001).unwrap();
         let (bs, changed) = discovered.merge_broadcast_source_data(
             &PeerId(1001),
             &BroadcastSource::default()
                 .with_address([1, 2, 3, 4, 5, 6])
                 .with_address_type(AddressType::Public)
                 .with_advertising_sid(AdvertisingSetId(1))
-                .with_broadcast_id(BroadcastId(1001)),
+                .with_broadcast_id(bid),
         );
         assert!(changed);
         assert_eq!(
@@ -204,7 +205,7 @@
                 address: Some([1, 2, 3, 4, 5, 6]),
                 address_type: Some(AddressType::Public),
                 advertising_sid: Some(AdvertisingSetId(1)),
-                broadcast_id: Some(BroadcastId(1001)),
+                broadcast_id: Some(bid),
                 pa_interval: None,
                 endpoint: None,
             }
@@ -223,7 +224,7 @@
                 address: Some([1, 2, 3, 4, 5, 6]),
                 address_type: Some(AddressType::Random),
                 advertising_sid: Some(AdvertisingSetId(1)),
-                broadcast_id: Some(BroadcastId(1001)),
+                broadcast_id: Some(bid),
                 pa_interval: None,
                 endpoint: Some(BroadcastAudioSourceEndpoint {
                     presentation_delay_ms: 32,
diff --git a/rust/bt-broadcast-assistant/src/assistant/event.rs b/rust/bt-broadcast-assistant/src/assistant/event.rs
index 993ba6f..4990e9c 100644
--- a/rust/bt-broadcast-assistant/src/assistant/event.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/event.rs
@@ -6,7 +6,7 @@
 use std::sync::Arc;
 use std::task::Poll;
 
-use bt_bass::types::BroadcastId;
+use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastId};
 use bt_common::packet_encoding::Decodable;
 use bt_common::packet_encoding::Error as PacketError;
 use bt_common::PeerId;
@@ -17,7 +17,7 @@
     DiscoveredBroadcastSources, Error, BASIC_AUDIO_ANNOUNCEMENT_SERVICE,
     BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE,
 };
-use crate::types::{BroadcastAudioSourceEndpoint, BroadcastSource};
+use crate::types::BroadcastSource;
 
 #[derive(Debug)]
 pub enum Event {
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs
index 3369a4d..ad101cf 100644
--- a/rust/bt-broadcast-assistant/src/assistant/peer.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -7,10 +7,11 @@
 use std::sync::Arc;
 use thiserror::Error;
 
+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::types::{BroadcastId, PaSync};
+use bt_bass::types::PaSync;
 use bt_common::core::PaInterval;
 use bt_common::packet_encoding::Error as PacketError;
 use bt_common::PeerId;
@@ -110,11 +111,7 @@
                 broadcast_source.advertising_sid.unwrap(),
                 pa_sync,
                 broadcast_source.pa_interval.unwrap_or(PaInterval::unknown()),
-                broadcast_source
-                    .endpoint
-                    .unwrap()
-                    .get_bass_subgroups(bis_sync)
-                    .map_err(Error::PacketError)?,
+                broadcast_source.endpoint_to_big_subgroups(bis_sync).map_err(Error::PacketError)?,
             )
             .await
             .map_err(Into::into)
@@ -187,6 +184,7 @@
     use bt_gatt::types::{
         AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
     };
+
     use bt_gatt::Characteristic;
 
     use crate::types::BroadcastSource;
@@ -276,7 +274,7 @@
                 .with_address([1, 2, 3, 4, 5, 6])
                 .with_address_type(AddressType::Public)
                 .with_advertising_sid(AdvertisingSetId(1))
-                .with_broadcast_id(BroadcastId(1001)),
+                .with_broadcast_id(BroadcastId::try_from(1001).unwrap()),
         );
 
         // Should fail because not enough information.
diff --git a/rust/bt-broadcast-assistant/src/types.rs b/rust/bt-broadcast-assistant/src/types.rs
index 8c6074e..35d6c6e 100644
--- a/rust/bt-broadcast-assistant/src/types.rs
+++ b/rust/bt-broadcast-assistant/src/types.rs
@@ -2,14 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use bt_bap::types::*;
 use bt_bass::client::BigToBisSync;
-use bt_bass::types::{BigSubgroup, BisSync, BroadcastId};
-use bt_common::core::ltv::LtValue;
-use bt_common::core::{Address, AddressType, CodecId};
+use bt_bass::types::{BigSubgroup, BisSync};
+use bt_common::core::{Address, AddressType};
 use bt_common::core::{AdvertisingSetId, PaInterval};
-use bt_common::generic_audio::codec_configuration::CodecConfiguration;
-use bt_common::generic_audio::metadata_ltv::Metadata;
-use bt_common::packet_encoding::{Decodable, Error as PacketError};
+use bt_common::packet_encoding::Error as PacketError;
 
 /// Broadcast source data as advertised through Basic Audio Announcement
 /// PA and Broadcast Audio Announcement.
@@ -86,37 +84,29 @@
             self.endpoint = Some(endpoint.clone());
         }
     }
-}
 
-/// Parameters exposed as part of Basic Audio Announcement from Broadcast
-/// Sources. See BAP spec v1.0.1 Section 3.7.2.2 for more details.
-// TODO(b/308481381): Fill out the struct.
-#[derive(Clone, Debug, PartialEq)]
-pub struct BroadcastAudioSourceEndpoint {
-    // Delay is 3 bytes.
-    pub(crate) presentation_delay_ms: u32,
-    pub(crate) big: Vec<BroadcastIsochronousGroup>,
-}
-
-impl BroadcastAudioSourceEndpoint {
-    // Should contain presentation delay, num BIG, and at least one BIG praram.
-    const MIN_PACKET_SIZE: usize = 3 + 1 + BroadcastIsochronousGroup::MIN_PACKET_SIZE;
-
-    /// Returns the representation of this object's broadcast isochronous groups
-    /// that's usable with Broadcast Audio Scan Service operations.
+    /// Returns the representation of this object's endpoint field to
+    /// broadcast isochronous groups presetation that's usable with
+    /// Broadcast Audio Scan Service operations.
     ///
     /// # Arguments
     ///
     /// * `bis_sync` - BIG to BIS sync information. If the set is empty, no
     ///   preference value is used for all the BIGs
-    pub(crate) fn get_bass_subgroups(
+    pub(crate) fn endpoint_to_big_subgroups(
         &self,
         bis_sync: BigToBisSync,
     ) -> Result<Vec<BigSubgroup>, PacketError> {
+        if self.endpoint.is_none() {
+            return Err(PacketError::InvalidParameter(
+                "cannot convert empty Broadcast Audio Source Endpoint data to BIG subgroups data"
+                    .to_string(),
+            ));
+        }
         let mut subgroups = Vec::new();
         let sync_map = bt_bass::client::big_to_bis_sync_indices(&bis_sync);
 
-        for (big_index, group) in self.big.iter().enumerate() {
+        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);
@@ -131,305 +121,53 @@
     }
 }
 
-impl Decodable for BroadcastAudioSourceEndpoint {
-    type Error = PacketError;
-
-    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
-        if buf.len() < Self::MIN_PACKET_SIZE {
-            return Err(PacketError::UnexpectedDataLength);
-        }
-
-        let mut idx = 0 as usize;
-        let presentation_delay = u32::from_le_bytes([buf[idx], buf[idx + 1], buf[idx + 2], 0x00]);
-        idx += 3;
-
-        let num_big: usize = buf[idx] as usize;
-        idx += 1;
-        if num_big < 1 {
-            return Err(PacketError::InvalidParameter(format!(
-                "num of subgroups shall be at least 1 got {num_big}"
-            )));
-        }
-
-        let mut big = Vec::new();
-        while big.len() < num_big {
-            let (group, len) = BroadcastIsochronousGroup::decode(&buf[idx..])
-                .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
-            big.push(group);
-            idx += len;
-        }
-
-        Ok((Self { presentation_delay_ms: presentation_delay, big }, idx))
-    }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct BroadcastIsochronousGroup {
-    pub(crate) codec_id: CodecId,
-    pub(crate) codec_specific_config: Vec<CodecConfiguration>,
-    pub(crate) metadata: Vec<Metadata>,
-    pub(crate) bis: Vec<BroadcastIsochronousStream>,
-}
-
-impl BroadcastIsochronousGroup {
-    // Should contain num BIS, codec id, codec specific config len, metadata len,
-    // and at least one BIS praram.
-    const MIN_PACKET_SIZE: usize =
-        1 + CodecId::BYTE_SIZE + 1 + 1 + BroadcastIsochronousStream::MIN_PACKET_SIZE;
-}
-
-impl Decodable for BroadcastIsochronousGroup {
-    type Error = PacketError;
-
-    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
-        if buf.len() < BroadcastIsochronousGroup::MIN_PACKET_SIZE {
-            return Err(PacketError::UnexpectedDataLength);
-        }
-
-        let mut idx = 0;
-        let num_bis = buf[idx] as usize;
-        idx += 1;
-        if num_bis < 1 {
-            return Err(PacketError::InvalidParameter(format!(
-                "num of BIS shall be at least 1 got {num_bis}"
-            )));
-        }
-
-        let (codec_id, read_bytes) = CodecId::decode(&buf[idx..])?;
-        idx += read_bytes;
-
-        let codec_config_len = buf[idx] as usize;
-        idx += 1;
-
-        let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
-        if consumed != codec_config_len {
-            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
-        }
-
-        let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
-        idx += codec_config_len;
-
-        let metadata_len = buf[idx] as usize;
-        idx += 1;
-
-        let (results_metadata, consumed_len) = Metadata::decode_all(&buf[idx..idx + metadata_len]);
-        if consumed_len != metadata_len {
-            return Err(PacketError::UnexpectedDataLength);
-        }
-        // Ignore any undecodable metadata types
-        let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
-        idx += consumed_len;
-
-        let mut bis = Vec::new();
-        while bis.len() < num_bis {
-            let (stream, len) = BroadcastIsochronousStream::decode(&buf[idx..])
-                .map_err(|e| PacketError::InvalidParameter(e.to_string()))?;
-            bis.push(stream);
-            idx += len;
-        }
-
-        Ok((
-            BroadcastIsochronousGroup {
-                codec_id,
-                codec_specific_config: codec_specific_configs,
-                metadata,
-                bis,
-            },
-            idx,
-        ))
-    }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct BroadcastIsochronousStream {
-    pub(crate) bis_index: u8,
-    pub(crate) codec_specific_config: Vec<CodecConfiguration>,
-}
-
-impl BroadcastIsochronousStream {
-    const MIN_PACKET_SIZE: usize = 1 + 1;
-}
-
-impl Decodable for BroadcastIsochronousStream {
-    type Error = PacketError;
-
-    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
-        if buf.len() < BroadcastIsochronousStream::MIN_PACKET_SIZE {
-            return Err(PacketError::UnexpectedDataLength);
-        }
-
-        let mut idx = 0;
-
-        let bis_index = buf[idx];
-        idx += 1;
-
-        let codec_config_len = buf[idx] as usize;
-        idx += 1;
-
-        let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
-        if consumed != codec_config_len {
-            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
-        }
-        let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
-        idx += codec_config_len;
-
-        Ok((
-            BroadcastIsochronousStream { bis_index, codec_specific_config: codec_specific_configs },
-            idx,
-        ))
-    }
-}
-
 #[cfg(test)]
 mod tests {
-    use std::collections::HashSet;
-
     use super::*;
 
-    use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency};
-    use bt_common::generic_audio::AudioLocation;
+    use std::collections::HashSet;
+
+    use bt_common::core::CodecId;
+    use bt_common::generic_audio::metadata_ltv::Metadata;
 
     #[test]
-    fn decode_bis() {
-        #[rustfmt::skip]
-        let buf = [
-            0x01, 0x09,                          // bis index and codec specific config len
-            0x02, 0x01, 0x06,                    // sampling frequency LTV
-            0x05, 0x03, 0x03, 0x00, 0x00, 0x0C,  // audio location LTV
-        ];
+    fn broadcast_source() {
+        let mut b = BroadcastSource::default();
+        assert!(!b.into_add_source());
 
-        let (bis, _read_bytes) =
-            BroadcastIsochronousStream::decode(&buf[..]).expect("should not fail");
-        assert_eq!(
-            bis,
-            BroadcastIsochronousStream {
-                bis_index: 0x01,
-                codec_specific_config: vec![
-                    CodecConfiguration::SamplingFrequency(SamplingFrequency::F32000Hz),
-                    CodecConfiguration::AudioChannelAllocation(HashSet::from([
-                        AudioLocation::FrontLeft,
-                        AudioLocation::FrontRight,
-                        AudioLocation::LeftSurround,
-                        AudioLocation::RightSurround
-                    ])),
-                ],
-            }
+        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()),
         );
-    }
 
-    #[test]
-    fn decode_big() {
-        #[rustfmt::skip]
-        let buf = [
-            0x02,  0x03, 0x00, 0x00, 0x00, 0x00,  // num of bis, codec id
-            0x04, 0x03, 0x04, 0x04, 0x10,         // codec specific config len, octets per codec frame LTV
-            0x03, 0x02, 0x08, 0x01,               // metadata len, audio active state LTV
-            0x01, 0x00,                           // bis index, codec specific config len (bis #1)
-            0x02, 0x03, 0x02, 0x02, 0x01,         // bis index, codec specific config len, frame duration LTV (bis #2)
-        ];
+        assert!(!b.into_add_source());
+        b.endpoint_to_big_subgroups(HashSet::from([(0, 1)]))
+            .expect_err("should fail no endpoint data");
 
-        let (big, _read_bytes) =
-            BroadcastIsochronousGroup::decode(&buf[..]).expect("should not fail");
-        assert_eq!(
-            big,
-            BroadcastIsochronousGroup {
-                codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
-                codec_specific_config: vec![CodecConfiguration::OctetsPerCodecFrame(0x1004),],
-                metadata: vec![Metadata::AudioActiveState(true)],
-                bis: vec![
-                    BroadcastIsochronousStream { bis_index: 0x01, codec_specific_config: vec![] },
-                    BroadcastIsochronousStream {
-                        bis_index: 0x02,
-                        codec_specific_config: vec![CodecConfiguration::FrameDuration(
-                            FrameDuration::TenMs
-                        )],
-                    },
-                ],
-            }
-        );
-    }
-
-    #[test]
-    fn decode_base() {
-        #[rustfmt::skip]
-        let buf = [
-            0x10, 0x20, 0x30, 0x02,               // presentation delay, num of subgroups
-            0x01, 0x03, 0x00, 0x00, 0x00, 0x00,   // num of bis, codec id (big #1)
-            0x00,                                 // codec specific config len
-            0x00,                                 // metadata len,
-            0x01, 0x00,                           // bis index, codec specific config len (big #1 / bis #1)
-            0x01, 0x02, 0x00, 0x00, 0x00, 0x00,   // num of bis, codec id (big #2)
-            0x00,                                 // codec specific config len
-            0x00,                                 // metadata len,
-            0x01, 0x03, 0x02, 0x05, 0x08,         // bis index, codec specific config len, codec frame blocks LTV (big #2 / bis #2)
-        ];
-
-        let (base, _read_bytes) =
-            BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
-        assert_eq!(base.presentation_delay_ms, 0x00302010);
-        assert_eq!(base.big.len(), 2);
-        assert_eq!(
-            base.big[0],
-            BroadcastIsochronousGroup {
-                codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent),
-                codec_specific_config: vec![],
-                metadata: vec![],
-                bis: vec![BroadcastIsochronousStream {
-                    bis_index: 0x01,
-                    codec_specific_config: vec![],
-                },],
-            }
-        );
-        assert_eq!(
-            base.big[1],
-            BroadcastIsochronousGroup {
+        b.merge(&BroadcastSource::default().with_endpoint(BroadcastAudioSourceEndpoint {
+            presentation_delay_ms: 0x010203,
+            big: vec![BroadcastIsochronousGroup {
                 codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd),
-                codec_specific_config: vec![],
-                metadata: vec![],
+                codec_specific_configs: vec![],
+                metadata: vec![Metadata::BroadcastAudioImmediateRenderingFlag],
                 bis: vec![BroadcastIsochronousStream {
-                    bis_index: 0x01,
-                    codec_specific_config: vec![CodecConfiguration::CodecFramesPerSdu(0x08)],
-                },],
-            }
+                    bis_index: 1,
+                    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");
+        assert_eq!(subgroups.len(), 1);
+        assert_eq!(
+            subgroups[0],
+            BigSubgroup::new(Some(BisSync(0x00000001)))
+                .with_metadata(vec![Metadata::BroadcastAudioImmediateRenderingFlag])
         );
     }
-
-    #[test]
-    fn decode_base_complex() {
-        // BroadcastAudioSourceEndpoint { presentation_delay_ms: 20000, big:
-        // [BroadcastIsochronousGroup { codec_id: Assigned(Lc3), codec_specific_config:
-        // [SamplingFrequency(F24000Hz), FrameDuration(TenMs),
-        // AudioChannelAllocation({FrontLeft, FrontRight}), OctetsPerCodecFrame(60)],
-        // metadata: [StreamingAudioContexts([Media])], bis:
-        // [BroadcastIsochronousStream { bis_index: 1, codec_specific_config:
-        // [AudioChannelAllocation({FrontLeft})] }, BroadcastIsochronousStream {
-        // bis_index: 2, codec_specific_con$
-        // ig: [AudioChannelAllocation({FrontRight})] }] }] }
-        #[rustfmt::skip]
-        let buf = [
-            0x20, 0x4e, 0x00, 0x01, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05,
-            0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x04, 0x3c, 0x00, 0x04, 0x03, 0x02, 0x04, 0x00, 0x01, 0x06, 0x05, 0x03, 0x01,
-            0x00, 0x00, 0x00, 0x02, 0x06, 0x05, 0x03, 0x02, 0x00, 0x00, 0x00
-        ];
-        let (base, _read_bytes) =
-            BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
-        println!("{base:?}");
-
-        // BroadcastAudioSourceEndpoint { presentation_delay_ms: 20000, big:
-        // [BroadcastIsochronousGroup { codec_id: Assigned(Lc3), codec_specific_config:
-        // [SamplingFrequency(F24000Hz), FrameDuration(TenMs),
-        // AudioChannelAllocation({FrontLeft}), OctetsPerCodecFrame(60)],
-        // metadata: [BroadcastAudioImmediateRenderingFlag], bis:
-        // [BroadcastIsochronousStream { bis_index: 1, codec_specific_config:
-        // [SamplingFrequency(F24000Hz)] }] }] }
-        #[rustfmt::skip]
-        let buf = [
-            0x20, 0x4e, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x05,
-            0x02, 0x02, 0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x3c, 0x00, 0x02,
-            0x01, 0x09, 0x01, 0x03, 0x02, 0x01, 0x05,
-        ];
-        let (base, _read_bytes) =
-            BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
-        println!("{base:?}");
-    }
 }