1use bt_common::core::ltv::LtValue;
6use bt_common::core::CodecId;
7use bt_common::generic_audio::codec_capabilities::CodecCapability;
8use bt_common::generic_audio::metadata_ltv::Metadata;
9use bt_common::generic_audio::{AudioLocation, ContextType};
10use bt_common::packet_encoding::{Decodable, Encodable};
11use bt_common::Uuid;
12use bt_gatt::{client::FromCharacteristic, Characteristic};
13
14use std::collections::HashSet;
15
16pub mod debug;
17pub mod server;
18
19pub use server::types::AudioContexts;
20
21pub const PACS_UUID: Uuid = Uuid::from_u16(0x1850);
23
24#[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#[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 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#[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 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 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 FromIterator<ContextType> for AvailableContexts {
307 fn from_iter<I: IntoIterator<Item = ContextType>>(iter: I) -> Self {
308 let types_: HashSet<_> = iter.into_iter().collect();
309 if types_.len() == 0 {
310 AvailableContexts::NotAvailable
311 } else {
312 AvailableContexts::Available(types_)
313 }
314 }
315}
316
317impl Decodable for AvailableContexts {
318 type Error = bt_common::packet_encoding::Error;
319
320 fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
321 if buf.len() < 2 {
322 return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), 2);
323 }
324 let encoded = u16::from_le_bytes([buf[0], buf[1]]);
325 if encoded == 0 {
326 (Ok(Self::NotAvailable), 2)
327 } else {
328 (Ok(Self::Available(ContextType::from_bits(encoded).collect())), 2)
329 }
330 }
331}
332
333impl Encodable for AvailableContexts {
334 type Error = bt_common::packet_encoding::Error;
335
336 fn encoded_len(&self) -> core::primitive::usize {
337 2
338 }
339
340 fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
341 if buf.len() < 2 {
342 return Err(Self::Error::BufferTooSmall);
343 }
344 match self {
345 AvailableContexts::NotAvailable => [buf[0], buf[1]] = [0x00, 0x00],
346 AvailableContexts::Available(set) => {
347 [buf[0], buf[1]] = ContextType::to_bits(set.iter()).to_le_bytes()
348 }
349 }
350 Ok(())
351 }
352}
353
354impl From<&HashSet<ContextType>> for AvailableContexts {
355 fn from(value: &HashSet<ContextType>) -> Self {
356 if value.is_empty() {
357 return AvailableContexts::NotAvailable;
358 }
359 AvailableContexts::Available(value.clone())
360 }
361}
362
363#[derive(Debug, Clone)]
364pub struct AvailableAudioContexts {
365 pub handle: bt_gatt::types::Handle,
366 pub sink: AvailableContexts,
367 pub source: AvailableContexts,
368}
369
370impl AvailableAudioContexts {
371 fn into_char_value(&self) -> Vec<u8> {
372 let mut val = Vec::with_capacity(self.sink.encoded_len() + self.source.encoded_len());
373 val.resize(self.sink.encoded_len() + self.source.encoded_len(), 0);
374 self.sink.encode(&mut val[0..]).unwrap();
375 self.source.encode(&mut val[2..]).unwrap();
376 val
377 }
378}
379
380impl FromCharacteristic for AvailableAudioContexts {
381 const UUID: Uuid = Uuid::from_u16(0x2BCD);
383
384 fn from_chr(
385 characteristic: Characteristic,
386 value: &[u8],
387 ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
388 let handle = characteristic.handle;
389 if value.len() < 4 {
390 return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
391 }
392 let sink = AvailableContexts::decode(&value[0..2]).0?;
393 let source = AvailableContexts::decode(&value[2..4]).0?;
394 Ok(Self { handle, sink, source })
395 }
396
397 fn update(
398 &mut self,
399 new_value: &[u8],
400 ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
401 if new_value.len() != 4 {
402 return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
403 }
404 let sink = AvailableContexts::decode(&new_value[0..2]).0?;
405 let source = AvailableContexts::decode(&new_value[2..4]).0?;
406 self.sink = sink;
407 self.source = source;
408 Ok(self)
409 }
410}
411
412#[derive(Debug, Clone)]
413pub struct SupportedAudioContexts {
414 pub handle: bt_gatt::types::Handle,
415 pub sink: HashSet<ContextType>,
416 pub source: HashSet<ContextType>,
417}
418
419impl SupportedAudioContexts {
420 fn into_char_value(&self) -> Vec<u8> {
421 let mut val = Vec::with_capacity(self.encoded_len());
422 val.resize(self.encoded_len(), 0);
423 self.encode(&mut val[0..]).unwrap();
424 val
425 }
426}
427
428impl Encodable for SupportedAudioContexts {
429 type Error = bt_common::packet_encoding::Error;
430
431 fn encoded_len(&self) -> core::primitive::usize {
432 4
433 }
434
435 fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
436 if buf.len() < 4 {
437 return Err(Self::Error::BufferTooSmall);
438 }
439 [buf[0], buf[1]] = ContextType::to_bits(self.sink.iter()).to_le_bytes();
440 [buf[2], buf[3]] = ContextType::to_bits(self.source.iter()).to_le_bytes();
441 Ok(())
442 }
443}
444
445impl FromCharacteristic for SupportedAudioContexts {
446 const UUID: Uuid = Uuid::from_u16(0x2BCE);
447
448 fn from_chr(
449 characteristic: Characteristic,
450 value: &[u8],
451 ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
452 let handle = characteristic.handle;
453 if value.len() < 4 {
454 return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
455 }
456 let sink = ContextType::from_bits(u16::from_le_bytes([value[0], value[1]])).collect();
457 let source = ContextType::from_bits(u16::from_le_bytes([value[2], value[3]])).collect();
458 Ok(Self { handle, sink, source })
459 }
460
461 fn update(
462 &mut self,
463 new_value: &[u8],
464 ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
465 if new_value.len() < 4 {
466 return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
467 }
468 self.sink =
469 ContextType::from_bits(u16::from_le_bytes([new_value[0], new_value[1]])).collect();
470 self.source =
471 ContextType::from_bits(u16::from_le_bytes([new_value[2], new_value[3]])).collect();
472 Ok(self)
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 use bt_common::{
481 generic_audio::codec_capabilities::{CodecCapabilityType, SamplingFrequency},
482 Uuid,
483 };
484 use bt_gatt::{
485 types::{AttributePermissions, Handle},
486 Characteristic,
487 };
488
489 use pretty_assertions::assert_eq;
490
491 const SINGLE_PAC_SIMPLE: [u8; 12] = [
492 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01, 0xC0, 0x02, 0x00, ];
500
501 const MULTIPLE_PAC_COMPLEX: [u8; 29] = [
502 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x03, 0x01, 0xC0, 0x02, 0x05, 0x04, 0x11, 0x00, 0x00, 0x10, 0x04, 0x03, 0x01, 0x03, 0x00, 0xFF, 0xE0, 0x00, 0x01, 0x10, 0x00, 0x00, ];
519
520 #[track_caller]
521 fn assert_has_frequencies(
522 cap: &bt_common::generic_audio::codec_capabilities::CodecCapability,
523 freqs: &[SamplingFrequency],
524 ) {
525 let CodecCapability::SupportedSamplingFrequencies(set) = cap else {
526 unreachable!();
527 };
528
529 for freq in freqs {
530 assert!(set.contains(freq));
531 }
532 }
533
534 #[test]
535 fn simple_sink_pac() {
536 let pac = SinkPac::from_chr(
537 Characteristic {
538 handle: Handle(1),
539 uuid: Uuid::from_u16(0x2BC9),
540 properties: bt_gatt::types::CharacteristicProperties(vec![
541 bt_gatt::types::CharacteristicProperty::Read,
542 ]),
543 permissions: AttributePermissions::default(),
544 descriptors: vec![],
545 },
546 &SINGLE_PAC_SIMPLE,
547 )
548 .expect("should decode correctly");
549
550 assert_eq!(pac.capabilities.len(), 1);
551 let cap = &pac.capabilities[0];
552 assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
553 let freq_cap = cap
554 .codec_specific_capabilities
555 .iter()
556 .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
557 .unwrap();
558 assert_has_frequencies(
559 freq_cap,
560 &[
561 SamplingFrequency::F44100Hz,
562 SamplingFrequency::F48000Hz,
563 SamplingFrequency::F96000Hz,
564 ],
565 );
566 }
567
568 #[test]
569 fn simple_source_pac() {
570 let pac = SinkPac::from_chr(
571 Characteristic {
572 handle: Handle(1),
573 uuid: Uuid::from_u16(0x2BC9),
574 properties: bt_gatt::types::CharacteristicProperties(vec![
575 bt_gatt::types::CharacteristicProperty::Read,
576 ]),
577 permissions: AttributePermissions::default(),
578 descriptors: vec![],
579 },
580 &SINGLE_PAC_SIMPLE,
581 )
582 .expect("should decode correctly");
583
584 assert_eq!(pac.capabilities.len(), 1);
585 let cap = &pac.capabilities[0];
586 assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
587 let freq_cap = cap
588 .codec_specific_capabilities
589 .iter()
590 .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
591 .unwrap();
592 assert_has_frequencies(
593 freq_cap,
594 &[
595 SamplingFrequency::F44100Hz,
596 SamplingFrequency::F48000Hz,
597 SamplingFrequency::F96000Hz,
598 ],
599 );
600 }
601
602 #[test]
603 fn complex_sink_pac() {
604 let pac = SinkPac::from_chr(
605 Characteristic {
606 handle: Handle(1),
607 uuid: Uuid::from_u16(0x2BC9),
608 properties: bt_gatt::types::CharacteristicProperties(vec![
609 bt_gatt::types::CharacteristicProperty::Read,
610 ]),
611 permissions: AttributePermissions::default(),
612 descriptors: vec![],
613 },
614 &MULTIPLE_PAC_COMPLEX,
615 )
616 .expect("should decode correctly");
617 assert_eq!(pac.capabilities.len(), 2);
618 let first = &pac.capabilities[0];
619 assert_eq!(first.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Msbc));
620 let freq_cap = first
621 .codec_specific_capabilities
622 .iter()
623 .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
624 .unwrap();
625 assert_has_frequencies(
626 freq_cap,
627 &[
628 SamplingFrequency::F44100Hz,
629 SamplingFrequency::F48000Hz,
630 SamplingFrequency::F96000Hz,
631 ],
632 );
633
634 let metadata = &first.metadata;
635
636 assert_eq!(metadata.len(), 1);
637
638 let Metadata::PreferredAudioContexts(p) = &metadata[0] else {
639 panic!("expected PreferredAudioContexts, got {:?}", metadata[0]);
640 };
641
642 assert_eq!(p.len(), 2);
643
644 let second = &pac.capabilities[1];
645 assert_eq!(
646 second.codec_id,
647 CodecId::VendorSpecific {
648 company_id: 0x00E0.into(),
649 vendor_specific_codec_id: 0x1001_u16,
650 }
651 );
652 assert_eq!(second.codec_specific_capabilities.len(), 0);
653 }
654
655 #[test]
656 fn available_contexts_no_sink() {
657 let available = AvailableAudioContexts::from_chr(
658 Characteristic {
659 handle: Handle(1),
660 uuid: Uuid::from_u16(0x28CD),
661 properties: bt_gatt::types::CharacteristicProperties(vec![
662 bt_gatt::types::CharacteristicProperty::Read,
663 ]),
664 permissions: AttributePermissions::default(),
665 descriptors: vec![],
666 },
667 &[0x00, 0x00, 0x02, 0x04],
668 )
669 .expect("should decode correctly");
670 assert_eq!(available.handle, Handle(1));
671 assert_eq!(available.sink, AvailableContexts::NotAvailable);
672 let AvailableContexts::Available(a) = available.source else {
673 panic!("Source should be available");
674 };
675 assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
676 }
677
678 #[test]
679 fn available_contexts_wrong_size() {
680 let chr = Characteristic {
681 handle: Handle(1),
682 uuid: Uuid::from_u16(0x28CD),
683 properties: bt_gatt::types::CharacteristicProperties(vec![
684 bt_gatt::types::CharacteristicProperty::Read,
685 ]),
686 permissions: AttributePermissions::default(),
687 descriptors: vec![],
688 };
689 let _ = AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02])
690 .expect_err("should not decode with too short");
691
692 let available =
693 AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02, 0x04, 0xCA, 0xFE])
694 .expect("should attempt to decode with too long");
695
696 assert_eq!(available.sink, AvailableContexts::NotAvailable);
697 let AvailableContexts::Available(a) = available.source else {
698 panic!("Source should be available");
699 };
700 assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
701 }
702
703 #[test]
704 fn supported_contexts() {
705 let chr = Characteristic {
706 handle: Handle(1),
707 uuid: Uuid::from_u16(0x28CE),
708 properties: bt_gatt::types::CharacteristicProperties(vec![
709 bt_gatt::types::CharacteristicProperty::Read,
710 ]),
711 permissions: AttributePermissions::default(),
712 descriptors: vec![],
713 };
714
715 let supported =
716 SupportedAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x00, 0x00]).unwrap();
717 assert_eq!(supported.sink.len(), 0);
718 assert_eq!(supported.source.len(), 0);
719
720 let supported =
721 SupportedAudioContexts::from_chr(chr.clone(), &[0x08, 0x06, 0x06, 0x03]).unwrap();
722 assert_eq!(supported.sink.len(), 3);
723 assert_eq!(supported.source.len(), 4);
724 assert_eq!(
725 supported.source,
726 [
727 ContextType::Media,
728 ContextType::Conversational,
729 ContextType::Ringtone,
730 ContextType::Notifications
731 ]
732 .into_iter()
733 .collect()
734 );
735 }
736}