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
286 let audio_count = bounded_wire_count(
287 word(payload, 0, message_id)?,
288 MAX_AUDIO_CAPABILITIES,
289 "audio capabilities",
290 message_id,
291 )?;
292 let video_count = bounded_wire_count(
293 word(payload, 4, message_id)?,
294 MAX_VIDEO_CAPABILITIES,
295 "video capabilities",
296 message_id,
297 )?;
298 let data_count = bounded_wire_count(
299 word(payload, 8, message_id)?,
300 MAX_DATA_CAPABILITIES,
301 "data capabilities",
302 message_id,
303 )?;
304 let rtp_payload_format = word(payload, 12, message_id)?;
305 let picture_count = bounded_wire_count(
306 word(payload, 16, message_id)?,
307 MAX_CUSTOM_PICTURES,
308 "custom picture formats",
309 message_id,
310 )?;
311
312 require_declared_entries(
313 payload,
314 CUSTOM_PICTURES_OFFSET,
315 picture_count,
316 CUSTOM_PICTURE_SIZE,
317 message_id,
318 )?;
319 require_declared_entries(
320 payload,
321 AUDIO_OFFSET,
322 audio_count,
323 AUDIO_CAPABILITY_SIZE,
324 message_id,
325 )?;
326 require_declared_entries(payload, VIDEO_OFFSET, video_count, video_size, message_id)?;
327 require_declared_entries(
328 payload,
329 data_offset,
330 data_count,
331 variant.data_entry_size(),
332 message_id,
333 )?;
334
335 let mut custom_picture_formats = Vec::with_capacity(picture_count);
336 for index in 0..picture_count {
337 let offset = CUSTOM_PICTURES_OFFSET + index * CUSTOM_PICTURE_SIZE;
338 custom_picture_formats.push(CustomPictureFormat {
339 width: word(payload, offset, message_id)?,
340 height: word(payload, offset + 4, message_id)?,
341 pixel_aspect_ratio: word(payload, offset + 8, message_id)?,
342 pixel_clock_conversion: word(payload, offset + 12, message_id)?,
343 pixel_clock_divisor: word(payload, offset + 16, message_id)?,
344 });
345 }
346
347 let service_count = match optional_word(payload, CONFERENCE_OFFSET + 8) {
348 Some(count) => bounded_wire_count(
349 count,
350 MAX_CONFERENCE_SERVICES,
351 "conference services",
352 message_id,
353 )?,
354 None => 0,
355 };
356 let mut services = Vec::with_capacity(service_count);
357 require_declared_entries(
358 payload,
359 CONFERENCE_OFFSET + 12,
360 service_count,
361 CONFERENCE_SERVICE_SIZE,
362 message_id,
363 )?;
364 for index in 0..service_count {
365 let offset = CONFERENCE_OFFSET + 12 + index * CONFERENCE_SERVICE_SIZE;
366 let layout_count = bounded_wire_count(
367 word(payload, offset, message_id)?,
368 MAX_SERVICE_LAYOUTS,
369 "conference service layouts",
370 message_id,
371 )?;
372 let mut layouts = Vec::with_capacity(layout_count);
373 for layout in 0..layout_count {
374 layouts.push(word(payload, offset + 4 + layout * 4, message_id)?);
375 }
376 services.push(ConferenceServiceResource {
377 layouts,
378 service_number: word(payload, offset + 24, message_id)?,
379 max_streams: word(payload, offset + 28, message_id)?,
380 max_conferences: word(payload, offset + 32, message_id)?,
381 active_conference_on_registration: word(payload, offset + 36, message_id)?,
382 });
383 }
384 let conference = ConferenceResource {
385 active_streams_on_registration: optional_word(payload, CONFERENCE_OFFSET).unwrap_or(0),
386 max_bandwidth: optional_word(payload, CONFERENCE_OFFSET + 4).unwrap_or(0),
387 services,
388 };
389
390 let mut audio = Vec::with_capacity(audio_count);
391 for index in 0..audio_count {
392 let offset = AUDIO_OFFSET + index * AUDIO_CAPABILITY_SIZE;
393 audio.push(MediaCapability {
394 codec: Codec::from(word(payload, offset, message_id)?),
395 max_frames_per_packet: word(payload, offset + 4, message_id)?,
396 codec_parameters: payload[offset + 8..offset + 16]
397 .try_into()
398 .expect("validated fixed audio capability bounds"),
399 });
400 }
401
402 let mut video = Vec::with_capacity(video_count);
403 for index in 0..video_count {
404 let offset = VIDEO_OFFSET + index * video_size;
405 let level_count = bounded_wire_count(
406 word(payload, offset + 8, message_id)?,
407 MAX_LEVEL_PREFERENCES,
408 "video level preferences",
409 message_id,
410 )?;
411 let mut level_preferences = Vec::with_capacity(level_count);
412 for level in 0..level_count {
413 let level_offset = offset + 12 + level * 24;
414 level_preferences.push(VideoLevelPreference {
415 transmit_preference: word(payload, level_offset, message_id)?,
416 format: word(payload, level_offset + 4, message_id)?,
417 max_bit_rate: word(payload, level_offset + 8, message_id)?,
418 min_bit_rate: word(payload, level_offset + 12, message_id)?,
419 minimum_picture_interval: word(payload, level_offset + 16, message_id)?,
420 service_number: word(payload, level_offset + 20, message_id)?,
421 });
422 }
423 let parameters_offset =
424 offset + 108 + usize::from(variant == CapabilityUpdateVariant::Version3) * 4;
425 let codec_parameters = (0..variant.codec_parameter_words())
426 .map(|parameter| word(payload, parameters_offset + parameter * 4, message_id))
427 .collect::<Result<_, _>>()?;
428 video.push(VideoCapability {
429 codec: Codec::from(word(payload, offset, message_id)?),
430 direction: ReceiveTransmit::from_bits_retain(word(
431 payload,
432 offset + 4,
433 message_id,
434 )?),
435 level_preferences,
436 codec_parameters,
437 encryption_capability: (variant == CapabilityUpdateVariant::Version3)
438 .then(|| {
439 word(payload, offset + 108, message_id).map(EncryptionCapability::from)
440 })
441 .transpose()?,
442 address_type: (variant == CapabilityUpdateVariant::Version3 && protocol >= 17)
443 .then(|| word(payload, offset + 136, message_id).map(IpAddressType::from))
444 .transpose()?,
445 });
446 }
447
448 let mut data = Vec::with_capacity(data_count);
449 for index in 0..data_count {
450 let offset = data_offset + index * variant.data_entry_size();
451 data.push(DataCapability {
452 payload_capability: word(payload, offset, message_id)?,
453 direction: ReceiveTransmit::from_bits_retain(word(
454 payload,
455 offset + 4,
456 message_id,
457 )?),
458 protocol_dependent_data: word(payload, offset + 8, message_id)?,
459 max_bit_rate: word(payload, offset + 12, message_id)?,
460 encryption_capability: (variant == CapabilityUpdateVariant::Version3)
461 .then(|| word(payload, offset + 16, message_id).map(EncryptionCapability::from))
462 .transpose()?,
463 });
464 }
465
466 let trailing = payload.get(trailing_offset..).unwrap_or_default();
467 let trailing_words = (0..trailing.len() / 4)
468 .map(|index| {
469 let offset = index * 4;
470 u32::from_le_bytes([
471 trailing[offset],
472 trailing[offset + 1],
473 trailing[offset + 2],
474 trailing[offset + 3],
475 ])
476 })
477 .collect();
478
479 Ok(Self {
480 variant,
481 rtp_payload_format,
482 custom_picture_formats,
483 conference,
484 audio,
485 video,
486 data,
487 trailing_words,
488 raw_payload: payload.to_vec(),
489 })
490 }
491}
492
493fn bounded_wire_count(
494 count: u32,
495 maximum: usize,
496 field: &'static str,
497 message_id: u32,
498) -> Result<usize, CodecError> {
499 let count = usize::try_from(count).map_err(|_| CodecError::InvalidValue {
500 message_id,
501 field,
502 value: u64::from(count),
503 })?;
504 if count > maximum {
505 Err(CodecError::CountTooLarge {
506 message_id,
507 field,
508 count,
509 maximum,
510 })
511 } else {
512 Ok(count)
513 }
514}
515
516fn require_len(payload: &[u8], needed: usize, message_id: u32) -> Result<(), CodecError> {
517 if payload.len() < needed {
518 Err(CodecError::Truncated {
519 message_id,
520 needed,
521 actual: payload.len(),
522 })
523 } else {
524 Ok(())
525 }
526}
527
528fn require_declared_entries(
529 payload: &[u8],
530 offset: usize,
531 count: usize,
532 entry_size: usize,
533 message_id: u32,
534) -> Result<(), CodecError> {
535 if count == 0 {
536 Ok(())
537 } else {
538 require_len(payload, offset + count * entry_size, message_id)
539 }
540}
541
542fn word(payload: &[u8], offset: usize, message_id: u32) -> Result<u32, CodecError> {
543 require_len(payload, offset + 4, message_id)?;
544 Ok(u32::from_le_bytes(
545 payload[offset..offset + 4]
546 .try_into()
547 .expect("validated word bounds"),
548 ))
549}
550
551fn optional_word(payload: &[u8], offset: usize) -> Option<u32> {
552 payload
553 .get(offset..offset + 4)
554 .and_then(|bytes| bytes.try_into().ok())
555 .map(u32::from_le_bytes)
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use crate::message::ClientMessage;
562 use crate::message::values::ProtocolVersion;
563 use crate::message::wire::{Frame, FrameDecoder};
564
565 fn put(payload: &mut [u8], offset: usize, value: u32) {
566 payload[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
567 }
568
569 fn fixture(source: &str) -> Vec<u8> {
570 source
571 .split_whitespace()
572 .map(|byte| u8::from_str_radix(byte, 16).expect("valid fixture byte"))
573 .collect()
574 }
575
576 fn assert_declared_table_boundaries(
577 protocol: u32,
578 count_offset: usize,
579 table_offset: usize,
580 entry_size: usize,
581 maximum: usize,
582 decoded_count: impl Fn(&CapabilityUpdate) -> usize,
583 ) {
584 for count in 1..=maximum {
585 let needed = table_offset + count * entry_size;
586 let mut truncated = vec![0; needed - 1];
587 put(&mut truncated, count_offset, count as u32);
588 assert!(matches!(
589 CapabilityUpdate::decode(
590 CapabilityUpdateVariant::Version3,
591 protocol,
592 &truncated
593 ),
594 Err(CodecError::Truncated {
595 needed: actual_needed,
596 actual,
597 ..
598 }) if actual_needed == needed && actual == needed - 1
599 ));
600
601 let mut complete = vec![0; needed];
602 put(&mut complete, count_offset, count as u32);
603 let update =
604 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, protocol, &complete)
605 .unwrap();
606 assert_eq!(decoded_count(&update), count);
607 assert_eq!(update.raw_payload(), complete);
608 }
609 }
610
611 #[test]
612 fn version_three_update_exposes_every_capability_family() {
613 let mut payload = vec![0; 2_380];
614 put(&mut payload, 0, 1);
615 put(&mut payload, 4, 1);
616 put(&mut payload, 8, 1);
617 put(&mut payload, 12, 101);
618 put(&mut payload, 16, 1);
619 for (index, value) in [640, 480, 1, 2, 3].into_iter().enumerate() {
620 put(&mut payload, CUSTOM_PICTURES_OFFSET + index * 4, value);
621 }
622 put(&mut payload, CONFERENCE_OFFSET, 1);
623 put(&mut payload, CONFERENCE_OFFSET + 4, 2_048);
624 put(&mut payload, CONFERENCE_OFFSET + 8, 1);
625 put(&mut payload, CONFERENCE_OFFSET + 12, 1);
626 put(&mut payload, CONFERENCE_OFFSET + 16, 7);
627 put(&mut payload, CONFERENCE_OFFSET + 36, 9);
628 put(&mut payload, CONFERENCE_OFFSET + 40, 2);
629 put(&mut payload, CONFERENCE_OFFSET + 44, 1);
630 put(&mut payload, CONFERENCE_OFFSET + 48, 0);
631 put(&mut payload, AUDIO_OFFSET, Codec::Pcmu.wire_value());
632 put(&mut payload, AUDIO_OFFSET + 4, 4);
633 payload[AUDIO_OFFSET + 8..AUDIO_OFFSET + 16].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
634 put(&mut payload, VIDEO_OFFSET, Codec::H264.wire_value());
635 put(&mut payload, VIDEO_OFFSET + 4, 3);
636 put(&mut payload, VIDEO_OFFSET + 8, 1);
637 for (index, value) in [1, 5, 4_000, 128, 2, 7].into_iter().enumerate() {
638 put(&mut payload, VIDEO_OFFSET + 12 + index * 4, value);
639 }
640 put(&mut payload, VIDEO_OFFSET + 108, 1);
641 for (index, value) in [66, 31, 120, 240, 360, 480].into_iter().enumerate() {
642 put(&mut payload, VIDEO_OFFSET + 112 + index * 4, value);
643 }
644 put(&mut payload, VIDEO_OFFSET + 136, 2);
645 let data_offset = VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * 140;
646 for (index, value) in [0x120, 3, 8, 64_000, 1].into_iter().enumerate() {
647 put(&mut payload, data_offset + index * 4, value);
648 }
649 put(
650 &mut payload,
651 data_offset + MAX_DATA_CAPABILITIES * 20,
652 0xfeed_beef,
653 );
654
655 let update =
656 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload).unwrap();
657 assert_eq!(update.rtp_payload_format(), 101);
658 assert_eq!(update.custom_picture_formats()[0].width, 640);
659 assert_eq!(update.conference().services[0].layouts, [7]);
660 assert_eq!(update.audio()[0].codec, Codec::Pcmu);
661 assert_eq!(update.audio()[0].codec_parameters, [1, 2, 3, 4, 5, 6, 7, 8]);
662 assert_eq!(update.video()[0].codec, Codec::H264);
663 assert_eq!(
664 update.video()[0].direction,
665 ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT
666 );
667 assert_eq!(
668 update.video()[0].encryption_capability,
669 Some(EncryptionCapability::Capable)
670 );
671 assert_eq!(
672 update.video()[0].address_type,
673 Some(IpAddressType::Ipv4AndIpv6)
674 );
675 assert_eq!(update.data()[0].max_bit_rate, 64_000);
676 assert_eq!(
677 update.data()[0].encryption_capability,
678 Some(EncryptionCapability::Capable)
679 );
680 assert_eq!(update.trailing_words()[0], 0xfeed_beef);
681 assert_eq!(update.raw_payload(), payload);
682
683 let expected_audio = update.audio().to_vec();
684 let expected_video = update.video().to_vec();
685 let media = update.into_media_capabilities();
686 assert_eq!(media.audio(), expected_audio);
687 assert_eq!(media.video(), expected_video);
688 }
689
690 #[test]
691 fn version_three_accepts_every_bounded_progressive_length() {
692 for size in 20..=2_380 {
693 let payload = vec![0; size];
694 let update =
695 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload).unwrap();
696 assert_eq!(update.raw_payload(), payload, "payload size {size}");
697 }
698
699 assert!(matches!(
700 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &[0; 19]),
701 Err(CodecError::Truncated { .. })
702 ));
703 assert!(matches!(
704 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &[0; 2_381]),
705 Err(CodecError::TrailingBytes { .. })
706 ));
707 assert!(
708 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 16, &[0; 2_380]).is_ok()
709 );
710 assert!(matches!(
711 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 16, &[0; 2_381]),
712 Err(CodecError::TrailingBytes { .. })
713 ));
714 }
715
716 #[test]
717 fn version_three_rejects_declared_tables_that_do_not_fit() {
718 for (count_offset, needed) in [
719 (0, AUDIO_OFFSET + AUDIO_CAPABILITY_SIZE),
720 (4, VIDEO_OFFSET + 140),
721 (8, VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * 140 + 20),
722 (16, CUSTOM_PICTURES_OFFSET + CUSTOM_PICTURE_SIZE),
723 ] {
724 let mut payload = vec![0; 20];
725 put(&mut payload, count_offset, 1);
726 assert!(matches!(
727 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload),
728 Err(CodecError::Truncated {
729 needed: actual_needed,
730 actual: 20,
731 ..
732 }) if actual_needed == needed
733 ));
734 }
735
736 assert_declared_table_boundaries(
737 22,
738 16,
739 CUSTOM_PICTURES_OFFSET,
740 CUSTOM_PICTURE_SIZE,
741 MAX_CUSTOM_PICTURES,
742 |update| update.custom_picture_formats().len(),
743 );
744 assert_declared_table_boundaries(
745 22,
746 0,
747 AUDIO_OFFSET,
748 AUDIO_CAPABILITY_SIZE,
749 MAX_AUDIO_CAPABILITIES,
750 |update| update.audio().len(),
751 );
752 for (protocol, video_size) in [(16, 136), (17, 140)] {
753 assert_declared_table_boundaries(
754 protocol,
755 4,
756 VIDEO_OFFSET,
757 video_size,
758 MAX_VIDEO_CAPABILITIES,
759 |update| update.video().len(),
760 );
761 assert_declared_table_boundaries(
762 protocol,
763 8,
764 VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * video_size,
765 20,
766 MAX_DATA_CAPABILITIES,
767 |update| update.data().len(),
768 );
769 }
770 }
771
772 #[test]
773 fn version_three_rejects_declared_conference_services_that_do_not_fit() {
774 for count in 1..=MAX_CONFERENCE_SERVICES {
775 let needed = CONFERENCE_OFFSET + 12 + count * CONFERENCE_SERVICE_SIZE;
776 let mut truncated = vec![0; needed - 1];
777 put(&mut truncated, CONFERENCE_OFFSET + 8, count as u32);
778 assert!(matches!(
779 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &truncated),
780 Err(CodecError::Truncated {
781 needed: actual_needed,
782 actual,
783 ..
784 }) if actual_needed == needed && actual == needed - 1
785 ));
786
787 let mut complete = vec![0; needed];
788 put(&mut complete, CONFERENCE_OFFSET + 8, count as u32);
789 let update =
790 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &complete).unwrap();
791 assert_eq!(update.conference().services.len(), count);
792 }
793 }
794
795 #[test]
796 fn version_three_preserves_an_unstructured_suffix() {
797 let trailing_offset =
798 VIDEO_OFFSET + MAX_VIDEO_CAPABILITIES * 140 + MAX_DATA_CAPABILITIES * 20;
799 let mut payload = vec![0; trailing_offset + 7];
800 put(&mut payload, trailing_offset, 0xfeed_beef);
801 payload[trailing_offset + 4..].copy_from_slice(&[0xaa, 0xbb, 0xcc]);
802
803 let update =
804 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 22, &payload).unwrap();
805 assert_eq!(update.trailing_words(), [0xfeed_beef]);
806 assert_eq!(update.raw_payload(), payload);
807
808 let encoded = ClientMessage::CapabilitiesUpdate(update)
809 .encode(ProtocolVersion::V22)
810 .unwrap();
811 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
812 assert_eq!(frame.payload, payload);
813 }
814
815 #[test]
816 fn version_three_video_entry_boundary_depends_on_protocol() {
817 let mut before = vec![0; 2_060];
818 put(&mut before, 4, 1);
819 put(&mut before, VIDEO_OFFSET, Codec::H264.wire_value());
820 let before =
821 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 16, &before).unwrap();
822 assert_eq!(before.video().len(), 1);
823 assert_eq!(before.video()[0].address_type, None);
824
825 let mut from = vec![0; 2_100];
826 put(&mut from, 4, 1);
827 put(&mut from, VIDEO_OFFSET, Codec::H264.wire_value());
828 put(&mut from, VIDEO_OFFSET + 136, 2);
829 let from = CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, 17, &from).unwrap();
830 assert_eq!(from.video().len(), 1);
831 assert_eq!(
832 from.video()[0].address_type,
833 Some(IpAddressType::Ipv4AndIpv6)
834 );
835 }
836
837 #[test]
838 fn update_rejects_truncation_and_every_oversized_count() {
839 assert!(matches!(
840 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &[0; 100]),
841 Err(CodecError::Truncated { .. })
842 ));
843
844 for (offset, count) in [(0, 19), (4, 11), (8, 6), (16, 7)] {
845 let mut payload = vec![0; 1_840];
846 put(&mut payload, offset, count);
847 assert!(matches!(
848 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &payload),
849 Err(CodecError::CountTooLarge { .. })
850 ));
851 }
852
853 let mut services = vec![0; 1_840];
854 put(&mut services, CONFERENCE_OFFSET + 8, 5);
855 assert!(matches!(
856 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &services),
857 Err(CodecError::CountTooLarge { .. })
858 ));
859
860 let mut layouts = vec![0; 1_840];
861 put(&mut layouts, CONFERENCE_OFFSET + 8, 1);
862 put(&mut layouts, CONFERENCE_OFFSET + 12, 6);
863 assert!(matches!(
864 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &layouts),
865 Err(CodecError::CountTooLarge { .. })
866 ));
867
868 let mut levels = vec![0; 1_840];
869 put(&mut levels, 4, 1);
870 put(&mut levels, VIDEO_OFFSET + 8, 5);
871 assert!(matches!(
872 CapabilityUpdate::decode(CapabilityUpdateVariant::Version1, 3, &levels),
873 Err(CodecError::CountTooLarge { .. })
874 ));
875 }
876
877 #[test]
878 fn every_update_variant_round_trips_its_original_fixed_layout() {
879 for (variant, protocol, size) in [
880 (
881 CapabilityUpdateVariant::Version1,
882 ProtocolVersion::V3,
883 1_840,
884 ),
885 (
886 CapabilityUpdateVariant::Version1ExpandedVideo,
887 ProtocolVersion::V16,
888 2_000,
889 ),
890 (
891 CapabilityUpdateVariant::Version2,
892 ProtocolVersion::V22,
893 2_000,
894 ),
895 (
896 CapabilityUpdateVariant::Version3,
897 ProtocolVersion::V22,
898 2_380,
899 ),
900 ] {
901 let payload = vec![0; size];
902 let decoded = ClientMessage::decode_with_version(
903 Frame::new(protocol.wire(), variant.message_id(), payload.clone()),
904 protocol,
905 )
906 .unwrap();
907 assert!(matches!(
908 decoded,
909 ClientMessage::CapabilitiesUpdate(ref update) if update.variant() == variant
910 ));
911 let encoded = decoded.encode(protocol).unwrap();
912 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
913 assert_eq!(frame.message_id, variant.message_id());
914 assert_eq!(frame.payload, payload);
915 }
916 }
917
918 #[test]
919 fn v22_7961_legacy_body_size_overrides_the_modern_session_protocol() {
920 let bytes = fixture(include_str!(
921 "../../tests/fixtures/golden/update_capabilities_7961_v22_legacy.hex"
922 ));
923 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
924 assert_eq!(frame.protocol_version, ProtocolVersion::V22.wire());
925 assert_eq!(
926 frame.message_id,
927 CapabilityUpdateVariant::Version1.message_id()
928 );
929 assert_eq!(frame.payload.len(), 1_840);
930 let decoded = ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap();
931
932 assert!(matches!(
933 decoded,
934 ClientMessage::CapabilitiesUpdate(ref update)
935 if update.variant() == CapabilityUpdateVariant::Version1
936 ));
937 let encoded = decoded.encode(ProtocolVersion::V22).unwrap();
938 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
939 assert_eq!(frame.protocol_version, ProtocolVersion::V22.wire());
940 assert_eq!(frame.payload.len(), 1_840);
941 assert_eq!(encoded, bytes);
942 }
943}