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 {