Skip to main content

bt_gatt_fuchsia/
pii.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use bt_common::PeerId;
6use bt_common::core::{Address, AddressType};
7use bt_gatt::pii::GetPeerAddr;
8use bt_gatt::{Result, types};
9use fidl_fuchsia_bluetooth_sys::{AddressLookupLookupRequest, AddressLookupProxy, LookupError};
10use fuchsia_sync::Mutex;
11use std::collections::HashMap;
12
13use crate::to_fidl_peer_id;
14
15/// An implementation of `GetPeerAddr` that uses the `fuchsia.bluetooth.sys.AddressLookup`
16/// FIDL service.
17pub struct FuchsiaPeerAddr {
18    proxy: AddressLookupProxy,
19    cache: Mutex<HashMap<PeerId, (Address, AddressType)>>,
20}
21
22fn from_fidl_address(addr: fidl_fuchsia_bluetooth::Address) -> (Address, AddressType) {
23    let addr_type = match addr.type_ {
24        fidl_fuchsia_bluetooth::AddressType::Public => AddressType::Public,
25        fidl_fuchsia_bluetooth::AddressType::Random => AddressType::Random,
26    };
27    (addr.bytes.into(), addr_type)
28}
29
30impl FuchsiaPeerAddr {
31    pub fn new(proxy: AddressLookupProxy) -> Self {
32        Self { proxy, cache: Mutex::new(HashMap::new()) }
33    }
34}
35
36impl GetPeerAddr for FuchsiaPeerAddr {
37    async fn get_peer_address(&self, peer_id: PeerId) -> Result<(Address, AddressType)> {
38        // Check for the address in the cache. The lock is released after this scope.
39        if let Some(addr) = self.cache.lock().get(&peer_id) {
40            return Ok(*addr);
41        }
42
43        // If not in the cache, perform the FIDL call. The lock is not held.
44        let result = self
45            .proxy
46            .lookup(&AddressLookupLookupRequest {
47                peer_id: Some(to_fidl_peer_id(&peer_id)),
48                ..Default::default()
49            })
50            .await;
51
52        match result {
53            Ok(Ok(addr)) => {
54                let addr = from_fidl_address(addr);
55                // Lock again to insert the new address. The lock is released after this statement.
56                let _ = self.cache.lock().insert(peer_id, addr);
57                Ok(addr)
58            }
59            Ok(Err(LookupError::NotFound)) => Err(types::Error::PeerNotRecognized(peer_id)),
60            Ok(Err(e)) => Err(types::Error::from(format!("AddressLookup error: {:?}", e))),
61            Err(fidl_err) => Err(types::Error::other(fidl_err)),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use fidl::endpoints::create_proxy_and_stream;
70    use fidl_fuchsia_bluetooth_sys::AddressLookupRequest;
71    use fuchsia_async as fasync;
72    use futures::pin_mut;
73    use futures::stream::StreamExt;
74    use futures::task::Poll;
75
76    #[test]
77    fn test_get_peer_address_success() {
78        let mut exec = fasync::TestExecutor::new();
79        let (proxy, mut stream) =
80            create_proxy_and_stream::<fidl_fuchsia_bluetooth_sys::AddressLookupMarker>();
81
82        let peer_id = PeerId(123);
83        let addr = fidl_fuchsia_bluetooth::Address {
84            type_: fidl_fuchsia_bluetooth::AddressType::Random,
85            bytes: [1, 2, 3, 4, 5, 6],
86        };
87
88        let lookup = FuchsiaPeerAddr::new(proxy);
89
90        // First call - should be a cache miss and result in a FIDL call.
91        let client_fut = lookup.get_peer_address(peer_id);
92        pin_mut!(client_fut);
93        assert!(exec.run_until_stalled(&mut client_fut).is_pending());
94
95        // Handle the FIDL request from the stream.
96        let stream_fut = stream.next();
97        pin_mut!(stream_fut);
98        let stream_result = exec.run_until_stalled(&mut stream_fut);
99        let Poll::Ready(Some(Ok(AddressLookupRequest::Lookup { payload, responder }))) =
100            stream_result
101        else {
102            panic!("Expected Lookup request, got {:?}", stream_result);
103        };
104        assert_eq!(peer_id.0, payload.peer_id.unwrap().value);
105        responder.send(Ok(&addr.into())).unwrap();
106
107        // Now the client future should complete.
108        let client_result = exec.run_until_stalled(&mut client_fut);
109        let Poll::Ready(Ok((found_addr, found_type))) = client_result else {
110            panic!("Expected future to complete with Ok, got {:?}", client_result);
111        };
112        let (expected_addr, expected_type) = from_fidl_address(addr);
113        assert_eq!(found_addr, expected_addr);
114        assert_eq!(found_type, expected_type);
115
116        // Second call - should be a cache hit.
117        let client_fut2 = lookup.get_peer_address(peer_id);
118        pin_mut!(client_fut2);
119        let client_result2 = exec.run_until_stalled(&mut client_fut2);
120        let Poll::Ready(Ok((found_addr, found_type))) = client_result2 else {
121            panic!("Expected future to complete immediately from cache, got {:?}", client_result2);
122        };
123        assert_eq!(found_addr, expected_addr);
124        assert_eq!(found_type, expected_type);
125
126        // Verify that no FIDL request was made on the second call.
127        let stream_fut = stream.next();
128        pin_mut!(stream_fut);
129        assert!(exec.run_until_stalled(&mut stream_fut).is_pending());
130    }
131
132    #[test]
133    fn test_get_peer_address_not_found() {
134        let mut exec = fasync::TestExecutor::new();
135        let (proxy, mut stream) =
136            create_proxy_and_stream::<fidl_fuchsia_bluetooth_sys::AddressLookupMarker>();
137
138        let peer_id = PeerId(123);
139
140        let lookup = FuchsiaPeerAddr::new(proxy);
141
142        let client_fut = lookup.get_peer_address(peer_id);
143        pin_mut!(client_fut);
144        assert!(exec.run_until_stalled(&mut client_fut).is_pending());
145
146        // Handle the FIDL request from the stream.
147        let stream_fut = stream.next();
148        pin_mut!(stream_fut);
149        let stream_result = exec.run_until_stalled(&mut stream_fut);
150        let Poll::Ready(Some(Ok(AddressLookupRequest::Lookup { payload, responder }))) =
151            stream_result
152        else {
153            panic!("Expected Lookup request, got {:?}", stream_result);
154        };
155        assert_eq!(peer_id.0, payload.peer_id.unwrap().value);
156        responder.send(Err(LookupError::NotFound)).unwrap();
157
158        // Now the client future should complete with an error.
159        let client_result = exec.run_until_stalled(&mut client_fut);
160        let Poll::Ready(Err(e)) = client_result else {
161            panic!("Expected future to complete with Err, got {:?}", client_result);
162        };
163        assert!(matches!(e, types::Error::PeerNotRecognized(id) if id == peer_id));
164    }
165}