bt_vcs/
lib.rs

1// Copyright 2024 Google LLC
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use bt_common::packet_encoding::Encodable;
6use bt_common::Uuid;
7use bt_gatt::{
8    client::{FromCharacteristic, PeerService, PeerServiceHandle},
9    types::WriteMode,
10    Characteristic, Client,
11};
12
13use futures::TryFutureExt;
14use parking_lot::Mutex;
15use std::sync::Arc;
16use thiserror::Error;
17
18pub const VCS_UUID: Uuid = Uuid::from_u16(0x1844);
19
20pub mod debug;
21
22/// Volume State Characteristic
23/// See VCS v1.0 Section 2.3.1
24#[derive(Debug, Clone)]
25pub struct VolumeState {
26    handle: bt_gatt::types::Handle,
27    /// Unitless value, step sizes are implementation-specific
28    setting: u8,
29    /// True if the audio is muted.  Does not affect
30    /// [`setting`](VolumeState::setting).
31    mute: bool,
32    /// Server-incremented counter, used to invalidate commands against stale
33    /// state. Wraps around from 255 to 0.
34    change_counter: u8,
35}
36
37impl VolumeState {
38    fn from_value(
39        handle: bt_gatt::types::Handle,
40        value: &[u8],
41    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
42        let mut val = Self { handle, setting: 0, mute: false, change_counter: 0 };
43        val.update_from_value(value)?;
44        Ok(val)
45    }
46
47    fn update_from_value(
48        &mut self,
49        value: &[u8],
50    ) -> core::result::Result<(), bt_common::packet_encoding::Error> {
51        if value.len() < 3 {
52            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
53        }
54        self.setting = value[0];
55        self.mute = match value[1] {
56            0 => false,
57            1 => true,
58            _ => return Err(bt_common::packet_encoding::Error::OutOfRange),
59        };
60        self.change_counter = value[2];
61        Ok(())
62    }
63}
64
65impl FromCharacteristic for VolumeState {
66    const UUID: Uuid = Uuid::from_u16(0x2B7D);
67
68    fn from_chr(
69        characteristic: Characteristic,
70        value: &[u8],
71    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
72        Self::from_value(characteristic.handle, value)
73    }
74
75    fn update(
76        &mut self,
77        new_value: &[u8],
78    ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
79        self.update_from_value(new_value)?;
80        Ok(self)
81    }
82}
83
84/// Volume Control Point
85/// See VCS v1.0 Section 3.2
86#[derive(Debug, Clone)]
87pub struct VolumeControlPoint {
88    handle: bt_gatt::types::Handle,
89}
90
91impl VolumeControlPoint {
92    const UUID: Uuid = Uuid::from_u16(0x2B7E);
93}
94
95#[derive(Debug, Clone, PartialEq)]
96pub enum VcpProcedure {
97    RelativeVolumeDown,
98    RelativeVolumeUp,
99    UnmuteRelativeVolumeDown,
100    UnmuteRelativeVolumeUp,
101    SetAbsoluteVolume { setting: u8 },
102    Unmute,
103    Mute,
104}
105
106impl VcpProcedure {
107    fn opcode(&self) -> u8 {
108        match self {
109            VcpProcedure::RelativeVolumeDown => 0x00,
110            VcpProcedure::RelativeVolumeUp => 0x01,
111            VcpProcedure::UnmuteRelativeVolumeDown => 0x02,
112            VcpProcedure::UnmuteRelativeVolumeUp => 0x03,
113            VcpProcedure::SetAbsoluteVolume { .. } => 0x04,
114            VcpProcedure::Unmute => 0x05,
115            VcpProcedure::Mute => 0x06,
116        }
117    }
118}
119
120pub struct VolumeControlPointOperation {
121    procedure: VcpProcedure,
122    change_counter: u8,
123}
124
125impl Encodable for VolumeControlPointOperation {
126    type Error = bt_common::packet_encoding::Error;
127
128    fn encoded_len(&self) -> core::primitive::usize {
129        if let VcpProcedure::SetAbsoluteVolume { .. } = self.procedure {
130            return 3;
131        }
132        // All the other operations only have a change counter parameter
133        2
134    }
135
136    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
137        if buf.len() < self.encoded_len() {
138            return Err(bt_common::packet_encoding::Error::BufferTooSmall);
139        }
140
141        buf[0] = self.procedure.opcode();
142        buf[1] = self.change_counter;
143        if let VcpProcedure::SetAbsoluteVolume { setting } = self.procedure {
144            buf[2] = setting;
145        }
146        Ok(())
147    }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq)]
151pub enum VolumeSettingPersisted {
152    Reset,
153    UserSet,
154}
155
156impl TryFrom<&[u8]> for VolumeSettingPersisted {
157    type Error = bt_common::packet_encoding::Error;
158
159    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
160        if value.len() < 1 {
161            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
162        }
163        if (value[0] & 0x01) != 0 {
164            Ok(VolumeSettingPersisted::UserSet)
165        } else {
166            Ok(VolumeSettingPersisted::Reset)
167        }
168    }
169}
170
171/// Volume Flags characteristic
172/// See VCS v1.0 Section 3.3
173#[derive(Debug, Clone)]
174pub struct VolumeFlags {
175    handle: bt_gatt::types::Handle,
176    persisted: VolumeSettingPersisted,
177}
178
179impl FromCharacteristic for VolumeFlags {
180    const UUID: Uuid = Uuid::from_u16(0x2B7F);
181
182    fn from_chr(
183        characteristic: Characteristic,
184        value: &[u8],
185    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
186        let persisted = VolumeSettingPersisted::try_from(value)?;
187        Ok(Self { handle: characteristic.handle, persisted })
188    }
189
190    fn update(
191        &mut self,
192        new_value: &[u8],
193    ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
194        self.persisted = VolumeSettingPersisted::try_from(new_value)?;
195        Ok(self)
196    }
197}
198
199pub struct VolumeControlClient<T: bt_gatt::GattTypes> {
200    service: T::PeerService,
201    state: Arc<Mutex<VolumeState>>,
202    control_point: VolumeControlPoint,
203    flags: Arc<Mutex<VolumeFlags>>,
204}
205
206impl<T: bt_gatt::GattTypes> std::fmt::Display for VolumeControlClient<T> {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        let VolumeState { change_counter, setting, mute, .. } = self.state.lock().clone();
209        let VolumeFlags { persisted, .. } = self.flags.lock().clone();
210        let mute_str = mute.then_some("MUTED ").unwrap_or("");
211        write!(
212            f,
213            "Volume Control @ {change_counter}: Current Volume: {setting} {mute_str}from {persisted:?}"
214        )
215    }
216}
217
218#[derive(Error, Debug)]
219pub enum Error {
220    #[error("Issue encoding / decoding: {0}")]
221    Decoding(#[from] bt_common::packet_encoding::Error),
222    #[error("GATT error: {0}")]
223    Gatt(bt_gatt::types::Error),
224    #[error("Required Characteristic was not found: {0}")]
225    RequiredCharNotFound(&'static str),
226    #[error("Change Counter Mismatch")]
227    ChangeCounterMismatch,
228    #[error("Opcode Not Supported")]
229    OpcodeNotSupported,
230}
231
232impl From<bt_gatt::types::Error> for Error {
233    fn from(value: bt_gatt::types::Error) -> Self {
234        use bt_gatt::types::GattError::*;
235        match value {
236            bt_gatt::types::Error::Gatt(ApplicationError80) => Self::ChangeCounterMismatch,
237            bt_gatt::types::Error::Gatt(ApplicationError81) => Self::OpcodeNotSupported,
238            other => Self::Gatt(other),
239        }
240    }
241}
242
243// TODO(b/441327871): Add notification method for volume state changes.
244impl<T: bt_gatt::GattTypes> VolumeControlClient<T> {
245    pub async fn from_service(service: T::PeerService) -> Result<Self, Error> {
246        let mut state = None;
247        let mut control_point = None;
248        let mut flags = None;
249        for chr in service.discover_characteristics(None).await? {
250            if chr.uuid == VolumeState::UUID {
251                let _ = state.insert(VolumeState::try_read::<T>(chr, &service).await?);
252            } else if chr.uuid == VolumeControlPoint::UUID {
253                let _ = control_point.insert(VolumeControlPoint { handle: chr.handle });
254            } else if chr.uuid == VolumeFlags::UUID {
255                let _ = flags.insert(VolumeFlags::try_read::<T>(chr, &service).await?);
256            }
257        }
258
259        if state.is_none() {
260            return Err(Error::RequiredCharNotFound("Volume State"));
261        }
262        if control_point.is_none() {
263            return Err(Error::RequiredCharNotFound("Volume Conrol Point"));
264        }
265        if flags.is_none() {
266            return Err(Error::RequiredCharNotFound("Volume Flags"));
267        }
268
269        Ok(Self {
270            service,
271            state: Arc::new(Mutex::new(state.unwrap())),
272            control_point: control_point.unwrap(),
273            flags: Arc::new(Mutex::new(flags.unwrap())),
274        })
275    }
276
277    pub async fn connect(client: &T::Client) -> Result<Option<Self>, Error> {
278        let handles = client.find_service(VCS_UUID).await?;
279        let Some(handle) = handles.into_iter().next() else {
280            return Ok(None);
281        };
282        Ok(Some(handle.connect().map_err(Into::into).and_then(Self::from_service).await?))
283    }
284
285    fn change_counter(&self) -> u8 {
286        self.state.lock().change_counter
287    }
288
289    // Update the state of the volume by reading the characteristics.
290    // Returns the new volume level if successful, and an Error otherwise.
291    pub async fn update(&self) -> Result<u8, Error> {
292        let state_handle = self.state.lock().handle;
293
294        let mut state_buf = [0; 3];
295        let _ = self.service.read_characteristic(&state_handle, 0, &mut state_buf).await?;
296        let setting = self.state.lock().update(&state_buf)?.setting;
297
298        let flags_handle = self.flags.lock().handle;
299        let mut flags_buf = [0; 1];
300        let _ = self.service.read_characteristic(&flags_handle, 0, &mut flags_buf).await?;
301        let _ = self.flags.lock().update(&flags_buf)?;
302
303        Ok(setting)
304    }
305
306    async fn send_control_pt(&self, procedure: VcpProcedure) -> Result<(), Error> {
307        let change_counter = self.change_counter();
308        let op = VolumeControlPointOperation { procedure, change_counter };
309        let mut buf = [0; 2];
310        op.encode(&mut buf)?;
311        self.service
312            .write_characteristic(&self.control_point.handle, WriteMode::None, 0, &buf)
313            .await
314            .map_err(Into::into)
315    }
316
317    /// Relative volume up.  Should increase the volume by a static step size
318    /// unless the volume is at max.
319    /// If `unmute` is true, also unmute, otherwise it does not affect the mute
320    /// value.
321    pub async fn volume_up(&self, unmute: bool) -> Result<(), Error> {
322        let procedure = if unmute {
323            VcpProcedure::UnmuteRelativeVolumeUp
324        } else {
325            VcpProcedure::RelativeVolumeUp
326        };
327        self.send_control_pt(procedure).await
328    }
329
330    /// Relative volume down.  Should decrease the volume by a static step size
331    /// unless the volume is at zero.
332    /// If `unmute` is true, also unmute, otherwise it does not affect the mute
333    /// value.
334    pub async fn volume_down(&self, unmute: bool) -> Result<(), Error> {
335        let procedure = if unmute {
336            VcpProcedure::UnmuteRelativeVolumeDown
337        } else {
338            VcpProcedure::RelativeVolumeDown
339        };
340        self.send_control_pt(procedure).await
341    }
342
343    pub async fn mute(&self) -> Result<(), Error> {
344        self.send_control_pt(VcpProcedure::Mute).await
345    }
346
347    pub async fn unmute(&self) -> Result<(), Error> {
348        self.send_control_pt(VcpProcedure::Unmute).await
349    }
350
351    pub async fn set_absolute_volume(&self, setting: u8) -> Result<(), Error> {
352        let change_counter = self.change_counter();
353        let op = VolumeControlPointOperation {
354            procedure: VcpProcedure::SetAbsoluteVolume { setting },
355            change_counter,
356        };
357        let mut buf = [0; 3];
358        op.encode(&mut buf)?;
359        self.service
360            .write_characteristic(&self.control_point.handle, WriteMode::None, 0, &buf)
361            .await
362            .map_err(Into::into)
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    use bt_gatt::test_utils::*;
371    use futures::Future;
372
373    #[test]
374    fn volume_state_decode() {
375        let handle = bt_gatt::types::Handle(1);
376        // not long enogugh
377        assert!(VolumeState::from_value(handle, &[]).is_err());
378        // too long is fine, but mute value is wrong.
379        assert!(VolumeState::from_value(handle, &[1, 2, 3, 4]).is_err());
380        // muted
381        let state = VolumeState::from_value(handle, &[1, 1, 3, 4]).expect("okay");
382        assert_eq!(state.mute, true);
383        assert_eq!(state.setting, 1);
384        assert_eq!(state.change_counter, 3);
385        // not muted
386        let state = VolumeState::from_value(handle, &[3, 0, 1]).expect("okay");
387        assert_eq!(state.mute, false);
388        assert_eq!(state.setting, 3);
389        assert_eq!(state.change_counter, 1);
390    }
391
392    #[test]
393    fn volume_flags_decode() {
394        use bt_gatt::types::*;
395        let chr = Characteristic {
396            handle: Handle(1),
397            uuid: VolumeFlags::UUID,
398            properties: CharacteristicProperty::Read.into(),
399            permissions: AttributePermissions {
400                read: Some(SecurityLevels::default()),
401                ..Default::default()
402            },
403            descriptors: Vec::new(),
404        };
405        // not long enogugh
406        assert!(VolumeFlags::from_chr(chr.clone(), &[]).is_err());
407        // persisted
408        let flags = VolumeFlags::from_chr(chr.clone(), &[1]).expect("okay");
409        assert_eq!(flags.persisted, VolumeSettingPersisted::UserSet);
410        // not persisted (other bits ignored)
411        let flags = VolumeFlags::from_chr(chr, &[2]).expect("okay");
412        assert_eq!(flags.persisted, VolumeSettingPersisted::Reset);
413    }
414
415    #[track_caller]
416    fn is_ready<T>(fut: impl Future<Output = T>) -> T {
417        use futures::FutureExt;
418        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
419        let mut fut_pinned = std::pin::pin!(fut);
420        let futures::task::Poll::Ready(x) = fut_pinned.poll_unpin(&mut noop_cx) else {
421            panic!("Future is not ready");
422        };
423        x
424    }
425
426    fn try_from_service(
427        service: FakePeerService,
428    ) -> core::result::Result<VolumeControlClient<FakeTypes>, crate::Error> {
429        is_ready(VolumeControlClient::<FakeTypes>::from_service(service))
430    }
431
432    const STATE_HANDLE: bt_gatt::types::Handle = bt_gatt::types::Handle(1);
433    const FLAGS_HANDLE: bt_gatt::types::Handle = bt_gatt::types::Handle(2);
434    const CP_HANDLE: bt_gatt::types::Handle = bt_gatt::types::Handle(3);
435
436    fn state_chr() -> bt_gatt::types::Characteristic {
437        use bt_gatt::types::*;
438        Characteristic {
439            handle: STATE_HANDLE,
440            uuid: VolumeState::UUID,
441            properties: CharacteristicProperty::Read | CharacteristicProperty::Notify,
442            permissions: AttributePermissions {
443                read: Some(SecurityLevels::default()),
444                update: Some(SecurityLevels::default()),
445                ..Default::default()
446            },
447            descriptors: Vec::new(),
448        }
449    }
450
451    fn build_fake_service() -> FakePeerService {
452        use bt_gatt::types::*;
453
454        let mut service = FakePeerService::new();
455
456        // No services, error (check between adding each one, should be an error until
457        // all chars are added.
458        assert!(try_from_service(service.clone()).is_err());
459        service.add_characteristic(state_chr(), vec![1, 0, 1]);
460        assert!(try_from_service(service.clone()).is_err());
461        service.add_characteristic(
462            Characteristic {
463                handle: FLAGS_HANDLE,
464                uuid: VolumeFlags::UUID,
465                properties: CharacteristicProperty::Read.into(),
466                permissions: AttributePermissions {
467                    read: Some(SecurityLevels::default()),
468                    ..Default::default()
469                },
470                descriptors: Vec::new(),
471            },
472            vec![1],
473        );
474        assert!(try_from_service(service.clone()).is_err());
475        service.add_characteristic(
476            Characteristic {
477                handle: CP_HANDLE,
478                uuid: VolumeControlPoint::UUID,
479                properties: CharacteristicProperty::Write.into(),
480                permissions: AttributePermissions {
481                    write: Some(SecurityLevels::default()),
482                    ..Default::default()
483                },
484                descriptors: Vec::new(),
485            },
486            vec![],
487        );
488
489        service
490    }
491
492    #[test]
493    fn build_from_service() {
494        use futures::{task::Poll, FutureExt};
495
496        let mut service = build_fake_service();
497        let client = try_from_service(service.clone()).unwrap();
498        assert_eq!(client.change_counter(), 1);
499
500        service.add_characteristic(state_chr(), vec![100, 1, 3]);
501        let mut update_fut = Box::pin(client.update());
502        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
503        match update_fut.poll_unpin(&mut noop_cx) {
504            Poll::Ready(Ok(volume)) => assert_eq!(volume, 100),
505            x => panic!("Didn't update right: {x:?}"),
506        }
507    }
508
509    #[test]
510    fn connect() {
511        let mut service = build_fake_service();
512        let mut client = FakeClient::new();
513
514        // Successfully didn't find the service
515        assert!(is_ready(VolumeControlClient::<FakeTypes>::connect(&client)).unwrap().is_none());
516
517        client.add_service(VCS_UUID, true, service.clone());
518
519        let client = is_ready(VolumeControlClient::<FakeTypes>::connect(&client)).unwrap().unwrap();
520        assert_eq!(client.change_counter(), 1);
521        service.add_characteristic(state_chr(), vec![250, 1, 3]);
522        assert_eq!(250, is_ready(client.update()).unwrap());
523    }
524
525    #[test]
526    fn volume() {
527        let mut service = build_fake_service();
528        let client = try_from_service(service.clone()).unwrap();
529        assert_eq!(client.change_counter(), 1);
530
531        service.expect_characteristic_value(&CP_HANDLE, vec![0x01, client.change_counter()]);
532        assert!(is_ready(client.volume_up(false)).is_ok());
533
534        service.expect_characteristic_value(&CP_HANDLE, vec![0x03, client.change_counter()]);
535        assert!(is_ready(client.volume_up(true)).is_ok());
536
537        service.expect_characteristic_value(&CP_HANDLE, vec![0x02, client.change_counter()]);
538        assert!(is_ready(client.volume_down(true)).is_ok());
539
540        service.add_characteristic(state_chr(), vec![250, 1, 3]);
541        assert_eq!(250, is_ready(client.update()).unwrap());
542
543        service.expect_characteristic_value(&CP_HANDLE, vec![0x00, 3]);
544        assert!(is_ready(client.volume_down(false)).is_ok());
545    }
546
547    #[test]
548    fn mutes() {
549        let mut service = build_fake_service();
550        let client = try_from_service(service.clone()).unwrap();
551        assert_eq!(client.change_counter(), 1);
552
553        service.expect_characteristic_value(&CP_HANDLE, vec![0x06, 1]);
554        assert!(is_ready(client.mute()).is_ok());
555
556        service.expect_characteristic_value(&CP_HANDLE, vec![0x05, 1]);
557        assert!(is_ready(client.unmute()).is_ok());
558
559        service.expect_characteristic_value(&CP_HANDLE, vec![0x05, 1]);
560        assert!(is_ready(client.unmute()).is_ok());
561    }
562
563    #[test]
564    fn absolute_volume() {
565        let mut service = build_fake_service();
566        let client = try_from_service(service.clone()).unwrap();
567        assert_eq!(client.change_counter(), 1);
568
569        service.expect_characteristic_value(&CP_HANDLE, vec![0x04, 1, 123]);
570        assert!(is_ready(client.set_absolute_volume(123)).is_ok());
571    }
572}