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