rust/bt-gatt: Add periodic advertising sync API Adds a new API to the `Central` trait for synchronizing to periodic advertising trains. This is the Rust equivalent of the Periodic Advertisement Scanning FIDL API. - Adds `sync_to_periodic_advertising` to the `Central` trait - Adds a `periodic_advertising` module with necessary types - Adds a `PeriodicAdvertisingTypes` trait for a clean API - Updates tests and fixes other crates affected by the changes Bug: b/433283601 Test: cargo build && cargo test Change-Id: I2c70c352a5f703b50f5036a2d6aa446bc6786380 Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/2500 Reviewed-by: Marie Janssen <jamuraa@google.com>
diff --git a/rust/bt-broadcast-assistant/src/assistant/event.rs b/rust/bt-broadcast-assistant/src/assistant/event.rs index 3f781f6..e1e444c 100644 --- a/rust/bt-broadcast-assistant/src/assistant/event.rs +++ b/rust/bt-broadcast-assistant/src/assistant/event.rs
@@ -165,6 +165,7 @@ BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE, vec![0x01, 0x02, 0x03], )], + advertising_sid: 0, })); // Found broadcast source event shouldn't have been sent since braodcast source @@ -209,6 +210,7 @@ BASIC_AUDIO_ANNOUNCEMENT_SERVICE, base_data.clone(), )], + advertising_sid: 0, })); // Expect the stream to send out broadcast source found event since information @@ -231,6 +233,7 @@ BASIC_AUDIO_ANNOUNCEMENT_SERVICE, base_data.clone(), )], + advertising_sid: 0, })); // Shouldn't have gotten the event again since the information remained the
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs index ec141e9..5c3b5b5 100644 --- a/rust/bt-broadcast-assistant/src/assistant/peer.rs +++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -3,8 +3,8 @@ // found in the LICENSE file. use bt_gatt::pii::GetPeerAddr; -use futures::Stream; use futures::stream::FusedStream; +use futures::Stream; use std::sync::Arc; use thiserror::Error; @@ -94,8 +94,8 @@ /// /// * `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. + /// * `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 @@ -198,7 +198,7 @@ use assert_matches::assert_matches; use bt_gatt::pii::StaticPeerAddr; - use futures::{FutureExt, pin_mut}; + use futures::{pin_mut, FutureExt}; use std::collections::HashSet; use std::task::Poll;
diff --git a/rust/bt-broadcast-assistant/src/debug.rs b/rust/bt-broadcast-assistant/src/debug.rs index 02d3cb7..6a4f308 100644 --- a/rust/bt-broadcast-assistant/src/debug.rs +++ b/rust/bt-broadcast-assistant/src/debug.rs
@@ -263,7 +263,13 @@ if args.len() == 3 { parse_bis_sync(&args[2]) } else { HashSet::new() }; self.with_peer(|peer| async move { - peer.add_broadcast_source(broadcast_source_pid, &self.peer_addr_getter, pa_sync, bis_sync).await + peer.add_broadcast_source( + broadcast_source_pid, + &self.peer_addr_getter, + pa_sync, + bis_sync, + ) + .await }) .await; }
diff --git a/rust/bt-common/src/core.rs b/rust/bt-common/src/core.rs index 9ee3fa0..d2e2ce5 100644 --- a/rust/bt-common/src/core.rs +++ b/rust/bt-common/src/core.rs
@@ -177,6 +177,16 @@ } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Phy { + /// LE 1M PHY + Le1m, + /// LE 2M PHY + Le2m, + /// LE Coded PHY + LeCoded, +} + #[cfg(test)] mod tests { use crate::packet_encoding::Decodable;
diff --git a/rust/bt-gatt/src/central.rs b/rust/bt-gatt/src/central.rs index 5ca7712..c896717 100644 --- a/rust/bt-gatt/src/central.rs +++ b/rust/bt-gatt/src/central.rs
@@ -75,6 +75,7 @@ pub connectable: bool, pub name: PeerName, pub advertised: Vec<AdvertisingDatum>, + pub advertising_sid: u8, } pub trait Central<T: crate::GattTypes> { @@ -84,4 +85,7 @@ /// Connect to a specific peer. fn connect(&self, peer_id: PeerId) -> T::ConnectFuture; + + /// Get API for periodic advertising. + fn periodic_advertising(&self) -> crate::Result<T::PeriodicAdvertising>; }
diff --git a/rust/bt-gatt/src/lib.rs b/rust/bt-gatt/src/lib.rs index 74c7f3b..3ca4b1a 100644 --- a/rust/bt-gatt/src/lib.rs +++ b/rust/bt-gatt/src/lib.rs
@@ -14,6 +14,7 @@ pub mod central; pub use central::Central; +pub mod periodic_advertising; pub mod pii; #[cfg(any(test, feature = "test-utils"))] @@ -24,6 +25,8 @@ use futures::{Future, Stream}; +use crate::periodic_advertising::PeriodicAdvertising; + /// Implementors implement traits with respect to GattTypes. /// Implementation crates provide an object which relates a constellation of /// types to each other, used to provide concrete types for library crates to @@ -56,6 +59,8 @@ /// Future resolving when a characteristic or descriptor has been written. /// Returns an error if the value could not be written. type WriteFut<'a>: Future<Output = Result<()>> + 'a; + /// The implementation of periodic advertising for this GATT implementation. + type PeriodicAdvertising: PeriodicAdvertising; } /// Servers and services are defined with respect to ServerTypes.
diff --git a/rust/bt-gatt/src/periodic_advertising.rs b/rust/bt-gatt/src/periodic_advertising.rs new file mode 100644 index 0000000..c0c227f --- /dev/null +++ b/rust/bt-gatt/src/periodic_advertising.rs
@@ -0,0 +1,92 @@ +// 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. + +//! Contains traits that are used to synchronize to Periodic Advertisements, +//! i.e. the GAP Central role role defined in the Bluetooth Core +//! Specification (5.4, Volume 3 Part C Section 2.2.2) +//! +//! These traits should be implemented outside this crate, conforming to the +//! types and structs here when necessary. + +use bt_common::core::Phy; +use futures::{Future, Stream}; +use thiserror::Error; + +use bt_common::PeerId; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("Periodic Advertising Sync failed to establish")] + SyncEstablishFailed, + #[error("Periodic Advertising Sync lost")] + SyncLost, + #[error("I/O error")] + Io, +} + +/// A trait for managing a periodic advertising. +pub trait PeriodicAdvertising { + type SyncFut: Future<Output = crate::Result<Self::SyncStream>>; + type SyncStream: Stream<Item = crate::Result<SyncReport>>; + + /// Request to sync to periodic advertising resports. + /// On success, returns the SyncStream which can be used to receive + /// SyncReports. + fn sync_to_advertising_reports( + peer_id: PeerId, + advertising_sid: u8, + config: SyncConfiguration, + ) -> Self::SyncFut; + + // TODO(b/340885203): Add a method to sync to subevents. +} + +#[derive(Debug, Clone)] +pub struct SyncConfiguration { + /// Filter out duplicate advertising reports. + /// Optional. + /// Default: true + pub filter_duplicates: bool, +} + +#[derive(Debug, Clone)] +pub struct PeriodicAdvertisingReport { + pub rssi: i8, + pub data: Vec<u8>, + /// The event counter of the event that the advertising packet was received + /// in. + pub event_counter: Option<u16>, + /// The subevent number of the report. Only present if the packet was + /// received in a subevent. + pub subevent: Option<u8>, + pub timestamp: i64, +} + +#[derive(Debug, Clone)] +pub struct BroadcastIsochronousGroupInfo { + /// The number of Broadcast Isochronous Streams in this group. + /// The specification calls this "num_bis". + pub streams_count: u8, + /// The time interval of the periodic SDUs. + pub sdu_interval: i64, + /// The maximum size of an SDU. + pub max_sdu_size: u16, + /// The PHY used for transmission of data. + pub phy: Phy, + /// Indicates whether the BIG is encrypted. + pub encryption: bool, +} + +#[derive(Debug, Clone)] +pub struct BroadcastIsochronousGroupInfoReport { + pub info: BroadcastIsochronousGroupInfo, + pub timestamp: i64, +} + +#[derive(Debug, Clone)] +pub enum SyncReport { + PeriodicAdvertisingReport(PeriodicAdvertisingReport), + BroadcastIsochronousGroupInfoReport(BroadcastIsochronousGroupInfoReport), +}
diff --git a/rust/bt-gatt/src/pii.rs b/rust/bt-gatt/src/pii.rs index f165db6..df17047 100644 --- a/rust/bt-gatt/src/pii.rs +++ b/rust/bt-gatt/src/pii.rs
@@ -5,8 +5,8 @@ use std::future::Future; use bt_common::{ - PeerId, core::{Address, AddressType}, + PeerId, }; use crate::types::*;
diff --git a/rust/bt-gatt/src/test_utils.rs b/rust/bt-gatt/src/test_utils.rs index 15ad2fa..c72b395 100644 --- a/rust/bt-gatt/src/test_utils.rs +++ b/rust/bt-gatt/src/test_utils.rs
@@ -15,9 +15,10 @@ use crate::central::ScanResult; use crate::client::CharacteristicNotification; +use crate::periodic_advertising::{PeriodicAdvertising, SyncReport}; use crate::pii::GetPeerAddr; use crate::server::{self, LocalService, ReadResponder, ServiceDefinition, WriteResponder}; -use crate::{GattTypes, ServerTypes, types::*}; +use crate::{types::*, GattTypes, ServerTypes}; #[derive(Default)] struct FakePeerServiceInner { @@ -293,6 +294,7 @@ type NotificationStream = UnboundedReceiver<Result<CharacteristicNotification>>; type ReadFut<'a> = Ready<Result<(usize, bool)>>; type WriteFut<'a> = Ready<Result<()>>; + type PeriodicAdvertising = FakePeriodicAdvertising; } impl ServerTypes for FakeTypes { @@ -306,6 +308,21 @@ type IndicateConfirmationStream = UnboundedReceiver<Result<server::ConfirmationEvent>>; } +pub struct FakePeriodicAdvertising; + +impl PeriodicAdvertising for FakePeriodicAdvertising { + type SyncFut = Ready<Result<Self::SyncStream>>; + type SyncStream = futures::stream::Empty<Result<SyncReport>>; + + fn sync_to_advertising_reports( + _peer_id: PeerId, + _advertising_sid: u8, + _config: crate::periodic_advertising::SyncConfiguration, + ) -> Self::SyncFut { + unimplemented!() + } +} + #[derive(Default)] pub struct FakeCentralInner { clients: HashMap<PeerId, FakeClient>, @@ -339,6 +356,10 @@ }; futures::future::ready(res) } + + fn periodic_advertising(&self) -> Result<<FakeTypes as GattTypes>::PeriodicAdvertising> { + unimplemented!() + } } pub enum FakeServerEvent {
diff --git a/rust/bt-gatt/src/tests.rs b/rust/bt-gatt/src/tests.rs index 896f2cc..cc51dd5 100644 --- a/rust/bt-gatt/src/tests.rs +++ b/rust/bt-gatt/src/tests.rs
@@ -243,6 +243,7 @@ connectable: true, name: PeerName::CompleteName("Marie's Pixel 7 Pro".to_owned()), advertised: vec![AdvertisingDatum::Services(vec![Uuid::from_u16(0x1844)])], + advertising_sid: 0, }; let _ = scan_results.set_scanned_result(Ok(scanned_result));
diff --git a/rust/bt-gatt/src/types.rs b/rust/bt-gatt/src/types.rs index 2171070..ad182b4 100644 --- a/rust/bt-gatt/src/types.rs +++ b/rust/bt-gatt/src/types.rs
@@ -153,6 +153,8 @@ AlreadyPublished(crate::server::ServiceId), #[error("Encoding/decoding error: {0}")] Encoding(#[from] bt_common::packet_encoding::Error), + #[error("Periodic Advertising error: {0}")] + PeriodicAdvertising(crate::periodic_advertising::Error), } impl Error {