1use 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
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 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 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, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01, 0xC0, 0x02, 0x00, ];
489
490 const MULTIPLE_PAC_COMPLEX: [u8; 29] = [
491 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, ];
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}