bt_pacs/
lib.rs

1// Copyright 2023 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
5use bt_common::Uuid;
6use bt_common::core::CodecId;
7use bt_common::core::ltv::LtValue;
8use bt_common::generic_audio::codec_capabilities::CodecCapability;
9use bt_common::generic_audio::metadata_ltv::Metadata;
10use bt_common::generic_audio::{AudioLocation, ContextType};
11use bt_common::packet_encoding::{Decodable, Encodable};
12use bt_gatt::{Characteristic, client::FromCharacteristic};
13
14use std::collections::HashSet;
15
16pub mod debug;
17pub mod server;
18
19pub use server::types::AudioContexts;
20
21/// UUID from Assigned Numbers section 3.4.
22pub const PACS_UUID: Uuid = Uuid::from_u16(0x1850);
23
24/// A Published Audio Capability (PAC) record.
25/// Published Audio Capabilities represent the capabilities of a given peer to
26/// transmit or receive Audio capabilities, exposed in PAC records, represent
27/// the server audio capabilities independent of available resources at any
28/// given time. Audio capabilities do not distinguish between unicast
29/// Audio Streams or broadcast Audio Streams.
30#[derive(Debug, Clone, PartialEq)]
31pub struct PacRecord {
32    pub codec_id: CodecId,
33    pub codec_specific_capabilities: Vec<CodecCapability>,
34    pub metadata: Vec<Metadata>,
35}
36
37impl Decodable for PacRecord {
38    type Error = bt_common::packet_encoding::Error;
39
40    fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
41        let mut idx = 0;
42        let codec_id = match CodecId::decode(&buf[idx..]) {
43            (Ok(codec_id), consumed) => {
44                idx += consumed;
45                codec_id
46            }
47            (Err(e), _) => return (Err(e), buf.len()),
48        };
49        let codec_specific_capabilites_length = buf[idx] as usize;
50        idx += 1;
51        if idx + codec_specific_capabilites_length > buf.len() {
52            return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
53        }
54        let (results, consumed) =
55            CodecCapability::decode_all(&buf[idx..idx + codec_specific_capabilites_length]);
56        if consumed != codec_specific_capabilites_length {
57            return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
58        }
59        let codec_specific_capabilities = results.into_iter().filter_map(Result::ok).collect();
60        idx += consumed;
61
62        let metadata_length = buf[idx] as usize;
63        idx += 1;
64        if idx + metadata_length > buf.len() {
65            return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
66        }
67        let (results, consumed) = Metadata::decode_all(&buf[idx..idx + metadata_length]);
68        if consumed != metadata_length {
69            return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
70        }
71        let metadata = results.into_iter().filter_map(Result::ok).collect();
72        idx += consumed;
73
74        (Ok(Self { codec_id, codec_specific_capabilities, metadata }), idx)
75    }
76}
77
78impl Encodable for PacRecord {
79    type Error = bt_common::packet_encoding::Error;
80
81    fn encoded_len(&self) -> core::primitive::usize {
82        5usize
83            + self.codec_specific_capabilities.iter().fold(0, |a, x| a + x.encoded_len())
84            + self.metadata.iter().fold(0, |a, x| a + x.encoded_len())
85            + 2
86    }
87
88    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
89        if buf.len() < self.encoded_len() {
90            return Err(Self::Error::BufferTooSmall);
91        }
92        self.codec_id.encode(&mut buf[0..]).unwrap();
93        let codec_capabilities_len =
94            self.codec_specific_capabilities.iter().fold(0, |a, x| a + x.encoded_len());
95        buf[5] = codec_capabilities_len as u8;
96        LtValue::encode_all(self.codec_specific_capabilities.clone().into_iter(), &mut buf[6..])?;
97        let metadata_len = self.metadata.iter().fold(0, |a, x| a + x.encoded_len());
98        buf[6 + codec_capabilities_len] = metadata_len as u8;
99        if metadata_len != 0 {
100            LtValue::encode_all(
101                self.metadata.clone().into_iter(),
102                &mut buf[6 + codec_capabilities_len + 1..],
103            )?;
104        }
105        Ok(())
106    }
107}
108
109fn pac_records_into_char_value(records: &Vec<PacRecord>) -> Vec<u8> {
110    let mut val = Vec::new();
111    val.push(records.len() as u8);
112    let mut idx = 1;
113    for record in records {
114        let record_len = record.encoded_len();
115        val.resize(val.len() + record_len, 0);
116        record.encode(&mut val[idx..]).unwrap();
117        idx += record_len;
118    }
119    val
120}
121
122fn pac_records_from_bytes(
123    value: &[u8],
124) -> Result<Vec<PacRecord>, bt_common::packet_encoding::Error> {
125    if value.len() < 1 {
126        return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
127    }
128    let num_of_pac_records = value[0] as usize;
129    let mut next_idx = 1;
130    let mut capabilities = Vec::with_capacity(num_of_pac_records);
131    for _ in 0..num_of_pac_records {
132        let (cap, consumed) = PacRecord::decode(&value[next_idx..]);
133        capabilities.push(cap?);
134        next_idx += consumed;
135    }
136    Ok(capabilities)
137}
138
139/// One Sink Published Audio Capability Characteristic, or Sink PAC, exposed on
140/// a service. More than one Sink PAC can exist on a given PACS service.  If
141/// multiple are exposed, they are returned separately and can be notified by
142/// the server separately.
143#[derive(Debug, PartialEq, Clone)]
144pub struct SinkPac {
145    pub handle: bt_gatt::types::Handle,
146    pub capabilities: Vec<PacRecord>,
147}
148
149impl FromCharacteristic for SinkPac {
150    /// UUID from Assigned Numbers section 3.8.
151    const UUID: Uuid = Uuid::from_u16(0x2BC9);
152
153    fn from_chr(
154        characteristic: Characteristic,
155        value: &[u8],
156    ) -> Result<Self, bt_common::packet_encoding::Error> {
157        let handle = characteristic.handle;
158        let capabilities = pac_records_from_bytes(value)?;
159        Ok(Self { handle, capabilities })
160    }
161
162    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
163        self.capabilities = pac_records_from_bytes(new_value)?;
164        Ok(self)
165    }
166}
167
168/// One Sink Published Audio Capability Characteristic, or Sink PAC, exposed on
169/// a service. More than one Sink PAC can exist on a given PACS service.  If
170/// multiple are exposed, they are returned separately and can be notified by
171/// the server separately.
172#[derive(Debug, PartialEq, Clone)]
173pub struct SourcePac {
174    pub handle: bt_gatt::types::Handle,
175    pub capabilities: Vec<PacRecord>,
176}
177
178impl FromCharacteristic for SourcePac {
179    /// UUID from Assigned Numbers section 3.8.
180    const UUID: Uuid = Uuid::from_u16(0x2BCB);
181
182    fn from_chr(
183        characteristic: Characteristic,
184        value: &[u8],
185    ) -> Result<Self, bt_common::packet_encoding::Error> {
186        let handle = characteristic.handle;
187        let capabilities = pac_records_from_bytes(value)?;
188        Ok(Self { handle, capabilities })
189    }
190
191    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
192        self.capabilities = pac_records_from_bytes(new_value)?;
193        Ok(self)
194    }
195}
196
197#[derive(Debug, PartialEq, Clone, Default)]
198pub struct AudioLocations {
199    pub locations: HashSet<AudioLocation>,
200}
201
202impl Decodable for AudioLocations {
203    type Error = bt_common::packet_encoding::Error;
204
205    fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
206        if buf.len() != 4 {
207            return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
208        }
209        let locations =
210            AudioLocation::from_bits(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
211                .collect();
212        (Ok(AudioLocations { locations }), 4)
213    }
214}
215
216impl Encodable for AudioLocations {
217    type Error = bt_common::packet_encoding::Error;
218
219    fn encoded_len(&self) -> core::primitive::usize {
220        4
221    }
222
223    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
224        if buf.len() < 4 {
225            return Err(Self::Error::BufferTooSmall);
226        }
227        [buf[0], buf[1], buf[2], buf[3]] =
228            AudioLocation::to_bits(self.locations.iter()).to_le_bytes();
229        Ok(())
230    }
231}
232
233#[derive(Debug, PartialEq, Clone)]
234pub struct SourceAudioLocations {
235    pub handle: bt_gatt::types::Handle,
236    pub locations: AudioLocations,
237}
238
239impl SourceAudioLocations {
240    fn into_char_value(&self) -> Vec<u8> {
241        let mut val = Vec::with_capacity(self.locations.encoded_len());
242        val.resize(self.locations.encoded_len(), 0);
243        self.locations.encode(&mut val[..]).unwrap();
244        val
245    }
246}
247
248impl FromCharacteristic for SourceAudioLocations {
249    /// UUID from Assigned Numbers section 3.8.
250    const UUID: Uuid = Uuid::from_u16(0x2BCC);
251
252    fn from_chr(
253        characteristic: Characteristic,
254        value: &[u8],
255    ) -> Result<Self, bt_common::packet_encoding::Error> {
256        let handle = characteristic.handle;
257        let locations = AudioLocations::decode(value).0?;
258        Ok(Self { handle, locations })
259    }
260
261    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
262        self.locations = AudioLocations::decode(new_value).0?;
263        Ok(self)
264    }
265}
266
267#[derive(Debug, PartialEq, Clone)]
268pub struct SinkAudioLocations {
269    pub handle: bt_gatt::types::Handle,
270    pub locations: AudioLocations,
271}
272
273impl SinkAudioLocations {
274    fn into_char_value(&self) -> Vec<u8> {
275        let mut val = Vec::with_capacity(self.locations.encoded_len());
276        val.resize(self.locations.encoded_len(), 0);
277        self.locations.encode(&mut val[..]).unwrap();
278        val
279    }
280}
281
282impl FromCharacteristic for SinkAudioLocations {
283    const UUID: Uuid = Uuid::from_u16(0x2BCA);
284
285    fn from_chr(
286        characteristic: Characteristic,
287        value: &[u8],
288    ) -> Result<Self, bt_common::packet_encoding::Error> {
289        let handle = characteristic.handle;
290        let locations = AudioLocations::decode(value).0?;
291        Ok(Self { handle, locations })
292    }
293
294    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
295        self.locations = AudioLocations::decode(new_value).0?;
296        Ok(self)
297    }
298}
299
300#[derive(Debug, PartialEq, Clone)]
301pub enum AvailableContexts {
302    NotAvailable,
303    Available(HashSet<ContextType>),
304}
305
306impl Decodable for AvailableContexts {
307    type Error = bt_common::packet_encoding::Error;
308
309    fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
310        if buf.len() < 2 {
311            return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), 2);
312        }
313        let encoded = u16::from_le_bytes([buf[0], buf[1]]);
314        if encoded == 0 {
315            (Ok(Self::NotAvailable), 2)
316        } else {
317            (Ok(Self::Available(ContextType::from_bits(encoded).collect())), 2)
318        }
319    }
320}
321
322impl Encodable for AvailableContexts {
323    type Error = bt_common::packet_encoding::Error;
324
325    fn encoded_len(&self) -> core::primitive::usize {
326        2
327    }
328
329    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
330        if buf.len() < 2 {
331            return Err(Self::Error::BufferTooSmall);
332        }
333        match self {
334            AvailableContexts::NotAvailable => [buf[0], buf[1]] = [0x00, 0x00],
335            AvailableContexts::Available(set) => {
336                [buf[0], buf[1]] = ContextType::to_bits(set.iter()).to_le_bytes()
337            }
338        }
339        Ok(())
340    }
341}
342
343impl From<&HashSet<ContextType>> for AvailableContexts {
344    fn from(value: &HashSet<ContextType>) -> Self {
345        if value.is_empty() {
346            return AvailableContexts::NotAvailable;
347        }
348        AvailableContexts::Available(value.clone())
349    }
350}
351
352#[derive(Debug, Clone)]
353pub struct AvailableAudioContexts {
354    pub handle: bt_gatt::types::Handle,
355    pub sink: AvailableContexts,
356    pub source: AvailableContexts,
357}
358
359impl AvailableAudioContexts {
360    fn into_char_value(&self) -> Vec<u8> {
361        let mut val = Vec::with_capacity(self.sink.encoded_len() + self.source.encoded_len());
362        val.resize(self.sink.encoded_len() + self.source.encoded_len(), 0);
363        self.sink.encode(&mut val[0..]).unwrap();
364        self.source.encode(&mut val[2..]).unwrap();
365        val
366    }
367}
368
369impl FromCharacteristic for AvailableAudioContexts {
370    /// UUID from Assigned Numbers section 3.8.
371    const UUID: Uuid = Uuid::from_u16(0x2BCD);
372
373    fn from_chr(
374        characteristic: Characteristic,
375        value: &[u8],
376    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
377        let handle = characteristic.handle;
378        if value.len() < 4 {
379            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
380        }
381        let sink = AvailableContexts::decode(&value[0..2]).0?;
382        let source = AvailableContexts::decode(&value[2..4]).0?;
383        Ok(Self { handle, sink, source })
384    }
385
386    fn update(
387        &mut self,
388        new_value: &[u8],
389    ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
390        if new_value.len() != 4 {
391            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
392        }
393        let sink = AvailableContexts::decode(&new_value[0..2]).0?;
394        let source = AvailableContexts::decode(&new_value[2..4]).0?;
395        self.sink = sink;
396        self.source = source;
397        Ok(self)
398    }
399}
400
401#[derive(Debug, Clone)]
402pub struct SupportedAudioContexts {
403    pub handle: bt_gatt::types::Handle,
404    pub sink: HashSet<ContextType>,
405    pub source: HashSet<ContextType>,
406}
407
408impl SupportedAudioContexts {
409    fn into_char_value(&self) -> Vec<u8> {
410        let mut val = Vec::with_capacity(self.encoded_len());
411        val.resize(self.encoded_len(), 0);
412        self.encode(&mut val[0..]).unwrap();
413        val
414    }
415}
416
417impl Encodable for SupportedAudioContexts {
418    type Error = bt_common::packet_encoding::Error;
419
420    fn encoded_len(&self) -> core::primitive::usize {
421        4
422    }
423
424    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
425        if buf.len() < 4 {
426            return Err(Self::Error::BufferTooSmall);
427        }
428        [buf[0], buf[1]] = ContextType::to_bits(self.sink.iter()).to_le_bytes();
429        [buf[2], buf[3]] = ContextType::to_bits(self.source.iter()).to_le_bytes();
430        Ok(())
431    }
432}
433
434impl FromCharacteristic for SupportedAudioContexts {
435    const UUID: Uuid = Uuid::from_u16(0x2BCE);
436
437    fn from_chr(
438        characteristic: Characteristic,
439        value: &[u8],
440    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
441        let handle = characteristic.handle;
442        if value.len() < 4 {
443            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
444        }
445        let sink = ContextType::from_bits(u16::from_le_bytes([value[0], value[1]])).collect();
446        let source = ContextType::from_bits(u16::from_le_bytes([value[2], value[3]])).collect();
447        Ok(Self { handle, sink, source })
448    }
449
450    fn update(
451        &mut self,
452        new_value: &[u8],
453    ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
454        if new_value.len() < 4 {
455            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
456        }
457        self.sink =
458            ContextType::from_bits(u16::from_le_bytes([new_value[0], new_value[1]])).collect();
459        self.source =
460            ContextType::from_bits(u16::from_le_bytes([new_value[2], new_value[3]])).collect();
461        Ok(self)
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468
469    use bt_common::{
470        Uuid,
471        generic_audio::codec_capabilities::{CodecCapabilityType, SamplingFrequency},
472    };
473    use bt_gatt::{
474        Characteristic,
475        types::{AttributePermissions, Handle},
476    };
477
478    use pretty_assertions::assert_eq;
479
480    const SINGLE_PAC_SIMPLE: [u8; 12] = [
481        0x01, // Num of records; 1
482        0x06, // CodecID: codec LC3,
483        0x00, 0x00, 0x00, 0x00, // CodecID: company and specific id (zero)
484        0x04, // Len of Codec capabilities
485        0x03, 0x01, // lt: supported_sampling_frequencies
486        0xC0, 0x02, // Supported: 44.1kHz, 48kHz, 96kHz
487        0x00, // Len of metadata
488    ];
489
490    const MULTIPLE_PAC_COMPLEX: [u8; 29] = [
491        0x02, // Num of records; 2
492        0x05, // CodecID: codec MSBC,
493        0x00, 0x00, 0x00, 0x00, // CodecID: company and specific id (zero)
494        0x0A, // Len of Codec capabilities (10)
495        0x03, 0x01, // lt: supported_sampling_frequencies
496        0xC0, 0x02, // Supported: 44.1kHz, 48kHz, 96kHz
497        0x05, 0x04, // lt: Octets per codec frame
498        0x11, 0x00, // Minimum: 9
499        0x00, 0x10, // Maximum: 4096
500        0x04, // Len of metadata: 4
501        0x03, 0x01, 0x03, 0x00, // PreferredAudioContexts
502        0xFF, // CodecId: Vendor specific
503        0xE0, 0x00, // Google
504        0x01, 0x10, // ID 4097
505        0x00, // Len of codec capabilities (none)
506        0x00, // Len of metadata (none)
507    ];
508
509    #[track_caller]
510    fn assert_has_frequencies(
511        cap: &bt_common::generic_audio::codec_capabilities::CodecCapability,
512        freqs: &[SamplingFrequency],
513    ) {
514        let CodecCapability::SupportedSamplingFrequencies(set) = cap else {
515            unreachable!();
516        };
517
518        for freq in freqs {
519            assert!(set.contains(freq));
520        }
521    }
522
523    #[test]
524    fn simple_sink_pac() {
525        let pac = SinkPac::from_chr(
526            Characteristic {
527                handle: Handle(1),
528                uuid: Uuid::from_u16(0x2BC9),
529                properties: bt_gatt::types::CharacteristicProperties(vec![
530                    bt_gatt::types::CharacteristicProperty::Read,
531                ]),
532                permissions: AttributePermissions::default(),
533                descriptors: vec![],
534            },
535            &SINGLE_PAC_SIMPLE,
536        )
537        .expect("should decode correctly");
538
539        assert_eq!(pac.capabilities.len(), 1);
540        let cap = &pac.capabilities[0];
541        assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
542        let freq_cap = cap
543            .codec_specific_capabilities
544            .iter()
545            .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
546            .unwrap();
547        assert_has_frequencies(
548            freq_cap,
549            &[
550                SamplingFrequency::F44100Hz,
551                SamplingFrequency::F48000Hz,
552                SamplingFrequency::F96000Hz,
553            ],
554        );
555    }
556
557    #[test]
558    fn simple_source_pac() {
559        let pac = SinkPac::from_chr(
560            Characteristic {
561                handle: Handle(1),
562                uuid: Uuid::from_u16(0x2BC9),
563                properties: bt_gatt::types::CharacteristicProperties(vec![
564                    bt_gatt::types::CharacteristicProperty::Read,
565                ]),
566                permissions: AttributePermissions::default(),
567                descriptors: vec![],
568            },
569            &SINGLE_PAC_SIMPLE,
570        )
571        .expect("should decode correctly");
572
573        assert_eq!(pac.capabilities.len(), 1);
574        let cap = &pac.capabilities[0];
575        assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
576        let freq_cap = cap
577            .codec_specific_capabilities
578            .iter()
579            .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
580            .unwrap();
581        assert_has_frequencies(
582            freq_cap,
583            &[
584                SamplingFrequency::F44100Hz,
585                SamplingFrequency::F48000Hz,
586                SamplingFrequency::F96000Hz,
587            ],
588        );
589    }
590
591    #[test]
592    fn complex_sink_pac() {
593        let pac = SinkPac::from_chr(
594            Characteristic {
595                handle: Handle(1),
596                uuid: Uuid::from_u16(0x2BC9),
597                properties: bt_gatt::types::CharacteristicProperties(vec![
598                    bt_gatt::types::CharacteristicProperty::Read,
599                ]),
600                permissions: AttributePermissions::default(),
601                descriptors: vec![],
602            },
603            &MULTIPLE_PAC_COMPLEX,
604        )
605        .expect("should decode correctly");
606        assert_eq!(pac.capabilities.len(), 2);
607        let first = &pac.capabilities[0];
608        assert_eq!(first.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Msbc));
609        let freq_cap = first
610            .codec_specific_capabilities
611            .iter()
612            .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
613            .unwrap();
614        assert_has_frequencies(
615            freq_cap,
616            &[
617                SamplingFrequency::F44100Hz,
618                SamplingFrequency::F48000Hz,
619                SamplingFrequency::F96000Hz,
620            ],
621        );
622
623        let metadata = &first.metadata;
624
625        assert_eq!(metadata.len(), 1);
626
627        let Metadata::PreferredAudioContexts(p) = &metadata[0] else {
628            panic!("expected PreferredAudioContexts, got {:?}", metadata[0]);
629        };
630
631        assert_eq!(p.len(), 2);
632
633        let second = &pac.capabilities[1];
634        assert_eq!(
635            second.codec_id,
636            CodecId::VendorSpecific {
637                company_id: 0x00E0.into(),
638                vendor_specific_codec_id: 0x1001_u16,
639            }
640        );
641        assert_eq!(second.codec_specific_capabilities.len(), 0);
642    }
643
644    #[test]
645    fn available_contexts_no_sink() {
646        let available = AvailableAudioContexts::from_chr(
647            Characteristic {
648                handle: Handle(1),
649                uuid: Uuid::from_u16(0x28CD),
650                properties: bt_gatt::types::CharacteristicProperties(vec![
651                    bt_gatt::types::CharacteristicProperty::Read,
652                ]),
653                permissions: AttributePermissions::default(),
654                descriptors: vec![],
655            },
656            &[0x00, 0x00, 0x02, 0x04],
657        )
658        .expect("should decode correctly");
659        assert_eq!(available.handle, Handle(1));
660        assert_eq!(available.sink, AvailableContexts::NotAvailable);
661        let AvailableContexts::Available(a) = available.source else {
662            panic!("Source should be available");
663        };
664        assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
665    }
666
667    #[test]
668    fn available_contexts_wrong_size() {
669        let chr = Characteristic {
670            handle: Handle(1),
671            uuid: Uuid::from_u16(0x28CD),
672            properties: bt_gatt::types::CharacteristicProperties(vec![
673                bt_gatt::types::CharacteristicProperty::Read,
674            ]),
675            permissions: AttributePermissions::default(),
676            descriptors: vec![],
677        };
678        let _ = AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02])
679            .expect_err("should not decode with too short");
680
681        let available =
682            AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02, 0x04, 0xCA, 0xFE])
683                .expect("should attempt to decode with too long");
684
685        assert_eq!(available.sink, AvailableContexts::NotAvailable);
686        let AvailableContexts::Available(a) = available.source else {
687            panic!("Source should be available");
688        };
689        assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
690    }
691
692    #[test]
693    fn supported_contexts() {
694        let chr = Characteristic {
695            handle: Handle(1),
696            uuid: Uuid::from_u16(0x28CE),
697            properties: bt_gatt::types::CharacteristicProperties(vec![
698                bt_gatt::types::CharacteristicProperty::Read,
699            ]),
700            permissions: AttributePermissions::default(),
701            descriptors: vec![],
702        };
703
704        let supported =
705            SupportedAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x00, 0x00]).unwrap();
706        assert_eq!(supported.sink.len(), 0);
707        assert_eq!(supported.source.len(), 0);
708
709        let supported =
710            SupportedAudioContexts::from_chr(chr.clone(), &[0x08, 0x06, 0x06, 0x03]).unwrap();
711        assert_eq!(supported.sink.len(), 3);
712        assert_eq!(supported.source.len(), 4);
713        assert_eq!(
714            supported.source,
715            [
716                ContextType::Media,
717                ContextType::Conversational,
718                ContextType::Ringtone,
719                ContextType::Notifications
720            ]
721            .into_iter()
722            .collect()
723        );
724    }
725}