Skip to main content

bt_bass/
server.rs

1// Copyright 2026 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
5//! Implements the Broadcast Audio Scan Service (BASS) server role.
6
7use bt_gatt::server::{LocalService, Server as _, ServiceDefinition, ServiceId};
8use bt_gatt::types::{
9    AttributePermissions, CharacteristicProperty, Handle, SecurityLevels, ServiceKind,
10};
11use bt_gatt::Characteristic;
12use futures::Future;
13use pin_project::pin_project;
14use std::collections::HashMap;
15
16use crate::types::{
17    BroadcastReceiveState, SourceId, BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
18    BROADCAST_AUDIO_SCAN_SERVICE_UUID, BROADCAST_RECEIVE_STATE_UUID,
19};
20
21pub mod error;
22pub use error::Error;
23
24/// Service identifier assigned to the published BASS GATT service instance.
25const BASS_SERVICE_ID: ServiceId = ServiceId::new(1);
26
27/// Handle assigned to the Broadcast Audio Scan Control Point characteristic.
28const CONTROL_POINT_HANDLE: Handle = Handle(1);
29
30/// Maximum number of Broadcast Receive State characteristics that can be hosted
31/// by the server. See BASS v1.0 Section 3.2.1.
32const MAX_RECEIVE_STATES: usize = 255;
33
34/// Internal representation of a Broadcast Receive State characteristic slot.
35/// See BASS v1.0 Section 3.2.
36#[derive(Debug)]
37pub(crate) struct PublishedReceiveStateCharacteristic {
38    /// 1-indexed source identifier.
39    /// See BASS v1.0 Section 3.2.1.
40    source_id: SourceId,
41    /// Handle assigned to this GATT characteristic.
42    handle: Handle,
43    /// Current Broadcast Receive State value.
44    state: BroadcastReceiveState,
45}
46
47/// Internal publication state lifecycle of the BASS GATT service.
48#[pin_project(project = LocalServiceProj)]
49enum LocalServiceState<T: bt_gatt::ServerTypes> {
50    /// Service definition has not been registered in the GATT database.
51    NotPublished,
52    /// Service registration is in progress.
53    Preparing {
54        #[pin]
55        fut: T::LocalServiceFut,
56    },
57    /// Service registration is complete and active in the GATT database.
58    Published { service: T::LocalService },
59}
60
61impl<T: bt_gatt::ServerTypes> Default for LocalServiceState<T> {
62    fn default() -> Self {
63        Self::NotPublished
64    }
65}
66
67impl<T: bt_gatt::ServerTypes> LocalServiceState<T> {
68    fn is_published(&self) -> bool {
69        matches!(self, LocalServiceState::Published { .. })
70    }
71
72    fn poll_publish(
73        mut self: std::pin::Pin<&mut Self>,
74        cx: &mut std::task::Context<'_>,
75    ) -> std::task::Poll<Result<(), Error>> {
76        match self.as_mut().project() {
77            LocalServiceProj::NotPublished => std::task::Poll::Pending,
78            LocalServiceProj::Preparing { fut } => {
79                let service_result = futures::ready!(fut.poll(cx));
80                match service_result {
81                    Ok(service) => {
82                        let _ = service.publish();
83                        self.set(LocalServiceState::Published { service });
84                        std::task::Poll::Ready(Ok(()))
85                    }
86                    Err(e) => {
87                        self.set(LocalServiceState::NotPublished);
88                        std::task::Poll::Ready(Err(Error::Gatt(e)))
89                    }
90                }
91            }
92            LocalServiceProj::Published { .. } => std::task::Poll::Ready(Ok(())),
93        }
94    }
95}
96
97/// Builder for constructing a BASS GATT [`Server`].
98#[derive(Default)]
99pub struct ServerBuilder {
100    /// Staged Broadcast Receive State characteristics.
101    /// There must be at least one such staged characteristic.
102    receive_states: Vec<BroadcastReceiveState>,
103}
104
105impl ServerBuilder {
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    /// Adds a Broadcast Receive State characteristic slot to the builder.
111    pub fn add_receive_state_characteristic(mut self, state: BroadcastReceiveState) -> Self {
112        self.receive_states.push(state);
113        self
114    }
115
116    /// Constructs the Broadcast Audio Scan Control Point characteristic.
117    /// Defined in BASS v1.0 Section 3.1.
118    fn build_control_point() -> Characteristic {
119        let cp_properties =
120            CharacteristicProperty::Write | CharacteristicProperty::WriteWithoutResponse;
121        Characteristic {
122            handle: CONTROL_POINT_HANDLE,
123            uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID,
124            properties: cp_properties.clone(),
125            permissions: AttributePermissions::with_levels(
126                &cp_properties,
127                &SecurityLevels::encryption_required(),
128            ),
129            descriptors: Vec::new(),
130        }
131    }
132
133    /// Constructs the Broadcast Receive State characteristic.
134    /// Defined in BASS v1.0 Section 3.2.
135    fn build_receive_state(handle: Handle) -> Characteristic {
136        let properties = CharacteristicProperty::Read | CharacteristicProperty::Notify;
137        Characteristic {
138            handle,
139            uuid: BROADCAST_RECEIVE_STATE_UUID,
140            properties: properties.clone(),
141            permissions: AttributePermissions::with_levels(
142                &properties,
143                &SecurityLevels::encryption_required(),
144            ),
145            descriptors: Vec::new(),
146        }
147    }
148
149    /// Builds a [`Server`] instance after verifying required service
150    /// characteristics.
151    pub fn build<T: bt_gatt::ServerTypes>(self) -> Result<Server<T>, Error> {
152        // Per BASS v1.0 Section 3.2, there must be [1,255] Broadcast Receive State
153        // characteristics.
154        if self.receive_states.is_empty() {
155            return Err(Error::MissingReceiveState);
156        }
157        if self.receive_states.len() > MAX_RECEIVE_STATES {
158            return Err(Error::ExceedsMaxReceiveStates);
159        }
160
161        let mut service_def = ServiceDefinition::new(
162            BASS_SERVICE_ID,
163            BROADCAST_AUDIO_SCAN_SERVICE_UUID,
164            ServiceKind::Primary,
165        );
166
167        let _ = service_def.add_characteristic(Self::build_control_point());
168
169        // Broadcast Receive State characteristics (Read, Notify; Encryption Required)
170        const FIRST_RECEIVE_STATE_HANDLE: Handle = Handle(2);
171        let num_receive_states = self.receive_states.len();
172        let mut receive_state_characteristics = HashMap::with_capacity(num_receive_states);
173        let mut source_id_to_handle = HashMap::with_capacity(num_receive_states);
174        for (i, mut state) in self.receive_states.into_iter().enumerate() {
175            let source_id = (i + 1) as u8;
176            let handle = Handle(FIRST_RECEIVE_STATE_HANDLE.0 + i as u64);
177            let _ = service_def.add_characteristic(Self::build_receive_state(handle));
178
179            // Override Source ID as this is assigned by the server. See BASS v1.0 Section
180            // 3.2.1.
181            if let BroadcastReceiveState::NonEmpty(ref mut receive_state) = state {
182                receive_state.source_id = source_id;
183            }
184
185            receive_state_characteristics
186                .insert(handle, PublishedReceiveStateCharacteristic { source_id, handle, state });
187            source_id_to_handle.insert(source_id, handle);
188        }
189
190        let next_source_id = (num_receive_states + 1) as SourceId;
191
192        Ok(Server {
193            service_def,
194            local_service: Default::default(),
195            control_point_handle: CONTROL_POINT_HANDLE,
196            receive_state_characteristics,
197            source_id_to_handle,
198            next_source_id,
199        })
200    }
201}
202
203/// The BASS GATT Server implementation.
204///
205/// Manages the Control Point characteristic and one or more Broadcast Receive
206/// State characteristics.
207#[pin_project]
208pub struct Server<T: bt_gatt::ServerTypes> {
209    service_def: ServiceDefinition,
210    #[pin]
211    local_service: LocalServiceState<T>,
212    control_point_handle: Handle,
213    /// Broadcast Receive State characteristics mapped by GATT handle.
214    receive_state_characteristics: HashMap<Handle, PublishedReceiveStateCharacteristic>,
215    /// Map from Source ID to characteristic handle.
216    source_id_to_handle: HashMap<SourceId, Handle>,
217    /// Next available Source ID to be assigned to an empty Receive State
218    /// characteristic.
219    next_source_id: SourceId,
220}
221
222impl<T: bt_gatt::ServerTypes> Server<T> {
223    /// Returns true if the server has successfully published the GATT service.
224    pub fn is_published(&self) -> bool {
225        self.local_service.is_published()
226    }
227
228    /// Helper for polling publication to transition from Preparing to Published
229    /// state.
230    // TODO(b/534436439): Remove once the Stream implementation is defined.
231    #[cfg(test)]
232    pub(crate) fn poll_publish(
233        self: std::pin::Pin<&mut Self>,
234        cx: &mut std::task::Context<'_>,
235    ) -> std::task::Poll<Result<(), Error>> {
236        self.project().local_service.poll_publish(cx)
237    }
238
239    /// Publishes the service to the GATT database.
240    pub fn publish(&mut self, server: T::Server) -> Result<(), Error> {
241        if matches!(
242            self.local_service,
243            LocalServiceState::Preparing { .. } | LocalServiceState::Published { .. }
244        ) {
245            return Err(Error::AlreadyPublished);
246        }
247
248        let LocalServiceState::NotPublished = std::mem::replace(
249            &mut self.local_service,
250            LocalServiceState::Preparing { fut: server.prepare(self.service_def.clone()) },
251        ) else {
252            unreachable!();
253        };
254        Ok(())
255    }
256
257    /// Handles a read request for a characteristic on the BASS server.
258    // TODO(b/534436439): Remove once the Stream implementation is defined.
259    #[cfg(test)]
260    fn handle_read(
261        &self,
262        handle: Handle,
263        offset: usize,
264        responder: impl bt_gatt::server::ReadResponder,
265    ) {
266        use bt_common::packet_encoding::Encodable;
267        use bt_gatt::types::GattError;
268
269        if handle == self.control_point_handle {
270            responder.error(GattError::ReadNotPermitted);
271            return;
272        }
273
274        let Some(slot) = self.receive_state_characteristics.get(&handle) else {
275            responder.error(GattError::InvalidHandle);
276            return;
277        };
278
279        let mut buf = vec![0u8; slot.state.encoded_len()];
280        if slot.state.encode(&mut buf).is_ok() {
281            if offset > buf.len() {
282                responder.error(GattError::InvalidOffset);
283            } else {
284                responder.respond(&buf[offset..]);
285            }
286        } else {
287            responder.error(GattError::UnlikelyError);
288        }
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use bt_bap::types::BroadcastId;
296    use bt_common::core::{AddressType, AdvertisingSetId};
297    use bt_gatt::server::ReadResponder;
298    use bt_gatt::test_utils::{FakeServer, FakeServerEvent, FakeTypes};
299    use bt_gatt::types::GattError;
300    use futures::FutureExt;
301    use parking_lot::Mutex;
302    use std::sync::Arc;
303
304    use crate::types::{EncryptionStatus, PaSyncState, ReceiveState};
305
306    struct TestReadResponder {
307        result: Arc<Mutex<Option<Result<Vec<u8>, GattError>>>>,
308    }
309
310    impl ReadResponder for TestReadResponder {
311        fn respond(self, value: &[u8]) {
312            *self.result.lock() = Some(Ok(value.to_vec()));
313        }
314
315        fn error(self, error: GattError) {
316            *self.result.lock() = Some(Err(error));
317        }
318    }
319
320    fn make_test_receive_state(source_id: SourceId) -> BroadcastReceiveState {
321        BroadcastReceiveState::NonEmpty(ReceiveState::new(
322            source_id,
323            AddressType::Public,
324            [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
325            AdvertisingSetId::try_from(1).unwrap(),
326            BroadcastId::try_from(0x123456).unwrap(),
327            PaSyncState::NotSynced,
328            EncryptionStatus::NotEncrypted,
329            vec![],
330        ))
331    }
332
333    #[test]
334    fn empty_builder_fails() {
335        let builder = ServerBuilder::new();
336        assert!(matches!(builder.build::<FakeTypes>(), Err(Error::MissingReceiveState)));
337    }
338
339    #[test]
340    fn too_many_receive_states_fails() {
341        let mut builder = ServerBuilder::new();
342        for _ in 0..=MAX_RECEIVE_STATES {
343            builder = builder.add_receive_state_characteristic(BroadcastReceiveState::Empty);
344        }
345        assert!(matches!(builder.build::<FakeTypes>(), Err(Error::ExceedsMaxReceiveStates)));
346    }
347
348    #[test]
349    fn publish_server() {
350        use futures::task::Context;
351        use futures::StreamExt;
352        use std::task::Poll;
353
354        let mut noop_cx = Context::from_waker(futures::task::noop_waker_ref());
355
356        let mut server = ServerBuilder::new()
357            .add_receive_state_characteristic(BroadcastReceiveState::Empty)
358            .add_receive_state_characteristic(make_test_receive_state(2))
359            .build::<FakeTypes>()
360            .expect("building server works");
361
362        assert!(!server.is_published());
363
364        let (fake_gatt_server, mut event_receiver) = FakeServer::new();
365        let mut event_stream = event_receiver.next();
366
367        assert!(server.publish(fake_gatt_server.clone()).is_ok());
368        assert!(server.publish(fake_gatt_server).is_err());
369
370        // Poll publish to complete preparation and transition to Published state
371        assert!(std::pin::Pin::new(&mut server).poll_publish(&mut noop_cx).is_ready());
372        assert!(server.is_published());
373
374        let Poll::Ready(Some(FakeServerEvent::Published { id, definition })) =
375            event_stream.poll_unpin(&mut noop_cx)
376        else {
377            panic!("Expected published event");
378        };
379
380        assert_eq!(id, BASS_SERVICE_ID);
381        assert_eq!(definition.uuid(), BROADCAST_AUDIO_SCAN_SERVICE_UUID);
382        assert_eq!(definition.characteristics().count(), 3);
383    }
384
385    #[test]
386    fn read_receive_state() {
387        let server = ServerBuilder::new()
388            .add_receive_state_characteristic(BroadcastReceiveState::Empty)
389            .add_receive_state_characteristic(make_test_receive_state(2))
390            .build::<FakeTypes>()
391            .expect("building server works");
392
393        // 1. Read empty Receive State slot (Handle 2)
394        let res = Arc::new(Mutex::new(None));
395        server.handle_read(Handle(2), 0, TestReadResponder { result: res.clone() });
396        assert_eq!(res.lock().take().unwrap().expect("ok"), vec![]);
397
398        // 2. Read populated Receive State slot (Handle 3)
399        let res = Arc::new(Mutex::new(None));
400        server.handle_read(Handle(3), 0, TestReadResponder { result: res.clone() });
401        assert!(!res.lock().take().unwrap().expect("ok").is_empty());
402
403        // 3. Read unknown handle (Handle 99)
404        let res = Arc::new(Mutex::new(None));
405        server.handle_read(Handle(99), 0, TestReadResponder { result: res.clone() });
406        assert_eq!(res.lock().take().unwrap().expect_err("err"), GattError::InvalidHandle);
407    }
408
409    #[test]
410    fn read_control_point_fails() {
411        let server = ServerBuilder::new()
412            .add_receive_state_characteristic(BroadcastReceiveState::Empty)
413            .build::<FakeTypes>()
414            .expect("building server works");
415
416        let res = Arc::new(Mutex::new(None));
417        server.handle_read(CONTROL_POINT_HANDLE, 0, TestReadResponder { result: res.clone() });
418        assert_eq!(res.lock().take().unwrap().expect_err("err"), GattError::ReadNotPermitted);
419    }
420
421    #[test]
422    fn read_receive_state_invalid_offset() {
423        let server = ServerBuilder::new()
424            .add_receive_state_characteristic(BroadcastReceiveState::Empty)
425            .build::<FakeTypes>()
426            .expect("building server works");
427
428        let res = Arc::new(Mutex::new(None));
429        server.handle_read(Handle(2), 10, TestReadResponder { result: res.clone() });
430        assert_eq!(res.lock().take().unwrap().expect_err("err"), GattError::InvalidOffset);
431    }
432
433    #[test]
434    fn poll_publish_failure() {
435        use futures::task::Context;
436        use std::task::Poll;
437
438        let mut noop_cx = Context::from_waker(futures::task::noop_waker_ref());
439
440        let mut server = ServerBuilder::new()
441            .add_receive_state_characteristic(BroadcastReceiveState::Empty)
442            .build::<FakeTypes>()
443            .expect("building server works");
444
445        let (fake_gatt_server, _event_receiver) = FakeServer::new();
446        fake_gatt_server
447            .set_next_prepare_result(Err(bt_gatt::types::Error::Gatt(GattError::UnlikelyError)));
448
449        assert!(server.publish(fake_gatt_server.clone()).is_ok());
450
451        assert!(matches!(
452            std::pin::Pin::new(&mut server).poll_publish(&mut noop_cx),
453            Poll::Ready(Err(Error::Gatt(bt_gatt::types::Error::Gatt(GattError::UnlikelyError))))
454        ));
455
456        // Verifying server reset state to NotPublished so it is not published
457        assert!(!server.is_published());
458    }
459
460    #[test]
461    fn builder_normalizes_source_id() {
462        // Create state with an arbitrary source_id = 99
463        let state = make_test_receive_state(99);
464        let server = ServerBuilder::new()
465            .add_receive_state_characteristic(state)
466            .build::<FakeTypes>()
467            .expect("building server works");
468
469        // Verify slot's internal state and encoded read value have source_id = 1
470        let res = Arc::new(Mutex::new(None));
471        server.handle_read(Handle(2), 0, TestReadResponder { result: res.clone() });
472        let buf = res.lock().take().unwrap().expect("ok");
473        assert_eq!(buf[0], 1);
474    }
475}