1use async_utils::hanging_get::client::HangingGetStream;
6use bt_common::PeerId;
7use fidl_fuchsia_bluetooth_le as fidl_le;
8use futures::stream::Stream;
9use futures::{Future, StreamExt, TryStreamExt};
10use std::pin::Pin;
11
12#[cfg(test)]
13use crate::to_fidl_uuid;
14use crate::{to_fidl_peer_id, to_gatt_scan_data};
15
16fn to_gatt_sync_error(err: fidl_le::PeriodicAdvertisingSyncError) -> bt_gatt::types::Error {
17 let gatt_err = match err {
18 fidl_le::PeriodicAdvertisingSyncError::InitialSynchronizationFailed => {
19 bt_gatt::periodic_advertising::Error::SyncEstablishFailed
20 }
21 fidl_le::PeriodicAdvertisingSyncError::SynchronizationLost => {
22 bt_gatt::periodic_advertising::Error::SyncLost
23 }
24 _ => bt_gatt::periodic_advertising::Error::Io,
25 };
26 bt_gatt::types::Error::Other(Box::new(gatt_err))
27}
28
29fn to_gatt_phy(phy: fidl_le::PhysicalLayer) -> bt_common::core::Phy {
30 match phy {
31 fidl_le::PhysicalLayer::Le1M => bt_common::core::Phy::Le1m,
32 fidl_le::PhysicalLayer::Le2M => bt_common::core::Phy::Le2m,
33 fidl_le::PhysicalLayer::LeCoded => bt_common::core::Phy::LeCoded,
34 _ => bt_common::core::Phy::Le1m,
35 }
36}
37
38fn to_gatt_big_info(
39 info: &fidl_le::BroadcastIsochronousGroupInfo,
40) -> bt_gatt::periodic_advertising::BroadcastIsochronousGroupInfo {
41 bt_gatt::periodic_advertising::BroadcastIsochronousGroupInfo {
42 streams_count: info.streams_count.unwrap_or(0),
43 sdu_interval: 0, max_sdu_size: info.max_sdu_size.unwrap_or(0),
45 phy: info.phy.map(to_gatt_phy).unwrap_or(bt_common::core::Phy::Le1m),
46 encryption: info.encryption.unwrap_or(false),
47 }
48}
49
50fn to_gatt_periodic_advertising_report(
51 report: fidl_le::PeriodicAdvertisingReport,
52) -> bt_gatt::periodic_advertising::PeriodicAdvertisingReport {
53 bt_gatt::periodic_advertising::PeriodicAdvertisingReport {
54 rssi: report.rssi.unwrap_or(0),
55 data: report.data.map(to_gatt_scan_data).unwrap_or_default(),
56 event_counter: report.event_counter,
57 subevent: report.subevent,
58 timestamp: report.timestamp.unwrap_or(0),
59 }
60}
61
62fn to_gatt_sync_report(
63 report: fidl_le::SyncReport,
64) -> bt_gatt::Result<Option<bt_gatt::periodic_advertising::SyncReport>> {
65 match report {
66 fidl_le::SyncReport::PeriodicAdvertisingReport(r) => {
67 Ok(Some(bt_gatt::periodic_advertising::SyncReport::PeriodicAdvertisingReport(
68 to_gatt_periodic_advertising_report(r),
69 )))
70 }
71 fidl_le::SyncReport::BroadcastIsochronousGroupInfoReport(r) => {
72 match &r.info {
73 Some(info) => {
74 let info = to_gatt_big_info(info);
75 Ok(Some(bt_gatt::periodic_advertising::SyncReport::BroadcastIsochronousGroupInfoReport(
76 bt_gatt::periodic_advertising::BroadcastIsochronousGroupInfoReport {
77 info,
78 timestamp: r.timestamp.unwrap_or(0),
79 },
80 )))
81 }
82 None => Ok(None),
83 }
84 }
85 _ => Err(bt_gatt::types::Error::Other(Box::new(std::io::Error::new(
86 std::io::ErrorKind::InvalidData,
87 "unknown SyncReport variant",
88 )))),
89 }
90}
91
92fn create_sync_stream(
93 sync_proxy: fidl_le::PeriodicAdvertisingSyncProxy,
94 event_stream: fidl_le::PeriodicAdvertisingSyncEventStream,
95) -> <PeriodicAdvertising as bt_gatt::periodic_advertising::PeriodicAdvertising>::SyncStream {
96 let hanging_get_stream =
97 HangingGetStream::new_eager(sync_proxy, |p| p.watch_advertising_report());
98
99 let reports_stream = hanging_get_stream
100 .map_err(|e| bt_gatt::types::Error::Other(Box::new(e)))
101 .map(|res| match res {
102 Ok(response) => {
103 let reports = response.reports.unwrap_or_default();
104 let gatt_reports: Vec<bt_gatt::Result<bt_gatt::periodic_advertising::SyncReport>> =
105 reports
106 .into_iter()
107 .filter_map(|r| match to_gatt_sync_report(r) {
108 Ok(Some(report)) => Some(Ok(report)),
109 Ok(None) => None,
110 Err(e) => Some(Err(e)),
111 })
112 .collect();
113 futures::stream::iter(gatt_reports)
114 }
115 Err(e) => futures::stream::iter(vec![Err(e)]),
116 })
117 .flatten();
118
119 let event_errors = event_stream.filter_map(|event_res| {
120 let res = match event_res {
121 Ok(fidl_le::PeriodicAdvertisingSyncEvent::OnError { error }) => {
122 Some(Err(to_gatt_sync_error(error)))
123 }
124 Ok(_) => Some(Err(bt_gatt::types::Error::Other(Box::new(std::io::Error::new(
125 std::io::ErrorKind::InvalidData,
126 "unexpected event after establishment",
127 ))))),
128 Err(e) => Some(Err(bt_gatt::types::Error::Other(Box::new(e)))),
129 };
130 futures::future::ready(res)
131 });
132
133 let merged_stream = futures::stream::select(reports_stream, event_errors);
134
135 Box::pin(merged_stream)
136}
137
138#[derive(Clone)]
139pub struct PeriodicAdvertising {
140 pub(crate) proxy: fidl_le::CentralProxy,
141}
142
143impl bt_gatt::periodic_advertising::PeriodicAdvertising for PeriodicAdvertising {
144 type SyncFut = Pin<Box<dyn Future<Output = bt_gatt::Result<Self::SyncStream>> + Send>>;
145 type SyncStream = Pin<
146 Box<
147 dyn Stream<Item = bt_gatt::Result<bt_gatt::periodic_advertising::SyncReport>>
148 + Send
149 + 'static,
150 >,
151 >;
152
153 fn sync_to_advertising_reports(
154 &self,
155 peer_id: PeerId,
156 adv_sid: u8,
157 config: bt_gatt::periodic_advertising::SyncConfiguration,
158 ) -> Self::SyncFut {
159 let proxy = self.proxy.clone();
160
161 Box::pin(async move {
162 let (sync_proxy, server_end) =
163 fidl::endpoints::create_proxy::<fidl_le::PeriodicAdvertisingSyncMarker>();
164
165 let fidl_config = fidl_le::PeriodicAdvertisingSyncConfiguration {
166 filter_duplicates: Some(config.filter_duplicates),
167 ..Default::default()
168 };
169
170 proxy
171 .sync_to_periodic_advertising(fidl_le::CentralSyncToPeriodicAdvertisingRequest {
172 peer_id: Some(to_fidl_peer_id(&peer_id)),
173 advertising_sid: Some(adv_sid),
174 sync: Some(server_end),
175 config: Some(fidl_config),
176 ..Default::default()
177 })
178 .map_err(|e| bt_gatt::types::Error::Other(Box::new(e)))?;
179
180 let mut event_stream = sync_proxy.take_event_stream();
181
182 match event_stream.next().await {
183 Some(Ok(fidl_le::PeriodicAdvertisingSyncEvent::OnEstablished { .. })) => {
184 let stream = create_sync_stream(sync_proxy, event_stream);
185 Ok(stream)
186 }
187 Some(Ok(fidl_le::PeriodicAdvertisingSyncEvent::OnError { error })) => {
188 Err(to_gatt_sync_error(error))
189 }
190 Some(Ok(_)) => Err(bt_gatt::types::Error::Other(Box::new(std::io::Error::new(
191 std::io::ErrorKind::InvalidData,
192 "unknown event received during establishment",
193 )))),
194 Some(Err(e)) => Err(bt_gatt::types::Error::Other(Box::new(e))),
195 None => Err(bt_gatt::types::Error::Other(Box::new(std::io::Error::new(
196 std::io::ErrorKind::UnexpectedEof,
197 "Event stream closed before establishment",
198 )))),
199 }
200 })
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use bt_common::Uuid;
208 use bt_gatt::central::AdvertisingDatum;
209
210 #[test]
211 fn test_to_gatt_sync_error() {
212 let err = fidl_le::PeriodicAdvertisingSyncError::InitialSynchronizationFailed;
213 let gatt_err = to_gatt_sync_error(err);
214 assert!(format!("{gatt_err:?}").contains("SyncEstablishFailed"));
215
216 let err = fidl_le::PeriodicAdvertisingSyncError::SynchronizationLost;
217 let gatt_err = to_gatt_sync_error(err);
218 assert!(format!("{gatt_err:?}").contains("SyncLost"));
219
220 let err = fidl_le::PeriodicAdvertisingSyncError::NotSupportedLocal;
221 let gatt_err = to_gatt_sync_error(err);
222 assert!(format!("{gatt_err:?}").contains("Io"));
223 }
224
225 #[test]
226 fn test_to_gatt_phy() {
227 assert_eq!(to_gatt_phy(fidl_le::PhysicalLayer::Le1M), bt_common::core::Phy::Le1m);
228 assert_eq!(to_gatt_phy(fidl_le::PhysicalLayer::Le2M), bt_common::core::Phy::Le2m);
229 assert_eq!(to_gatt_phy(fidl_le::PhysicalLayer::LeCoded), bt_common::core::Phy::LeCoded);
230 }
231
232 #[test]
233 fn test_to_gatt_big_info() {
234 let fidl_info = fidl_le::BroadcastIsochronousGroupInfo {
235 streams_count: Some(5),
236 max_sdu_size: Some(200),
237 phy: Some(fidl_le::PhysicalLayer::LeCoded),
238 encryption: Some(true),
239 ..Default::default()
240 };
241 let gatt_info = to_gatt_big_info(&fidl_info);
242 assert_eq!(gatt_info.streams_count, 5);
243 assert_eq!(gatt_info.sdu_interval, 0); assert_eq!(gatt_info.max_sdu_size, 200);
245 assert_eq!(gatt_info.phy, bt_common::core::Phy::LeCoded);
246 assert_eq!(gatt_info.encryption, true);
247 }
248
249 #[test]
250 fn test_to_gatt_scan_data() {
251 let service_uuid = Uuid::from_u16(0x1852);
252 let scan_data = fidl_le::ScanData {
253 tx_power: Some(-15),
254 service_uuids: Some(vec![to_fidl_uuid(&service_uuid)]),
255 service_data: Some(vec![fidl_le::ServiceData {
256 uuid: to_fidl_uuid(&service_uuid),
257 data: vec![4, 5, 6],
258 }]),
259 manufacturer_data: Some(vec![fidl_le::ManufacturerData {
260 company_id: 0x00E0,
261 data: vec![7, 8],
262 }]),
263 uris: Some(vec!["https://example.com".to_string()]),
264 broadcast_name: Some("My Broadcast".to_string()),
265 resolvable_set_identifier: Some([1, 2, 3, 4, 5, 6]),
266 ..Default::default()
267 };
268
269 let advertised = to_gatt_scan_data(scan_data);
270 assert_eq!(advertised.len(), 7);
271
272 assert!(advertised.iter().any(|d| matches!(d, AdvertisingDatum::TxPowerLevel(-15))));
273
274 assert!(advertised.iter().any(|d| match d {
275 AdvertisingDatum::Services(uuids) => uuids == &[service_uuid],
276 _ => false,
277 }));
278
279 assert!(advertised.iter().any(|d| match d {
280 AdvertisingDatum::ServiceData(uuid, data) =>
281 *uuid == service_uuid && data == &[4, 5, 6],
282 _ => false,
283 }));
284
285 assert!(advertised.iter().any(|d| match d {
286 AdvertisingDatum::ManufacturerData(company_id, data) =>
287 *company_id == 0x00E0 && data == &[7, 8],
288 _ => false,
289 }));
290
291 assert!(advertised.iter().any(|d| match d {
292 AdvertisingDatum::Uri(uri) => uri == "https://example.com",
293 _ => false,
294 }));
295
296 assert!(advertised.iter().any(|d| match d {
297 AdvertisingDatum::BroadcastName(name) => name == "My Broadcast",
298 _ => false,
299 }));
300
301 assert!(advertised.iter().any(|d| match d {
302 AdvertisingDatum::ResolvableSetIdentifier(rsi) => rsi == &[1, 2, 3, 4, 5, 6],
303 _ => false,
304 }));
305 }
306}