1use std::sync::Arc;
9
10use super::MediaCapability;
11use super::values::{Codec, EncryptionCapability, IpAddressType, ReceiveTransmit};
12use super::wire::CodecError;
13
14const MAX_AUDIO_CAPABILITIES: usize = 18;
15const MAX_VIDEO_CAPABILITIES: usize = 10;
16const MAX_DATA_CAPABILITIES: usize = 5;
17const MAX_CUSTOM_PICTURES: usize = 6;
18const MAX_CONFERENCE_SERVICES: usize = 4;
19const MAX_SERVICE_LAYOUTS: usize = 5;
20const MAX_LEVEL_PREFERENCES: usize = 4;
21
22const CUSTOM_PICTURES_OFFSET: usize = 20;
23const CUSTOM_PICTURE_SIZE: usize = 20;
24const CONFERENCE_OFFSET: usize = CUSTOM_PICTURES_OFFSET + MAX_CUSTOM_PICTURES * CUSTOM_PICTURE_SIZE;
25const CONFERENCE_SERVICE_SIZE: usize = 40;
26const CONFERENCE_SIZE: usize = 12 + MAX_CONFERENCE_SERVICES * CONFERENCE_SERVICE_SIZE;
27const AUDIO_OFFSET: usize = CONFERENCE_OFFSET + CONFERENCE_SIZE;
28const AUDIO_CAPABILITY_SIZE: usize = 16;
29const VIDEO_OFFSET: usize = AUDIO_OFFSET + MAX_AUDIO_CAPABILITIES * AUDIO_CAPABILITY_SIZE;
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum CapabilityUpdateVariant {
34 Version1,
37 Version1ExpandedVideo,
39 Version2,
41 Version3,
43}
44
45impl CapabilityUpdateVariant {
46 pub const fn message_id(self) -> u32 {
48 match self {
49 Self::Version1 | Self::Version1ExpandedVideo => 0x0030,
50 Self::Version2 => 0x0043,
51 Self::Version3 => 0x0044,
52 }
53 }
54
55 const fn video_entry_size(self, protocol: u32) -> usize {
56 match self {
57 Self::Version1 => 116,
58 Self::Version1ExpandedVideo | Self::Version2 => 132,
59 Self::Version3 if protocol < 17 => 136,
60 Self::Version3 => 140,
61 }
62 }
63
64 const fn data_entry_size(self) -> usize {
65 match self {
66 Self::Version3 => 20,
67 _ => 16,
68 }
69 }
70
71 const fn codec_parameter_words(self) -> usize {
72 match self {
73 Self::Version1 => 2,
74 _ => 6,
75 }
76 }
77
78 pub(crate) const fn minimum_payload_bytes(self, protocol: u32) -> usize {
79 let data_offset = VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * self.video_entry_size(protocol);
80 data_offset + MAX_DATA_CAPABILITIES * self.data_entry_size()
81 }
82
83 pub(crate) const fn maximum_payload_bytes(self, protocol: u32) -> usize {
84 if matches!(self, Self::Version3) {
85 2_380
86 } else {
87 self.minimum_payload_bytes(protocol)
88 }
89 }
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct CustomPictureFormat {
95 pub width: u32,
97 pub height: u32,
99 pub pixel_aspect_ratio: u32,
101 pub pixel_clock_conversion: u32,
103 pub pixel_clock_divisor: u32,
105}
106
107#[derive(Clone, Debug, Eq, PartialEq)]
108pub struct ConferenceServiceResource {
110 pub layouts: Vec<u32>,
111 pub service_number: u32,
112 pub max_streams: u32,
113 pub max_conferences: u32,
114 pub active_conference_on_registration: u32,
115}
116
117#[derive(Clone, Debug, Eq, PartialEq)]
118pub struct ConferenceResource {
120 pub active_streams_on_registration: u32,
121 pub max_bandwidth: u32,
123 pub services: Vec<ConferenceServiceResource>,
124}
125
126#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct VideoLevelPreference {
129 pub transmit_preference: u32,
131 pub format: u32,
132 pub max_bit_rate: u32,
133 pub min_bit_rate: u32,
134 pub minimum_picture_interval: u32,
135 pub service_number: u32,
136}
137
138#[derive(Clone, Debug, Eq, PartialEq)]
139pub struct VideoCapability {
141 pub codec: Codec,
142 pub direction: ReceiveTransmit,
143 pub level_preferences: Vec<VideoLevelPreference>,
144 pub codec_parameters: Vec<u32>,
146 pub encryption_capability: Option<EncryptionCapability>,
148 pub address_type: Option<IpAddressType>,
150}
151
152#[derive(Clone, Debug, Eq, PartialEq)]
153pub struct DataCapability {
155 pub payload_capability: u32,
156 pub direction: ReceiveTransmit,
157 pub protocol_dependent_data: u32,
159 pub max_bit_rate: u32,
160 pub encryption_capability: Option<EncryptionCapability>,
162}
163
164#[derive(Clone, Debug, Default, Eq, PartialEq)]
169pub struct StationMediaCapabilities {
170 audio: Arc<[MediaCapability]>,
171 video: Arc<[VideoCapability]>,
172}
173
174impl StationMediaCapabilities {
175 pub fn new(audio: Vec<MediaCapability>, video: Vec<VideoCapability>) -> Self {
177 Self {
178 audio: audio.into(),
179 video: video.into(),
180 }
181 }
182
183 pub fn audio(&self) -> &[MediaCapability] {
184 &self.audio
185 }
186
187 pub fn video(&self) -> &[VideoCapability] {
188 &self.video
189 }
190
191 pub fn is_empty(&self) -> bool {
192 self.audio.is_empty() && self.video.is_empty()
193 }
194}
195
196impl From<Vec<MediaCapability>> for StationMediaCapabilities {
197 fn from(audio: Vec<MediaCapability>) -> Self {
198 Self::new(audio, Vec::new())
199 }
200}
201
202#[derive(Clone, Debug, Eq, PartialEq)]
205pub struct CapabilityUpdate {
206 variant: CapabilityUpdateVariant,
207 rtp_payload_format: u32,
208 custom_picture_formats: Vec<CustomPictureFormat>,
209 conference: ConferenceResource,
210 audio: Vec<MediaCapability>,
211 video: Vec<VideoCapability>,
212 data: Vec<DataCapability>,
213 trailing_words: Vec<u32>,
214 raw_payload: Vec<u8>,
215}
216
217impl CapabilityUpdate {
218 pub fn into_media_capabilities(self) -> StationMediaCapabilities {
223 StationMediaCapabilities::new(self.audio, self.video)
224 }
225
226 pub const fn variant(&self) -> CapabilityUpdateVariant {
227 self.variant
228 }
229
230 pub const fn rtp_payload_format(&self) -> u32 {
231 self.rtp_payload_format
232 }
233
234 pub fn custom_picture_formats(&self) -> &[CustomPictureFormat] {
235 &self.custom_picture_formats
236 }
237
238 pub const fn conference(&self) -> &ConferenceResource {
239 &self.conference
240 }
241
242 pub fn audio(&self) -> &[MediaCapability] {
243 &self.audio
244 }
245
246 pub fn video(&self) -> &[VideoCapability] {
247 &self.video
248 }
249
250 pub fn data(&self) -> &[DataCapability] {
251 &self.data
252 }
253
254 pub fn trailing_words(&self) -> &[u32] {
256 &self.trailing_words
257 }
258
259 pub(crate) fn raw_payload(&self) -> &[u8] {
260 &self.raw_payload
261 }
262
263 pub(crate) fn decode(
264 variant: CapabilityUpdateVariant,
265 protocol: u32,
266 payload: &[u8],
267 ) -> Result<Self, CodecError> {
268 let message_id = variant.message_id();
269 let video_size = variant.video_entry_size(protocol);
270 let data_offset = VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * video_size;
271 let trailing_offset = data_offset + MAX_DATA_CAPABILITIES * variant.data_entry_size();
272 let required = if matches!(variant, CapabilityUpdateVariant::Version3) {
273 20
274 } else {
275 variant.minimum_payload_bytes(protocol)
276 };
277 require_len(payload, required, message_id)?;
278 let maximum = variant.maximum_payload_bytes(protocol);
279 if payload.len() > maximum {
280 return Err(CodecError::TrailingBytes {
281 message_id,
282 count: payload.len() - maximum,
283 });
284 }
285 let cursor = CapabilityCursor::new(payload, message_id);
286
287 let audio_count = bounded_wire_count(
288 cursor.word(0)?,
289 MAX_AUDIO_CAPABILITIES,
290 "audio capabilities",
291 message_id,
292 )?;
293 let video_count = bounded_wire_count(
294 cursor.word(4)?,
295 MAX_VIDEO_CAPABILITIES,
296 "video capabilities",
297 message_id,
298 )?;
299 let data_count = bounded_wire_count(
300 cursor.word(8)?,
301 MAX_DATA_CAPABILITIES,
302 "data capabilities",
303 message_id,
304 )?;
305 let rtp_payload_format = cursor.word(12)?;
306 let picture_count = bounded_wire_count(
307 cursor.word(16)?,
308 MAX_CUSTOM_PICTURES,
309 "custom picture formats",
310 message_id,
311 )?;
312
313 require_declared_entries(
314 payload,
315 CUSTOM_PICTURES_OFFSET,
316 picture_count,
317 CUSTOM_PICTURE_SIZE,
318 message_id,
319 )?;
320 require_declared_entries(
321 payload,
322 AUDIO_OFFSET,
323 audio_count,
324 AUDIO_CAPABILITY_SIZE,
325 message_id,
326 )?;
327 require_declared_entries(payload, VIDEO_OFFSET, video_count, video_size, message_id)?;
328 require_declared_entries(
329 payload,
330 data_offset,
331 data_count,
332 variant.data_entry_size(),
333 message_id,
334 )?;
335
336 let mut custom_picture_formats = Vec::with_capacity(picture_count);
337 for index in 0..picture_count {
338 let offset = CUSTOM_PICTURES_OFFSET + index * CUSTOM_PICTURE_SIZE;
339 custom_picture_formats.push(decode_picture_entry(&cursor, offset)?);
340 }
341
342 let service_count = match cursor.optional_word(CONFERENCE_OFFSET + 8) {
343 Some(count) => bounded_wire_count(
344 count,
345 MAX_CONFERENCE_SERVICES,
346 "conference services",
347 message_id,
348 )?,
349 None => 0,
350 };
351 let mut services = Vec::with_capacity(service_count);
352 require_declared_entries(
353 payload,
354 CONFERENCE_OFFSET + 12,
355 service_count,
356 CONFERENCE_SERVICE_SIZE,
357 message_id,
358 )?;
359 for index in 0..service_count {
360 let offset = CONFERENCE_OFFSET + 12 + index * CONFERENCE_SERVICE_SIZE;
361 services.push(decode_conference_service_entry(&cursor, offset)?);
362 }
363 let conference = ConferenceResource {
364 active_streams_on_registration: cursor.optional_word(CONFERENCE_OFFSET).unwrap_or(0),
365 max_bandwidth: cursor.optional_word(CONFERENCE_OFFSET + 4).unwrap_or(0),
366 services,
367 };
368
369 let mut audio = Vec::with_capacity(audio_count);
370 for index in 0..audio_count {
371 let offset = AUDIO_OFFSET + index * AUDIO_CAPABILITY_SIZE;
372 audio.push(decode_audio_entry(&cursor, offset)?);
373 }
374
375 let mut video = Vec::with_capacity(video_count);
376 for index in 0..video_count {
377 let offset = VIDEO_OFFSET + index * video_size;
378 video.push(decode_video_entry(&cursor, offset, variant, protocol)?);
379 }
380
381 let mut data = Vec::with_capacity(data_count);
382 for index in 0..data_count {
383 let offset = data_offset + index * variant.data_entry_size();
384 data.push(decode_data_entry(&cursor, offset, variant)?);
385 }
386
387 let trailing = payload.get(trailing_offset..).unwrap_or_default();
388 let trailing_words = (0..trailing.len() / 4)
389 .map(|index| {
390 let offset = index * 4;
391 u32::from_le_bytes([
392 trailing[offset],
393 trailing[offset + 1],
394 trailing[offset + 2],
395 trailing[offset + 3],
396 ])
397 })
398 .collect();
399
400 Ok(Self {
401 variant,
402 rtp_payload_format,
403 custom_picture_formats,
404 conference,
405 audio,
406 video,
407 data,
408 trailing_words,
409 raw_payload: payload.to_vec(),
410 })
411 }
412}
413
414struct CapabilityCursor<'a> {
417 payload: &'a [u8],
418 message_id: u32,
419}
420
421impl<'a> CapabilityCursor<'a> {
422 const fn new(payload: &'a [u8], message_id: u32) -> Self {
423 Self {
424 payload,
425 message_id,
426 }
427 }
428
429 fn bytes(&self, offset: usize, length: usize) -> Result<&'a [u8], CodecError> {
430 let end = offset.checked_add(length).ok_or(CodecError::Truncated {
431 message_id: self.message_id,
432 needed: usize::MAX,
433 actual: self.payload.len(),
434 })?;
435 require_len(self.payload, end, self.message_id)?;
436 Ok(&self.payload[offset..end])
437 }
438
439 fn word(&self, offset: usize) -> Result<u32, CodecError> {
440 Ok(u32::from_le_bytes(
441 self.bytes(offset, 4)?
442 .try_into()
443 .expect("bounded capability word"),
444 ))
445 }
446
447 fn optional_word(&self, offset: usize) -> Option<u32> {
448 self.payload
449 .get(offset..offset + 4)
450 .and_then(|bytes| bytes.try_into().ok())
451 .map(u32::from_le_bytes)
452 }
453}
454
455fn decode_picture_entry(
456 cursor: &CapabilityCursor<'_>,
457 offset: usize,
458) -> Result<CustomPictureFormat, CodecError> {
459 Ok(CustomPictureFormat {
460 width: cursor.word(offset)?,
461 height: cursor.word(offset + 4)?,
462 pixel_aspect_ratio: cursor.word(offset + 8)?,
463 pixel_clock_conversion: cursor.word(offset + 12)?,
464 pixel_clock_divisor: cursor.word(offset + 16)?,
465 })
466}
467
468fn decode_conference_service_entry(
469 cursor: &CapabilityCursor<'_>,
470 offset: usize,
471) -> Result<ConferenceServiceResource, CodecError> {
472 let layout_count = bounded_wire_count(
473 cursor.word(offset)?,
474 MAX_SERVICE_LAYOUTS,
475 "conference service layouts",
476 cursor.message_id,
477 )?;
478 let layouts = (0..layout_count)
479 .map(|layout| cursor.word(offset + 4 + layout * 4))
480 .collect::<Result<_, _>>()?;
481 Ok(ConferenceServiceResource {
482 layouts,
483 service_number: cursor.word(offset + 24)?,
484 max_streams: cursor.word(offset + 28)?,
485 max_conferences: cursor.word(offset + 32)?,
486 active_conference_on_registration: cursor.word(offset + 36)?,
487 })
488}
489
490fn decode_audio_entry(
491 cursor: &CapabilityCursor<'_>,
492 offset: usize,
493) -> Result<MediaCapability, CodecError> {
494 Ok(MediaCapability {
495 codec: Codec::from(cursor.word(offset)?),
496 max_frames_per_packet: cursor.word(offset + 4)?,
497 codec_parameters: cursor
498 .bytes(offset + 8, 8)?
499 .try_into()
500 .expect("bounded audio capability parameters"),
501 })
502}
503
504fn decode_video_entry(
505 cursor: &CapabilityCursor<'_>,
506 offset: usize,
507 variant: CapabilityUpdateVariant,
508 protocol: u32,
509) -> Result<VideoCapability, CodecError> {
510 let level_count = bounded_wire_count(
511 cursor.word(offset + 8)?,
512 MAX_LEVEL_PREFERENCES,
513 "video level preferences",
514 cursor.message_id,
515 )?;
516 let level_preferences = (0..level_count)
517 .map(|level| {
518 let level_offset = offset + 12 + level * 24;
519 Ok(VideoLevelPreference {
520 transmit_preference: cursor.word(level_offset)?,
521 format: cursor.word(level_offset + 4)?,
522 max_bit_rate: cursor.word(level_offset + 8)?,
523 min_bit_rate: cursor.word(level_offset + 12)?,
524 minimum_picture_interval: cursor.word(level_offset + 16)?,
525 service_number: cursor.word(level_offset + 20)?,
526 })
527 })
528 .collect::<Result<_, CodecError>>()?;
529 let parameters_offset =
530 offset + 108 + usize::from(variant == CapabilityUpdateVariant::Version3) * 4;
531 let codec_parameters = (0..variant.codec_parameter_words())
532 .map(|parameter| cursor.word(parameters_offset + parameter * 4))
533 .collect::<Result<_, _>>()?;
534 Ok(VideoCapability {
535 codec: Codec::from(cursor.word(offset)?),
536 direction: ReceiveTransmit::from_bits_retain(cursor.word(offset + 4)?),
537 level_preferences,
538 codec_parameters,
539 encryption_capability: (variant == CapabilityUpdateVariant::Version3)
540 .then(|| cursor.word(offset + 108).map(EncryptionCapability::from))
541 .transpose()?,
542 address_type: (variant == CapabilityUpdateVariant::Version3 && protocol >= 17)
543 .then(|| cursor.word(offset + 136).map(IpAddressType::from))
544 .transpose()?,
545 })
546}
547
548fn decode_data_entry(
549 cursor: &CapabilityCursor<'_>,
550 offset: usize,
551 variant: CapabilityUpdateVariant,
552) -> Result<DataCapability, CodecError> {
553 Ok(DataCapability {
554 payload_capability: cursor.word(offset)?,
555 direction: ReceiveTransmit::from_bits_retain(cursor.word(offset + 4)?),
556 protocol_dependent_data: cursor.word(offset + 8)?,
557 max_bit_rate: cursor.word(offset + 12)?,
558 encryption_capability: (variant == CapabilityUpdateVariant::Version3)
559 .then(|| cursor.word(offset + 16).map(EncryptionCapability::from))
560 .transpose()?,
561 })
562}
563
564fn bounded_wire_count(
565 count: u32,
566 maximum: usize,
567 field: &'static str,
568 message_id: u32,
569) -> Result<usize, CodecError> {
570 let count = usize::try_from(count).map_err(|_| CodecError::InvalidValue {
571 message_id,
572 field,
573 value: u64::from(count),
574 })?;
575 if count > maximum {
576 Err(CodecError::CountTooLarge {
577 message_id,
578 field,
579 count,
580 maximum,
581 })
582 } else {
583 Ok(count)
584 }
585}
586
587fn require_len(payload: &[u8], needed: usize, message_id: u32) -> Result<(), CodecError> {
588 if payload.len() < needed {
589 Err(CodecError::Truncated {
590 message_id,
591 needed,
592 actual: payload.len(),
593 })
594 } else {
595 Ok(())
596 }
597}
598
599fn require_declared_entries(
600 payload: &[u8],
601 offset: usize,
602 count: usize,
603 entry_size: usize,
604 message_id: u32,
605) -> Result<(), CodecError> {
606 if count == 0 {
607 Ok(())
608 } else {
609 require_len(payload, offset + count * entry_size, message_id)
610 }
611}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616 use crate::message::ClientMessage;
617 use crate::message::values::ProtocolVersion;
618 use crate::message::wire::{Frame, FrameDecoder};
619
620 fn put(payload: &mut [u8], offset: usize, value: u32) {
621 payload[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
622 }
623
624 fn fixture(source: &str) -> Vec<u8> {
625 source
626 .split_whitespace()
627 .map(|byte| u8::from_str_radix(byte, 16).expect("valid fixture byte"))
628 .collect()
629 }
630
631 fn assert_declared_table_boundaries(
632 protocol: u32,
633 count_offset: usize,
634 table_offset: usize,
635 entry_size: usize,
636 maximum: usize,
637 decoded_count: impl Fn(&CapabilityUpdate) -> usize,
638 ) {
639 for count in 1..=maximum {
640 let needed = table_offset + count * entry_size;
641 let mut truncated = vec![0; needed - 1];
642 put(&mut truncated, count_offset, count as u32);
643 assert!(matches!(
644 CapabilityUpdate::decode(
645 CapabilityUpdateVariant::Version3,
646 protocol,
647 &truncated
648 ),
649 Err(CodecError::Truncated {
650 needed: actual_needed,
651 actual,
652 ..
653 }) if actual_needed == needed && actual == needed - 1
654 ));
655
656 let mut complete = vec![0; needed];
657 put(&mut complete, count_offset, count as u32);
658 let update =
659 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, protocol, &complete)
660 .unwrap();
661 assert_eq!(decoded_count(&update), count);
662 assert_eq!(update.raw_payload(), complete);
663 }
664 }
665
666 #[test]
667 fn version_three_update_exposes_every_capability_family() {
668 let mut payload = vec![0; 2_380];
669 put(&mut payload, 0, 1);
670 put(&mut payload, 4, 1);
671 put(&mut payload, 8, 1);
672 put(&mut payload, 12, 101);
673 put(&mut payload, 16, 1);
674 for (index, value) in [640, 480, 1, 2, 3].into_iter().enumerate() {
675 put(&mut payload, CUSTOM_PICTURES_OFFSET + index * 4, value);
676 }
677 put(&mut payload, CONFERENCE_OFFSET, 1);
678 put(&mut payload, CONFERENCE_OFFSET + 4, 2_048);
679 put(&mut payload, CONFERENCE_OFFSET + 8, 1);
680 put(&mut payload, CONFERENCE_OFFSET + 12, 1);
681 put(&mut payload, CONFERENCE_OFFSET + 16, 7);
682 put(&mut payload, CONFERENCE_OFFSET + 36, 9);
683 put(&mut payload, CONFERENCE_OFFSET + 40, 2);
684 put(&mut payload, CONFERENCE_OFFSET + 44, 1);
685 put(&mut payload, CONFERENCE_OFFSET + 48, 0);
686 put(&mut payload, AUDIO_OFFSET, Codec::Pcmu.wire_value());
687 put(&mut payload, AUDIO_OFFSET + 4, 4);
688 payload[AUDIO_OFFSET + 8..AUDIO_OFFSET + 16].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
689 put(&mut payload, VIDEO_OFFSET, Codec::H264.wire_value());
690 put(&mut payload, VIDEO_OFFSET + 4, 3);
691 put(&mut payload, VIDEO_OFFSET + 8, 1);
692 for (index, value) in [1, 5, 4_000, 128, 2, 7].into_iter().enumerate() {
693 put(&mut payload, VIDEO_OFFSET + 12 + index * 4, value);
694 }
695 put(&mut payload, VIDEO_OFFSET + 108, 1);
696 for (index, value) in [66, 31, 120, 240, 360, 480].into_iter().enumerate() {
697 put(&mut payload, VIDEO_OFFSET + 112 + index * 4, value);
698 }
699 put(&mut payload, VIDEO_OFFSET + 136, 2);
700 let data_offset = VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * 140;
701 for (index, value) in [0x120, 3, 8, 64_000, 1].into_iter().enumerate() {
702 put(&mut payload, data_offset + index * 4, value);
703 }
704 put(
705 &mut payload,
706 data_offset + MAX_DATA_CAPABILITIES * 20,
707 0xfeed_beef,
708 );
709
710 let update =
711 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload).unwrap();
712 assert_eq!(update.rtp_payload_format(), 101);
713 assert_eq!(update.custom_picture_formats()[0].width, 640);
714 assert_eq!(update.conference().services[0].layouts, [7]);
715 assert_eq!(update.audio()[0].codec, Codec::Pcmu);
716 assert_eq!(update.audio()[0].codec_parameters, [1, 2, 3, 4, 5, 6, 7, 8]);
717 assert_eq!(update.video()[0].codec, Codec::H264);
718 assert_eq!(
719 update.video()[0].direction,
720 ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT
721 );
722 assert_eq!(
723 update.video()[0].encryption_capability,
724 Some(EncryptionCapability::Capable)
725 );
726 assert_eq!(
727 update.video()[0].address_type,
728 Some(IpAddressType::Ipv4AndIpv6)
729 );
730 assert_eq!(update.data()[0].max_bit_rate, 64_000);
731 assert_eq!(
732 update.data()[0].encryption_capability,
733 Some(EncryptionCapability::Capable)
734 );
735 assert_eq!(update.trailing_words()[0], 0xfeed_beef);
736 assert_eq!(update.raw_payload(), payload);
737
738 let expected_audio = update.audio().to_vec();
739 let expected_video = update.video().to_vec();
740 let media = update.into_media_capabilities();
741 assert_eq!(media.audio(), expected_audio);
742 assert_eq!(media.video(), expected_video);
743 }
744
745 #[test]
746 fn version_three_accepts_every_bounded_progressive_length() {
747 for size in 20..=2_380 {
748 let payload = vec![0; size];
749 let update =
750 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload).unwrap();
751 assert_eq!(update.raw_payload(), payload, "payload size {size}");
752 }
753
754 assert!(matches!(
755 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &[0; 19]),
756 Err(CodecError::Truncated { .. })
757 ));
758 assert!(matches!(
759 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &[0; 2_381]),
760 Err(CodecError::TrailingBytes { .. })
761 ));
762 assert!(
763 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 16, &[0; 2_380]).is_ok()
764 );
765 assert!(matches!(
766 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 16, &[0; 2_381]),
767 Err(CodecError::TrailingBytes { .. })
768 ));
769 }
770
771 #[test]
772 fn version_three_rejects_declared_tables_that_do_not_fit() {
773 for (count_offset, needed) in [
774 (0, AUDIO_OFFSET + AUDIO_CAPABILITY_SIZE),
775 (4, VIDEO_OFFSET + 140),
776 (8, VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * 140 + 20),
777 (16, CUSTOM_PICTURES_OFFSET + CUSTOM_PICTURE_SIZE),
778 ] {
779 let mut payload = vec![0; 20];
780 put(&mut payload, count_offset, 1);
781 assert!(matches!(
782 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload),
783 Err(CodecError::Truncated {
784 needed: actual_needed,
785 actual: 20,
786 ..
787 }) if actual_needed == needed
788 ));
789 }
790
791 assert_declared_table_boundaries(
792 22,
793 16,
794 CUSTOM_PICTURES_OFFSET,
795 CUSTOM_PICTURE_SIZE,
796 MAX_CUSTOM_PICTURES,
797 |update| update.custom_picture_formats().len(),
798 );
799 assert_declared_table_boundaries(
800 22,
801 0,
802 AUDIO_OFFSET,
803 AUDIO_CAPABILITY_SIZE,
804 MAX_AUDIO_CAPABILITIES,
805 |update| update.audio().len(),
806 );
807 for (protocol, video_size) in [(16, 136), (17, 140)] {
808 assert_declared_table_boundaries(
809 protocol,
810 4,
811 VIDEO_OFFSET,
812 video_size,
813 MAX_VIDEO_CAPABILITIES,
814 |update| update.video().len(),
815 );
816 assert_declared_table_boundaries(
817 protocol,
818 8,
819 VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * video_size,
820 20,
821 MAX_DATA_CAPABILITIES,
822 |update| update.data().len(),
823 );
824 }
825 }
826
827 #[test]
828 fn version_three_rejects_declared_conference_services_that_do_not_fit() {
829 for count in 1..=MAX_CONFERENCE_SERVICES {
830 let needed = CONFERENCE_OFFSET + 12 + count * CONFERENCE_SERVICE_SIZE;
831 let mut truncated = vec![0; needed - 1];
832 put(&mut truncated, CONFERENCE_OFFSET + 8, count as u32);
833 assert!(matches!(
834 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &truncated),
835 Err(CodecError::Truncated {
836 needed: actual_needed,
837 actual,
838 ..
839 }) if actual_needed == needed && actual == needed - 1
840 ));
841
842 let mut complete = vec![0; needed];
843 put(&mut complete, CONFERENCE_OFFSET + 8, count as u32);
844 let update =
845 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &complete).unwrap();
846 assert_eq!(update.conference().services.len(), count);
847 }
848 }
849
850 #[test]
851 fn version_three_preserves_an_unstructured_suffix() {
852 let trailing_offset =
853 VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * 140 + MAX_DATA_CAPABILITIES * 20;
854 let mut payload = vec![0; trailing_offset + 7];
855 put(&mut payload, trailing_offset, 0xfeed_beef);
856 payload[trailing_offset + 4..].copy_from_slice(&[0xaa, 0xbb, 0xcc]);
857
858 let update =
859 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload).unwrap();
860 assert_eq!(update.trailing_words(), [0xfeed_beef]);
861 assert_eq!(update.raw_payload(), payload);
862
863 let encoded = ClientMessage::CapabilitiesUpdate(update)
864 .encode(ProtocolVersion::V22)
865 .unwrap();
866 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
867 assert_eq!(frame.payload, payload);
868 }
869
870 #[test]
871 fn version_three_video_entry_boundary_depends_on_protocol() {
872 let mut before = vec![0; 2_060];
873 put(&mut before, 4, 1);
874 put(&mut before, VIDEO_OFFSET, Codec::H264.wire_value());
875 let before =
876 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 16, &before).unwrap();
877 assert_eq!(before.video().len(), 1);
878 assert_eq!(before.video()[0].address_type, None);
879
880 let mut from = vec![0; 2_100];
881 put(&mut from, 4, 1);
882 put(&mut from, VIDEO_OFFSET, Codec::H264.wire_value());
883 put(&mut from, VIDEO_OFFSET + 136, 2);
884 let from = CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 17, &from).unwrap();
885 assert_eq!(from.video().len(), 1);
886 assert_eq!(
887 from.video()[0].address_type,
888 Some(IpAddressType::Ipv4AndIpv6)
889 );
890 }
891
892 #[test]
893 fn update_rejects_truncation_and_every_oversized_count() {
894 assert!(matches!(
895 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &[0; 100]),
896 Err(CodecError::Truncated { .. })
897 ));
898
899 for (offset, count) in [(0, 19), (4, 11), (8, 6), (16, 7)] {
900 let mut payload = vec![0; 1_840];
901 put(&mut payload, offset, count);
902 assert!(matches!(
903 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &payload),
904 Err(CodecError::CountTooLarge { .. })
905 ));
906 }
907
908 let mut services = vec![0; 1_840];
909 put(&mut services, CONFERENCE_OFFSET + 8, 5);
910 assert!(matches!(
911 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &services),
912 Err(CodecError::CountTooLarge { .. })
913 ));
914
915 let mut layouts = vec![0; 1_840];
916 put(&mut layouts, CONFERENCE_OFFSET + 8, 1);
917 put(&mut layouts, CONFERENCE_OFFSET + 12, 6);
918 assert!(matches!(
919 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &layouts),
920 Err(CodecError::CountTooLarge { .. })
921 ));
922
923 let mut levels = vec![0; 1_840];
924 put(&mut levels, 4, 1);
925 put(&mut levels, VIDEO_OFFSET + 8, 5);
926 assert!(matches!(
927 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &levels),
928 Err(CodecError::CountTooLarge { .. })
929 ));
930 }
931
932 #[test]
933 fn every_update_variant_round_trips_its_original_fixed_layout() {
934 for (variant, protocol, size) in [
935 (
936 CapabilityUpdateVariant::Version1,
937 ProtocolVersion::V3,
938 1_840,
939 ),
940 (
941 CapabilityUpdateVariant::Version1ExpandedVideo,
942 ProtocolVersion::V16,
943 2_000,
944 ),
945 (
946 CapabilityUpdateVariant::Version2,
947 ProtocolVersion::V22,
948 2_000,
949 ),
950 (
951 CapabilityUpdateVariant::Version3,
952 ProtocolVersion::V22,
953 2_380,
954 ),
955 ] {
956 let payload = vec![0; size];
957 let decoded = ClientMessage::decode_with_version(
958 Frame::new(protocol.wire(), variant.message_id(), payload.clone()),
959 protocol,
960 )
961 .unwrap();
962 assert!(matches!(
963 decoded,
964 ClientMessage::CapabilitiesUpdate(ref update) if update.variant() == variant
965 ));
966 let encoded = decoded.encode(protocol).unwrap();
967 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
968 assert_eq!(frame.message_id, variant.message_id());
969 assert_eq!(frame.payload, payload);
970 }
971 }
972
973 #[test]
974 fn v22_7961_legacy_body_size_overrides_the_modern_session_protocol() {
975 let bytes = fixture(include_str!(
976 "../../tests/fixtures/golden/update_capabilities_7961_v22_legacy.hex"
977 ));
978 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
979 assert_eq!(frame.protocol_version, ProtocolVersion::V22.wire());
980 assert_eq!(
981 frame.message_id,
982 CapabilityUpdateVariant::Version1.message_id()
983 );
984 assert_eq!(frame.payload.len(), 1_840);
985 let decoded = ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap();
986
987 assert!(matches!(
988 decoded,
989 ClientMessage::CapabilitiesUpdate(ref update)
990 if update.variant() == CapabilityUpdateVariant::Version1
991 ));
992 let encoded = decoded.encode(ProtocolVersion::V22).unwrap();
993 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
994 assert_eq!(frame.protocol_version, ProtocolVersion::V22.wire());
995 assert_eq!(frame.payload.len(), 1_840);
996 assert_eq!(encoded, bytes);
997 }
998}