rust/bt-gatt: Add module for PII-sensitive ops

Adds a trait for obtaining the bluetooth address of a peerid into
a new module for operations that involve privacy-sensitive
information.

Also makes that trait an argument for the broadcast assistant's
`add_broadcast_source` method, so that the address can be filled in.

Fixed: 416319216
Change-Id: I81da037194a04536be2dc7dfdb98ce0bee100fbb
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/2440
Reviewed-by: Marie Janssen <jamuraa@google.com>
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs
index ad101cf..5131604 100644
--- a/rust/bt-broadcast-assistant/src/assistant/peer.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use futures::stream::FusedStream;
+use bt_gatt::pii::GetPeerAddr;
 use futures::Stream;
+use futures::stream::FusedStream;
 use std::sync::Arc;
 use thiserror::Error;
 
@@ -32,6 +33,9 @@
     #[error("Broadcast source with peer id ({0}) does not exist")]
     DoesNotExist(PeerId),
 
+    #[error("Failed to lookup address for peer id ({0}): {1:?}")]
+    AddressLookupError(PeerId, bt_gatt::types::Error),
+
     #[error("Packet error: {0}")]
     PacketError(#[from] PacketError),
 }
@@ -86,19 +90,29 @@
     ///
     /// * `broadcast_source_pid` - peer id of the braodcast source that's to be
     ///   added to this scan delegator peer
+    /// * `address_lookup` - An implementation of [`GetPeerAddr`] that will
+    ///   be used to look up the peer's address.
     /// * `pa_sync` - pa sync mode the peer should attempt to be in
     /// * `bis_sync` - desired BIG to BIS synchronization information. If the
     ///   set is empty, no preference value is used for all the BIGs
     pub async fn add_broadcast_source(
         &mut self,
         source_peer_id: PeerId,
+        address_lookup: &impl GetPeerAddr,
         pa_sync: PaSync,
         bis_sync: BigToBisSync,
     ) -> Result<(), Error> {
-        let broadcast_source = self
+        let mut broadcast_source = self
             .broadcast_sources
             .get_by_peer_id(&source_peer_id)
             .ok_or(Error::DoesNotExist(source_peer_id))?;
+
+        let (broadcast_addr, broadcast_addr_type) = address_lookup
+            .get_peer_address(source_peer_id)
+            .await
+            .map_err(|err| Error::AddressLookupError(source_peer_id, err))?;
+        broadcast_source.with_address(broadcast_addr).with_address_type(broadcast_addr_type);
+
         if !broadcast_source.into_add_source() {
             return Err(Error::NotEnoughInfo(source_peer_id));
         }
@@ -175,12 +189,13 @@
     use super::*;
 
     use assert_matches::assert_matches;
-    use futures::{pin_mut, FutureExt};
+    use bt_gatt::pii::StaticPeerAddr;
+    use futures::{FutureExt, pin_mut};
     use std::collections::HashSet;
     use std::task::Poll;
 
     use bt_common::core::{AddressType, AdvertisingSetId};
-    use bt_gatt::test_utils::{FakeClient, FakePeerService, FakeTypes};
+    use bt_gatt::test_utils::{FakeClient, FakeGetPeerAddr, FakePeerService, FakeTypes};
     use bt_gatt::types::{
         AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
     };
@@ -260,33 +275,50 @@
         {
             let fut = peer.add_broadcast_source(
                 PeerId(1001),
+                &FakeGetPeerAddr,
                 PaSync::SyncPastUnavailable,
                 HashSet::new(),
             );
             pin_mut!(fut);
             let polled = fut.poll_unpin(&mut noop_cx);
-            assert_matches!(polled, Poll::Ready(Err(_)));
+            assert_matches!(polled, Poll::Ready(Err(Error::DoesNotExist(_))));
         }
 
         let _ = broadcast_source.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::try_from(1001).unwrap()),
         );
 
-        // Should fail because not enough information.
+        // Should fail because peer address couldn't be looked up.
         {
+            let address_lookup =
+                StaticPeerAddr::new_for_peer(PeerId(1002), [1, 2, 3, 4, 5, 6], AddressType::Public);
             let fut = peer.add_broadcast_source(
                 PeerId(1001),
+                &address_lookup,
                 PaSync::SyncPastUnavailable,
                 HashSet::new(),
             );
             pin_mut!(fut);
             let polled = fut.poll_unpin(&mut noop_cx);
-            assert_matches!(polled, Poll::Ready(Err(_)));
+            assert_matches!(polled, Poll::Ready(Err(Error::AddressLookupError(_, _))));
+        }
+
+        // Should fail because not enough information.
+        {
+            let address_lookup =
+                StaticPeerAddr::new_for_peer(PeerId(1001), [1, 2, 3, 4, 5, 6], AddressType::Public);
+            let fut = peer.add_broadcast_source(
+                PeerId(1001),
+                &address_lookup,
+                PaSync::SyncPastUnavailable,
+                HashSet::new(),
+            );
+            pin_mut!(fut);
+            let polled = fut.poll_unpin(&mut noop_cx);
+            assert_matches!(polled, Poll::Ready(Err(Error::NotEnoughInfo(_))));
         }
     }
 }
diff --git a/rust/bt-gatt/src/lib.rs b/rust/bt-gatt/src/lib.rs
index f5adefd..74c7f3b 100644
--- a/rust/bt-gatt/src/lib.rs
+++ b/rust/bt-gatt/src/lib.rs
@@ -14,6 +14,8 @@
 pub mod central;
 pub use central::Central;
 
+pub mod pii;
+
 #[cfg(any(test, feature = "test-utils"))]
 pub mod test_utils;
 
diff --git a/rust/bt-gatt/src/pii.rs b/rust/bt-gatt/src/pii.rs
new file mode 100644
index 0000000..f165db6
--- /dev/null
+++ b/rust/bt-gatt/src/pii.rs
@@ -0,0 +1,55 @@
+// Copyright 2025 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 std::future::Future;
+
+use bt_common::{
+    PeerId,
+    core::{Address, AddressType},
+};
+
+use crate::types::*;
+
+/// Takes a peer address and queries an underlying service for its actual
+/// bluetooth address and address type. Used for adding broadcast sources.
+pub trait GetPeerAddr {
+    /// Resolve peer ID to peer address and address type.
+    fn get_peer_address(
+        &self,
+        peer_id: PeerId,
+    ) -> impl Future<Output = Result<(Address, AddressType)>>;
+}
+
+/// Helper for when the peer's address is known. Always returns the given
+/// address, if any.
+pub struct StaticPeerAddr {
+    peer_id: Option<PeerId>,
+    address: Address,
+    address_type: AddressType,
+}
+
+impl StaticPeerAddr {
+    /// Returns a [`StaticPeerAddr`] that always returns the given address,
+    /// regardless of the `peer_id` being looked up.
+    pub fn new(address: Address, address_type: AddressType) -> Self {
+        Self { peer_id: None, address, address_type }
+    }
+
+    /// Returns a [`StaticPeerAddr`] that will only return successfully if the
+    /// `peer_id` matches.
+    pub fn new_for_peer(peer_id: PeerId, address: Address, address_type: AddressType) -> Self {
+        Self { peer_id: Some(peer_id), address, address_type }
+    }
+}
+
+impl GetPeerAddr for StaticPeerAddr {
+    async fn get_peer_address(&self, peer_id: PeerId) -> Result<(Address, AddressType)> {
+        if let Some(validated_peer_id) = self.peer_id {
+            if peer_id != validated_peer_id {
+                return Err(Error::PeerNotRecognized(peer_id));
+            }
+        }
+        return Ok((self.address, self.address_type));
+    }
+}
diff --git a/rust/bt-gatt/src/test_utils.rs b/rust/bt-gatt/src/test_utils.rs
index bc3d88f..15ad2fa 100644
--- a/rust/bt-gatt/src/test_utils.rs
+++ b/rust/bt-gatt/src/test_utils.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_common::core::{Address, AddressType};
 use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
 use futures::future::{ready, Ready};
 use futures::Stream;
@@ -14,8 +15,9 @@
 
 use crate::central::ScanResult;
 use crate::client::CharacteristicNotification;
+use crate::pii::GetPeerAddr;
 use crate::server::{self, LocalService, ReadResponder, ServiceDefinition, WriteResponder};
-use crate::{types::*, GattTypes, ServerTypes};
+use crate::{GattTypes, ServerTypes, types::*};
 
 #[derive(Default)]
 struct FakePeerServiceInner {
@@ -256,6 +258,26 @@
     }
 }
 
+/// Implements a fake [`GetPeerAddr`] that just converts the peer_id into a
+/// public [`Address`] based on the given peer_id.
+pub struct FakeGetPeerAddr;
+
+impl GetPeerAddr for FakeGetPeerAddr {
+    async fn get_peer_address(&self, peer_id: PeerId) -> Result<(Address, AddressType)> {
+        Ok((
+            [
+                peer_id.0 as u8,
+                ((peer_id.0 >> 8) & 0xff) as u8,
+                ((peer_id.0 >> 16) & 0xff) as u8,
+                ((peer_id.0 >> 24) & 0xff) as u8,
+                ((peer_id.0 >> 32) & 0xff) as u8,
+                ((peer_id.0 >> 48) & 0xff) as u8,
+            ],
+            AddressType::Public,
+        ))
+    }
+}
+
 pub struct FakeTypes {}
 
 impl GattTypes for FakeTypes {