1mod bounded;
14pub mod capabilities;
15pub mod catalog;
16mod codec;
17pub mod values;
18pub mod wire;
19
20use std::fmt;
21use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
22use std::num::{NonZeroU16, NonZeroU32};
23
24use crate::types::DateTemplate;
25use crate::types::{
26 ApplicationId, CallInfo, CallReference, ConferenceId, DeviceId, MediaEndpoint, SoftKeyProfile,
27 TransactionId,
28};
29use capabilities::CapabilityUpdate;
30use catalog::MessageId;
31pub(crate) use catalog::wire_id;
32use values::{
33 AddParticipantResult, AlarmSeverity, AnnouncementPlayMode, AnnouncementPlayStatus,
34 AuditParticipantResult, BusyLampFieldState, ButtonType, CallHistoryDisposition, CallState,
35 Codec, ConferenceResourceType, CreateConferenceResult, DeleteConferenceResult, DeviceType,
36 Digit, EchoCancellation, EncryptionMethod, EndOfAnnouncementAck, G723BitRate, IpAddressType,
37 KeyMode, LampMode, MediaPathCapability, MediaPathEvent, MediaPathId, MediaStatus,
38 MediaTransport, MediaType, MessageWaitingResult, MicrophoneMode, ModifyConferenceResult,
39 NotificationPriority, PartyInformationRestrictions, PhoneFeatures, ProtocolVersion,
40 QosDirection, QosErrorCode, QosReservationStyle, ResetType, RingDuration, RingerMode,
41 RsvpErrorCode, SilenceSuppression, SpeakerMode, StatisticsProcessing, Stimulus,
42 SubscriptionCause, Tone, ToneDirection, VideoFormat,
43};
44use wire::CodecError;
45
46pub use bounded::{BoundedBytes, BoundedBytesError};
47
48pub const MAX_OPAQUE_MESSAGE_BYTES: usize = wire::MAX_FRAME_SIZE - wire::HEADER_SIZE;
50
51pub const MULTIMEDIA_CAPABILITY_BYTES: usize = 76;
53pub const MAX_MULTIMEDIA_PICTURE_FORMATS: usize = 5;
55
56pub(crate) const BUTTON_TEMPLATE_ENTRIES_PER_CHUNK: usize = 42;
58
59#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66pub struct MediaRequestToken(NonZeroU32);
67
68impl MediaRequestToken {
69 pub const fn new(value: u32) -> Option<Self> {
71 match NonZeroU32::new(value) {
72 Some(value) => Some(Self(value)),
73 None => None,
74 }
75 }
76
77 pub const fn get(self) -> u32 {
78 self.0.get()
79 }
80
81 pub const fn checked_next(self) -> Option<Self> {
86 match self.get().checked_add(1) {
87 Some(value) => Self::new(value),
88 None => None,
89 }
90 }
91}
92
93#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
96pub struct MediaRequestIdentity {
97 generation: u64,
98 token: MediaRequestToken,
99}
100
101impl MediaRequestIdentity {
102 pub const fn new(generation: u64, token: MediaRequestToken) -> Option<Self> {
106 if generation == 0 {
107 None
108 } else {
109 Some(Self { generation, token })
110 }
111 }
112
113 pub const fn generation(self) -> u64 {
114 self.generation
115 }
116
117 pub const fn token(self) -> MediaRequestToken {
118 self.token
119 }
120
121 pub const fn checked_next(self) -> Option<Self> {
124 let generation = match self.generation.checked_add(1) {
125 Some(generation) => generation,
126 None => return None,
127 };
128 let token = match self.token.checked_next() {
129 Some(token) => token,
130 None => return None,
131 };
132 Some(Self { generation, token })
133 }
134
135 pub const fn accepts_ack(
143 self,
144 acknowledgement_party_id: u32,
145 acknowledgement_call_reference: u32,
146 stable_call_reference: u32,
147 ) -> bool {
148 let call_matches = acknowledgement_call_reference == 0
149 || acknowledgement_call_reference == stable_call_reference;
150 if acknowledgement_party_id == self.token.get() {
151 return call_matches;
152 }
153 self.generation == 1
154 && acknowledgement_party_id == 0
155 && acknowledgement_call_reference == stable_call_reference
156 }
157}
158
159#[derive(Clone, Debug, Eq, PartialEq)]
160pub struct RawMessage {
162 pub message_id: u32,
163 pub protocol_version: u32,
164 pub payload: Vec<u8>,
165}
166
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct RegistrationMessage {
173 pub device_id: DeviceId,
174 pub reported_address: Option<Ipv4Addr>,
176 pub reported_ipv6_address: Option<Ipv6Addr>,
178 pub device_type: DeviceType,
179 pub advertised_protocol: u32,
183 pub features: PhoneFeatures,
185 pub firmware: String,
186 pub configuration_version_stamp: BoundedBytes<48>,
188 pub wire: Option<RegistrationWireDetails>,
192}
193
194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
199pub struct RegistrationWireDetails {
200 pub station_user_id: u32,
201 pub station_instance: u32,
202 pub max_streams: u32,
203 pub active_streams: u32,
204 pub mac_address_and_padding: [u8; 12],
206 pub max_conferences: u32,
207 pub active_conferences: u32,
208 pub ipv4_address_scope: u32,
210 pub max_lines: u32,
211 pub ipv6_address_scope: u32,
213}
214
215#[derive(Clone, Debug, Eq, PartialEq)]
216pub struct MediaCapability {
218 pub codec: Codec,
219 pub max_frames_per_packet: u32,
220 pub codec_parameters: [u8; 8],
222}
223
224pub const MEDIA_PORT_LIST_MAX_PORTS: usize = 16;
225
226#[derive(Clone, Debug, Eq, PartialEq)]
227pub struct MediaPortList {
228 pub rtp_ports: Vec<u16>,
229}
230
231#[derive(Clone, Eq, PartialEq)]
233pub struct MediaEncryption {
234 pub algorithm: EncryptionMethod,
235 key: [u8; 16],
236 key_length: u8,
237 salt: [u8; 16],
238 salt_length: u8,
239 pub mki_present: u32,
241 pub key_derivation_rate: u32,
243}
244
245impl MediaEncryption {
246 pub fn new(
250 algorithm: EncryptionMethod,
251 key: &[u8],
252 salt: &[u8],
253 mki_present: u32,
254 key_derivation_rate: u32,
255 ) -> Result<Self, CodecError> {
256 if key.len() > 16 {
257 return Err(CodecError::SecretTooLong {
258 field: "media encryption key",
259 actual: key.len(),
260 maximum: 16,
261 });
262 }
263 if salt.len() > 16 {
264 return Err(CodecError::SecretTooLong {
265 field: "media encryption salt",
266 actual: salt.len(),
267 maximum: 16,
268 });
269 }
270 let mut wire_key = [0; 16];
271 wire_key[..key.len()].copy_from_slice(key);
272 let mut wire_salt = [0; 16];
273 wire_salt[..salt.len()].copy_from_slice(salt);
274 Ok(Self {
275 algorithm,
276 key: wire_key,
277 key_length: key.len() as u8,
278 salt: wire_salt,
279 salt_length: salt.len() as u8,
280 mki_present,
281 key_derivation_rate,
282 })
283 }
284
285 pub(crate) const fn from_wire(
286 algorithm: EncryptionMethod,
287 key: [u8; 16],
288 key_length: u8,
289 salt: [u8; 16],
290 salt_length: u8,
291 mki_present: u32,
292 key_derivation_rate: u32,
293 ) -> Self {
294 Self {
295 algorithm,
296 key,
297 key_length,
298 salt,
299 salt_length,
300 mki_present,
301 key_derivation_rate,
302 }
303 }
304
305 pub fn key(&self) -> &[u8] {
306 &self.key[..usize::from(self.key_length)]
307 }
308
309 pub fn salt(&self) -> &[u8] {
310 &self.salt[..usize::from(self.salt_length)]
311 }
312}
313
314impl fmt::Debug for MediaEncryption {
315 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
316 formatter
317 .debug_struct("MediaEncryption")
318 .field("algorithm", &self.algorithm)
319 .field("key", &"<redacted>")
320 .field("key_len", &self.key_length)
321 .field("salt", &"<redacted>")
322 .field("salt_len", &self.salt_length)
323 .field("mki_present", &self.mki_present)
324 .field("key_derivation_rate", &self.key_derivation_rate)
325 .finish()
326 }
327}
328
329impl Drop for MediaEncryption {
330 fn drop(&mut self) {
331 self.key.fill(0);
332 self.salt.fill(0);
333 }
334}
335
336#[derive(Clone, Copy, Debug, Eq, PartialEq)]
338pub struct AnnouncementEntry {
339 pub locale: u32,
340 pub country: u32,
341 pub tone: Tone,
342}
343
344#[derive(Clone, Debug, Eq, PartialEq)]
346pub struct CreateConferenceRequest {
347 pub conference_id: ConferenceId,
348 pub reserved_participants: u32,
349 pub resource_type: ConferenceResourceType,
350 pub application_id: ApplicationId,
351 pub application_conference_id: String,
352 pub application_data: String,
353 pub passthrough_data: Vec<u8>,
354}
355
356#[derive(Clone, Debug, Eq, PartialEq)]
357pub struct CreateConferenceResponse {
359 pub conference_id: ConferenceId,
360 pub result: CreateConferenceResult,
361 pub passthrough_data: Vec<u8>,
362}
363
364#[derive(Clone, Debug, Eq, PartialEq)]
366pub struct ModifyConferenceRequest {
367 pub conference_id: ConferenceId,
368 pub reserved_participants: u32,
369 pub application_id: ApplicationId,
370 pub application_conference_id: String,
371 pub application_data: String,
372 pub passthrough_data: Vec<u8>,
373}
374
375#[derive(Clone, Debug, Eq, PartialEq)]
376pub struct ModifyConferenceResponse {
378 pub conference_id: ConferenceId,
379 pub result: ModifyConferenceResult,
380 pub passthrough_data: Vec<u8>,
381}
382
383#[derive(Clone, Debug, Eq, PartialEq)]
384pub struct AuditConferenceEntry {
386 pub conference_id: ConferenceId,
387 pub resource_type: ConferenceResourceType,
388 pub reserved_participants: u32,
389 pub active_participants: u32,
390 pub application_id: ApplicationId,
391 pub application_conference_id: String,
392 pub application_data: String,
393}
394
395#[derive(Clone, Debug, Eq, PartialEq)]
396pub struct AuditConferenceResponse {
398 pub last: u32,
400 pub entries: Vec<AuditConferenceEntry>,
401}
402
403#[derive(Clone, Debug, Eq, PartialEq)]
404pub struct ConferenceParticipant {
406 pub call_reference: CallReference,
407 pub presentation_restrictions: PartyInformationRestrictions,
408 pub name: String,
409 pub number: String,
410 pub conference_name: String,
411}
412
413#[derive(Clone, Debug, Eq, PartialEq)]
414pub struct AddParticipantRequest {
416 pub conference_id: ConferenceId,
417 pub participant: ConferenceParticipant,
418}
419
420#[derive(Clone, Debug, Eq, PartialEq)]
425pub struct ChangeParticipantRequest {
426 pub conference_id: ConferenceId,
427 pub participant: ConferenceParticipant,
428}
429
430#[derive(Clone, Debug, Eq, PartialEq)]
431pub struct AddParticipantResponse {
433 pub conference_id: ConferenceId,
434 pub call_reference: CallReference,
435 pub result: AddParticipantResult,
436 pub bridge_participant_id: BoundedBytes<257>,
438}
439
440#[derive(Clone, Debug, Eq, PartialEq)]
443pub struct AuditParticipantResponse {
444 pub result: AuditParticipantResult,
445 pub last: u32,
446 pub conference_id: ConferenceId,
447 pub number_of_entries: u32,
449 pub participant_entries: Vec<u8>,
451}
452
453#[derive(Clone, Copy, Debug, Eq, PartialEq)]
456pub struct ParticipantChangeRouting {
457 pub application_id: ApplicationId,
458 pub line_instance: u32,
459 pub transaction_id: TransactionId,
460 pub sequence_flag: u32,
461 pub display_priority: u32,
462 pub application_instance_id: ApplicationId,
463 pub routing: u32,
464}
465
466#[derive(Clone, Debug, Eq, PartialEq)]
467pub struct ConferenceParticipantChange {
469 pub conference_id: ConferenceId,
470 pub participant: ConferenceParticipant,
471}
472
473#[derive(Clone, Debug, Eq, PartialEq)]
474pub struct MulticastMediaReception {
476 pub conference_id: ConferenceId,
477 pub passthrough_party_id: crate::types::PassthroughPartyId,
478 pub call_reference: CallReference,
479 pub address: IpAddr,
480 pub port: u16,
481 pub packet_millis: u32,
482 pub codec: Codec,
483 pub echo_cancellation: EchoCancellation,
484 pub g723_bitrate: G723BitRate,
485}
486
487#[derive(Clone, Debug, Eq, PartialEq)]
488pub struct MulticastMediaTransmission {
490 pub conference_id: ConferenceId,
491 pub passthrough_party_id: crate::types::PassthroughPartyId,
492 pub call_reference: CallReference,
493 pub address: IpAddr,
494 pub port: u16,
495 pub packet_millis: u32,
496 pub codec: Codec,
497 pub precedence: u32,
498 pub silence_suppression: u32,
499 pub max_frames_per_packet: u32,
500 pub g723_bitrate: G723BitRate,
501}
502
503#[derive(Clone, Debug, Eq, PartialEq)]
504pub struct KnownOpaqueMessage {
506 pub id: MessageId,
507 pub protocol_version: u32,
508 pub payload: BoundedBytes<MAX_OPAQUE_MESSAGE_BYTES>,
509}
510
511#[derive(Clone, Debug, Eq, PartialEq)]
512pub struct UserDataMessage {
514 pub application_id: u32,
515 pub line_instance: u32,
516 pub call_reference: u32,
517 pub transaction_id: u32,
518 pub data: Vec<u8>,
519}
520
521#[derive(Clone, Debug, Eq, PartialEq)]
523pub struct UserDataV1Message {
524 pub application_id: u32,
525 pub line_instance: u32,
526 pub call_reference: u32,
527 pub transaction_id: u32,
528 pub sequence_flag: u32,
529 pub display_priority: u32,
530 pub conference_id: u32,
531 pub application_instance_id: u32,
532 pub routing: u32,
533 pub data: Vec<u8>,
534}
535
536#[derive(Clone, Debug, Eq, PartialEq)]
537pub struct RegisterTokenMessage {
539 pub device_id: DeviceId,
540 pub device_instance: u32,
541 pub address: IpAddr,
542 pub device_type: DeviceType,
543 pub flags: u32,
545}
546
547#[derive(Clone, Debug, Eq, PartialEq)]
548pub struct SpcpRegisterTokenMessage {
549 pub device_id: DeviceId,
550 pub device_instance: u32,
551 pub address: Ipv4Addr,
552 pub device_type: DeviceType,
553 pub max_streams: u32,
554}
555
556pub const MAX_SIGNALING_SERVERS: usize = 5;
558
559#[derive(Clone, Debug, Eq, PartialEq)]
561pub struct SignalingServerEndpoint {
562 pub name: String,
563 pub address: IpAddr,
564 pub port: NonZeroU16,
565}
566
567#[derive(Clone, Debug, Eq, PartialEq)]
568pub struct MediaResourceNotification {
570 pub device_type: DeviceType,
571 pub in_service_streams: u32,
572 pub max_streams_per_conference: u32,
573 pub out_of_service_streams: u32,
574}
575
576#[derive(Clone, Debug, Eq, PartialEq)]
577pub struct SubscriptionRequest {
579 pub transaction_id: u32,
580 pub feature_id: u32,
581 pub timer_seconds: u32,
582 pub subscription_id: String,
583}
584
585#[derive(Clone, Debug, Eq, PartialEq)]
586pub struct PortEndpoint {
588 pub conference_id: u32,
589 pub call_reference: u32,
590 pub passthrough_party_id: u32,
591 pub address: IpAddr,
592 pub rtp_port: u16,
593 pub rtcp_port: u16,
594 pub media_type: Option<MediaType>,
595}
596
597#[derive(Clone, Copy, Debug, Eq, PartialEq)]
598pub struct PortRequest {
600 pub conference_id: ConferenceId,
601 pub call_reference: CallReference,
602 pub passthrough_party_id: crate::types::PassthroughPartyId,
603 pub transport: MediaTransport,
604 pub address_type: Option<IpAddressType>,
605 pub media_type: Option<MediaType>,
606}
607
608#[derive(Clone, Copy, Debug, Eq, PartialEq)]
609pub struct PortClose {
611 pub conference_id: ConferenceId,
612 pub call_reference: CallReference,
613 pub passthrough_party_id: crate::types::PassthroughPartyId,
614 pub media_type: Option<MediaType>,
615}
616
617#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
619pub struct QosFlow {
620 pub conference_id: ConferenceId,
621 pub call_reference: CallReference,
622 pub passthrough_party_id: crate::types::PassthroughPartyId,
623 pub address: Ipv4Addr,
624 pub port: u16,
625}
626
627#[derive(Clone, Copy, Debug, Eq, PartialEq)]
632pub struct QosTrafficSpecification {
633 pub codec: Codec,
634 pub average_bit_rate: u32,
635 pub burst_size: u32,
636 pub peak_rate: u32,
637}
638
639#[derive(Clone, Debug, Eq, PartialEq)]
641pub struct QosApplicationIdentifier {
642 pub vendor_id: String,
643 pub version: String,
644 pub application_name: String,
645 pub sub_application_id: String,
646}
647
648#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
649pub struct MessageWaitingCounts {
651 pub new: u32,
652 pub old: u32,
653}
654
655#[derive(Clone, Debug, Eq, PartialEq)]
656pub struct MessageWaitingNotification {
658 pub target_number: String,
659 pub control_number: String,
660 pub messages_waiting: bool,
661 pub total_voicemail: MessageWaitingCounts,
662 pub priority_voicemail: MessageWaitingCounts,
663 pub total_fax: MessageWaitingCounts,
664 pub priority_fax: MessageWaitingCounts,
665}
666
667#[derive(Clone, Copy, Debug, Eq, PartialEq)]
668pub struct OpenMultimediaReceiveChannelAck {
670 pub status: MediaStatus,
671 pub endpoint: MediaEndpointAddress,
672 pub passthrough_party_id: crate::types::PassthroughPartyId,
673 pub call_reference: CallReference,
674}
675
676#[derive(Clone, Copy, Debug, Eq, PartialEq)]
677pub struct StartMultimediaTransmissionAck {
679 pub conference_id: ConferenceId,
680 pub passthrough_party_id: crate::types::PassthroughPartyId,
681 pub call_reference: CallReference,
682 pub endpoint: MediaEndpointAddress,
683 pub status: MediaStatus,
684}
685
686#[derive(Clone, Copy, Debug, Eq, PartialEq)]
687pub struct MediaEndpointAddress {
689 pub address: IpAddr,
690 pub port: u16,
691}
692
693#[derive(Clone, Copy, Debug, Eq, PartialEq)]
694pub struct MultimediaStreamControl {
696 pub conference_id: ConferenceId,
697 pub passthrough_party_id: crate::types::PassthroughPartyId,
698 pub call_reference: CallReference,
699 pub port_handling_flag: u32,
700}
701
702#[derive(Clone, Copy, Debug, Eq, PartialEq)]
703pub struct AudioStreamControl {
705 pub conference_id: ConferenceId,
706 pub passthrough_party_id: crate::types::PassthroughPartyId,
707 pub call_reference: CallReference,
708 pub port_handling_flag: u32,
709}
710
711#[derive(Clone, Copy, Debug, Eq, PartialEq)]
712pub struct SessionTransmission {
714 pub remote_address: IpAddr,
715 pub session_type: u32,
716}
717
718#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
720pub struct RtpPayloadNumber(u8);
721
722impl RtpPayloadNumber {
723 pub const MAX: u32 = 127;
724
725 pub const fn new(value: u32) -> Result<Self, RtpPayloadNumberError> {
726 if value <= Self::MAX {
727 Ok(Self(value as u8))
728 } else {
729 Err(RtpPayloadNumberError { actual: value })
730 }
731 }
732
733 pub const fn get(self) -> u8 {
734 self.0
735 }
736}
737
738impl TryFrom<u32> for RtpPayloadNumber {
739 type Error = RtpPayloadNumberError;
740
741 fn try_from(value: u32) -> Result<Self, Self::Error> {
742 Self::new(value)
743 }
744}
745
746impl From<RtpPayloadNumber> for u32 {
747 fn from(value: RtpPayloadNumber) -> Self {
748 u32::from(value.get())
749 }
750}
751
752#[derive(Clone, Copy, Debug, Eq, PartialEq)]
754pub struct RtpPayloadNumberError {
755 pub actual: u32,
756}
757
758impl fmt::Display for RtpPayloadNumberError {
759 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
760 write!(
761 formatter,
762 "RTP payload number {} exceeds {}",
763 self.actual,
764 RtpPayloadNumber::MAX
765 )
766 }
767}
768
769impl std::error::Error for RtpPayloadNumberError {}
770
771#[derive(Clone, Copy, Debug, Eq, PartialEq)]
773pub struct MultimediaPayloadDescriptor {
774 rfc_number: u32,
775 payload_number: RtpPayloadNumber,
776}
777
778impl MultimediaPayloadDescriptor {
779 pub const fn new(rfc_number: u32, payload_number: RtpPayloadNumber) -> Self {
781 Self {
782 rfc_number,
783 payload_number,
784 }
785 }
786
787 pub const fn rfc_number(self) -> u32 {
789 self.rfc_number
790 }
791
792 pub const fn payload_number(self) -> RtpPayloadNumber {
793 self.payload_number
794 }
795}
796
797#[derive(Clone, Copy, Debug, Eq, PartialEq)]
799pub struct MultimediaPictureFormat {
800 pub format: VideoFormat,
801 pub minimum_picture_interval: u32,
802}
803
804#[derive(Clone, Copy, Debug, Eq, PartialEq)]
806pub enum MultimediaVideoCapabilityArm {
807 H261 {
808 temporal_spatial_trade_off_capability: u32,
809 still_image_transmission: u32,
810 },
811 H263 {
812 capability_bitfield: u32,
813 annex_n_and_w_future_use: u32,
814 },
815 H263Plus {
816 model_number: u32,
817 bandwidth: u32,
818 },
819 H264 {
820 profile: u32,
821 level: u32,
822 custom_max_mbps: u32,
823 custom_max_fs: u32,
824 custom_max_dpb: u32,
825 custom_max_br_and_cpb: u32,
826 },
827}
828
829impl MultimediaVideoCapabilityArm {
830 pub const fn codec(self) -> Codec {
831 match self {
832 Self::H261 { .. } => Codec::H261,
833 Self::H263 { .. } => Codec::H263,
834 Self::H263Plus { .. } => Codec::H263Plus,
835 Self::H264 { .. } => Codec::H264,
836 }
837 }
838}
839
840#[derive(Clone)]
842pub struct MultimediaVideoCapability {
843 bit_rate: u32,
844 picture_formats: Box<[MultimediaPictureFormat]>,
845 conference_service_number: u32,
846 arm: MultimediaVideoCapabilityArm,
847 preserved_wire: Option<[u8; MULTIMEDIA_CAPABILITY_BYTES]>,
848}
849
850impl MultimediaVideoCapability {
851 pub fn new(
853 bit_rate: u32,
854 picture_formats: impl IntoIterator<Item = MultimediaPictureFormat>,
855 conference_service_number: u32,
856 arm: MultimediaVideoCapabilityArm,
857 ) -> Result<Self, MultimediaCapabilityError> {
858 let picture_formats = picture_formats.into_iter().collect::<Box<[_]>>();
859 if picture_formats.len() > MAX_MULTIMEDIA_PICTURE_FORMATS {
860 return Err(MultimediaCapabilityError {
861 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
862 actual: picture_formats.len(),
863 });
864 }
865 Ok(Self {
866 bit_rate,
867 picture_formats,
868 conference_service_number,
869 arm,
870 preserved_wire: None,
871 })
872 }
873
874 pub const fn bit_rate(&self) -> u32 {
875 self.bit_rate
876 }
877
878 pub fn picture_formats(&self) -> &[MultimediaPictureFormat] {
879 &self.picture_formats
880 }
881
882 pub const fn conference_service_number(&self) -> u32 {
883 self.conference_service_number
884 }
885
886 pub const fn arm(&self) -> MultimediaVideoCapabilityArm {
887 self.arm
888 }
889
890 pub const fn codec(&self) -> Codec {
891 self.arm.codec()
892 }
893}
894
895impl fmt::Debug for MultimediaVideoCapability {
896 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
897 formatter
898 .debug_struct("MultimediaVideoCapability")
899 .field("bit_rate", &self.bit_rate)
900 .field("picture_formats", &self.picture_formats)
901 .field("conference_service_number", &self.conference_service_number)
902 .field("arm", &self.arm)
903 .finish()
904 }
905}
906
907impl PartialEq for MultimediaVideoCapability {
908 fn eq(&self, other: &Self) -> bool {
909 self.bit_rate == other.bit_rate
910 && self.picture_formats == other.picture_formats
911 && self.conference_service_number == other.conference_service_number
912 && self.arm == other.arm
913 && self.preserved_wire == other.preserved_wire
914 }
915}
916
917impl Eq for MultimediaVideoCapability {}
918
919#[derive(Clone, Copy, Debug, Eq, PartialEq)]
920pub struct MultimediaCapabilityError {
922 pub maximum: usize,
923 pub actual: usize,
924}
925
926impl fmt::Display for MultimediaCapabilityError {
927 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
928 write!(
929 formatter,
930 "video capability contains {} picture formats, exceeding the maximum of {}",
931 self.actual, self.maximum
932 )
933 }
934}
935
936impl std::error::Error for MultimediaCapabilityError {}
937
938#[derive(Clone, Copy, Debug, Eq, PartialEq)]
939pub(crate) enum MultimediaPayloadDirection {
940 Receive,
941 Transmit,
942}
943
944#[derive(Clone, Eq, PartialEq)]
945enum MultimediaCapabilityState {
946 Video(MultimediaVideoCapability),
947 Preserved([u8; MULTIMEDIA_CAPABILITY_BYTES]),
948}
949
950#[derive(Clone, Copy, Debug, Eq, PartialEq)]
951enum MultimediaPayloadOrigin {
952 Constructed,
953 Decoded {
954 direction: MultimediaPayloadDirection,
955 protocol: ProtocolVersion,
956 compression_codec: Codec,
957 },
958}
959
960#[derive(Clone)]
962pub struct MultimediaPayload {
963 descriptor: MultimediaPayloadDescriptor,
964 capability: MultimediaCapabilityState,
965 origin: MultimediaPayloadOrigin,
966}
967
968impl MultimediaPayload {
969 pub fn new(payload_number: RtpPayloadNumber, capability: MultimediaVideoCapability) -> Self {
971 Self::with_descriptor(
972 MultimediaPayloadDescriptor::new(0, payload_number),
973 capability,
974 )
975 }
976
977 pub fn with_descriptor(
979 descriptor: MultimediaPayloadDescriptor,
980 capability: MultimediaVideoCapability,
981 ) -> Self {
982 Self {
983 descriptor,
984 capability: MultimediaCapabilityState::Video(capability),
985 origin: MultimediaPayloadOrigin::Constructed,
986 }
987 }
988
989 const fn from_decoded(
990 descriptor: MultimediaPayloadDescriptor,
991 capability: MultimediaCapabilityState,
992 direction: MultimediaPayloadDirection,
993 protocol: ProtocolVersion,
994 compression_codec: Codec,
995 ) -> Self {
996 Self {
997 descriptor,
998 capability,
999 origin: MultimediaPayloadOrigin::Decoded {
1000 direction,
1001 protocol,
1002 compression_codec,
1003 },
1004 }
1005 }
1006
1007 #[cfg(test)]
1008 pub(crate) fn from_wire(
1009 rfc_number: u32,
1010 payload_number: RtpPayloadNumber,
1011 capability: [u8; MULTIMEDIA_CAPABILITY_BYTES],
1012 codec: Codec,
1013 direction: MultimediaPayloadDirection,
1014 protocol: ProtocolVersion,
1015 ) -> Self {
1016 Self::from_decoded(
1017 MultimediaPayloadDescriptor::new(rfc_number, payload_number),
1018 MultimediaCapabilityState::Preserved(capability),
1019 direction,
1020 protocol,
1021 codec,
1022 )
1023 }
1024
1025 pub const fn descriptor(&self) -> MultimediaPayloadDescriptor {
1026 self.descriptor
1027 }
1028
1029 pub const fn codec(&self) -> Codec {
1030 self.compression_codec()
1031 }
1032
1033 pub const fn payload_number(&self) -> RtpPayloadNumber {
1034 self.descriptor.payload_number()
1035 }
1036
1037 pub const fn video_capability(&self) -> Option<&MultimediaVideoCapability> {
1039 match &self.capability {
1040 MultimediaCapabilityState::Video(capability) => Some(capability),
1041 MultimediaCapabilityState::Preserved(_) => None,
1042 }
1043 }
1044
1045 pub(crate) fn is_valid_for(
1046 &self,
1047 direction: MultimediaPayloadDirection,
1048 protocol: ProtocolVersion,
1049 ) -> bool {
1050 match self.origin {
1051 MultimediaPayloadOrigin::Constructed => true,
1052 MultimediaPayloadOrigin::Decoded {
1053 direction: decoded_direction,
1054 protocol: decoded_protocol,
1055 ..
1056 } => decoded_direction == direction && decoded_protocol.wire() == protocol.wire(),
1057 }
1058 }
1059
1060 pub(crate) fn is_direction(&self, direction: MultimediaPayloadDirection) -> bool {
1061 match self.origin {
1062 MultimediaPayloadOrigin::Constructed => true,
1063 MultimediaPayloadOrigin::Decoded {
1064 direction: decoded_direction,
1065 ..
1066 } => decoded_direction == direction,
1067 }
1068 }
1069
1070 pub(crate) const fn compression_codec(&self) -> Codec {
1071 match self.origin {
1072 MultimediaPayloadOrigin::Constructed => match &self.capability {
1073 MultimediaCapabilityState::Video(capability) => capability.codec(),
1074 MultimediaCapabilityState::Preserved(_) => unreachable!(),
1075 },
1076 MultimediaPayloadOrigin::Decoded {
1077 compression_codec, ..
1078 } => compression_codec,
1079 }
1080 }
1081}
1082
1083impl PartialEq for MultimediaPayload {
1084 fn eq(&self, other: &Self) -> bool {
1085 self.descriptor == other.descriptor
1086 && self.capability == other.capability
1087 && self.origin == other.origin
1088 }
1089}
1090
1091impl Eq for MultimediaPayload {}
1092
1093impl fmt::Debug for MultimediaPayload {
1094 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1095 formatter
1096 .debug_struct("MultimediaPayload")
1097 .field("descriptor", &self.descriptor)
1098 .field("codec", &self.codec())
1099 .field("video_capability", &self.video_capability())
1100 .finish()
1101 }
1102}
1103
1104#[derive(Clone, Debug, Eq, PartialEq)]
1105pub struct OpenMultimediaChannel {
1107 pub conference_id: ConferenceId,
1108 pub passthrough_party_id: crate::types::PassthroughPartyId,
1109 pub line_instance: u32,
1110 pub call_reference: CallReference,
1111 pub payload: MultimediaPayload,
1112 pub conference_creator: bool,
1113 pub encryption: Option<MediaEncryption>,
1115 pub stream_passthrough_id: u32,
1117 pub associated_stream_id: u32,
1119 pub source: MediaEndpointAddress,
1120 pub requested_address_type: IpAddressType,
1121}
1122
1123#[derive(Clone, Debug, Eq, PartialEq)]
1124pub struct StartMultimediaTransmission {
1126 pub conference_id: ConferenceId,
1127 pub passthrough_party_id: crate::types::PassthroughPartyId,
1128 pub endpoint: MediaEndpointAddress,
1129 pub call_reference: CallReference,
1130 pub payload: MultimediaPayload,
1131 pub traffic_class: crate::types::MediaTrafficClass,
1132 pub encryption: Option<MediaEncryption>,
1134 pub stream_passthrough_id: u32,
1136 pub associated_stream_id: u32,
1138}
1139
1140#[derive(Clone, Debug, Eq, PartialEq)]
1141pub struct MiscellaneousCommand {
1143 pub conference_id: ConferenceId,
1144 pub passthrough_party_id: crate::types::PassthroughPartyId,
1145 pub call_reference: CallReference,
1146 pub command: values::MiscCommandType,
1147 pub data: BoundedBytes<36>,
1149}
1150
1151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1152pub struct VideoFlowControl {
1154 pub conference_id: ConferenceId,
1155 pub passthrough_party_id: crate::types::PassthroughPartyId,
1156 pub call_reference: CallReference,
1157 pub maximum_bit_rate: u32,
1158}
1159
1160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1161pub struct DtmfToneControl {
1163 pub tone: Tone,
1164 pub conference_id: ConferenceId,
1165 pub passthrough_party_id: u32,
1166}
1167
1168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1169pub struct DtmfPayloadIdentity {
1171 pub payload_type: u32,
1173 pub conference_id: u32,
1174 pub passthrough_party_id: u32,
1175}
1176
1177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1178pub struct DtmfPayloadRequest {
1180 pub payload_type: u32,
1182 pub conference_id: u32,
1183 pub passthrough_party_id: u32,
1184 pub dtmf_type: u32,
1186}
1187
1188pub const XML_ALARM_MAX_WIRE_BYTES: usize = 2_048;
1190pub const XML_ALARM_CANONICAL_WIRE_BYTES: usize = 2_004;
1192pub const XML_ALARM_CANONICAL_DOCUMENT_BYTES: usize = 2_000;
1194
1195#[derive(Clone, Debug, Eq, PartialEq)]
1196pub struct XmlAlarmMessage {
1201 wire_payload: BoundedBytes<XML_ALARM_MAX_WIRE_BYTES>,
1202}
1203
1204impl XmlAlarmMessage {
1205 pub fn from_xml(xml: impl AsRef<[u8]>) -> Result<Self, CodecError> {
1207 let xml = xml.as_ref();
1208 if xml.contains(&0) {
1209 return Err(CodecError::InvalidText);
1210 }
1211 if xml.len() > XML_ALARM_CANONICAL_DOCUMENT_BYTES {
1212 return Err(CodecError::TextTooLong {
1213 message_id: wire_id::XML_ALARM,
1214 field: "alarm XML",
1215 actual: xml.len(),
1216 maximum: XML_ALARM_CANONICAL_DOCUMENT_BYTES,
1217 });
1218 }
1219 let mut wire_payload = vec![0; XML_ALARM_CANONICAL_WIRE_BYTES];
1220 wire_payload[..xml.len()].copy_from_slice(xml);
1221 Self::from_wire_payload(wire_payload)
1222 }
1223
1224 pub fn from_wire_payload(payload: impl Into<Box<[u8]>>) -> Result<Self, CodecError> {
1226 let payload = payload.into();
1227 let wire_payload =
1228 BoundedBytes::new(payload).map_err(|error| CodecError::CountTooLarge {
1229 message_id: wire_id::XML_ALARM,
1230 field: "alarm payload",
1231 count: error.actual,
1232 maximum: error.maximum,
1233 })?;
1234 Ok(Self { wire_payload })
1235 }
1236
1237 pub fn xml_bytes(&self) -> &[u8] {
1239 let bytes = self.wire_payload.as_bytes();
1240 let end = bytes
1241 .iter()
1242 .position(|byte| *byte == 0)
1243 .unwrap_or(bytes.len());
1244 &bytes[..end]
1245 }
1246
1247 pub fn wire_payload(&self) -> &[u8] {
1249 self.wire_payload.as_bytes()
1250 }
1251}
1252
1253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1259pub struct MediaFailureDetection {
1260 pub conference_id: ConferenceId,
1261 pub passthrough_party_id: u32,
1262 pub packet_millis: u32,
1263 pub codec: Codec,
1264 pub echo_cancellation: EchoCancellation,
1265 pub codec_qualifier: [u8; 4],
1266 pub call_reference: CallReference,
1267}
1268
1269#[derive(Clone, Debug, Eq, PartialEq)]
1272pub struct ExtensionDeviceCapabilities {
1273 pub unknown_1: u32,
1274 pub unknown_2: u32,
1275 pub unknown_3: u32,
1276 pub description: String,
1277}
1278
1279#[derive(Clone, Debug, Eq, PartialEq)]
1280pub struct ConfigurationStatus {
1282 pub device_name: String,
1283 pub station_user_id: u32,
1284 pub station_instance: u32,
1285 pub line_count: u32,
1286 pub speed_dial_count: u32,
1287 pub user_name: String,
1288 pub server_name: String,
1289}
1290
1291#[derive(Clone, Debug, Eq, PartialEq)]
1296pub enum ControlMessage {
1297 MediaResourceNotification(MediaResourceNotification),
1298 PortResponse(PortEndpoint),
1299 StartSessionTransmission(SessionTransmission),
1300 StopSessionTransmission(SessionTransmission),
1301 ClearConference {
1302 conference_id: ConferenceId,
1303 service_number: u32,
1304 },
1305 CreateConferenceRequest(CreateConferenceRequest),
1306 DeleteConferenceRequest {
1307 conference_id: ConferenceId,
1308 },
1309 ModifyConferenceRequest(ModifyConferenceRequest),
1310 AddParticipantRequest(AddParticipantRequest),
1311 DropParticipantRequest {
1312 conference_id: ConferenceId,
1313 call_reference: CallReference,
1314 },
1315 AuditConferenceRequest,
1316 AuditParticipantRequest {
1317 conference_id: ConferenceId,
1318 },
1319 ChangeParticipantRequest(ChangeParticipantRequest),
1320 CreateConferenceResponse(CreateConferenceResponse),
1321 DeleteConferenceResponse {
1322 conference_id: ConferenceId,
1323 result: DeleteConferenceResult,
1324 },
1325 ModifyConferenceResponse(ModifyConferenceResponse),
1326 AddParticipantResponse(AddParticipantResponse),
1327 AuditConferenceResponse(AuditConferenceResponse),
1328 AuditParticipantResponse(AuditParticipantResponse),
1329 StartAnnouncement {
1331 announcements: Vec<AnnouncementEntry>,
1332 end_of_ack: EndOfAnnouncementAck,
1334 conference_id: u32,
1335 matrix_conference_party_ids: Vec<u32>,
1337 hearing_conference_party_mask: u32,
1339 play_mode: AnnouncementPlayMode,
1340 },
1341 StopAnnouncement {
1342 conference_id: u32,
1343 },
1344 AnnouncementFinish {
1345 conference_id: u32,
1346 play_status: AnnouncementPlayStatus,
1347 },
1348 QosReservationNotify {
1349 flow: QosFlow,
1350 direction: QosDirection,
1351 },
1352 QosErrorNotify {
1354 flow: QosFlow,
1355 direction: QosDirection,
1356 error_code: QosErrorCode,
1357 failure_node: Ipv4Addr,
1359 rsvp_error_code: RsvpErrorCode,
1360 rsvp_error_subcode: u32,
1361 rsvp_error_flags: u32,
1362 },
1363 QosListen {
1365 flow: QosFlow,
1366 reservation_style: QosReservationStyle,
1367 maximum_retries: u32,
1368 retry_timer: u32,
1369 confirmation_required: bool,
1371 preemption_priority: u32,
1373 defending_priority: u32,
1375 traffic: QosTrafficSpecification,
1376 application: QosApplicationIdentifier,
1377 },
1378 QosPath {
1380 flow: QosFlow,
1381 reservation_style: QosReservationStyle,
1382 maximum_retries: u32,
1383 retry_timer: u32,
1384 preemption_priority: u32,
1385 defending_priority: u32,
1386 traffic: QosTrafficSpecification,
1387 application: QosApplicationIdentifier,
1388 },
1389 QosTeardown {
1391 flow: QosFlow,
1392 direction: QosDirection,
1393 },
1394 UpdateDscp {
1396 flow: QosFlow,
1397 dscp: u8,
1398 },
1399 QosModify {
1401 flow: QosFlow,
1402 direction: QosDirection,
1403 traffic: QosTrafficSpecification,
1404 application: QosApplicationIdentifier,
1405 },
1406 MessageWaitingNotification(MessageWaitingNotification),
1407 MessageWaitingResponse {
1408 target_number: String,
1409 result: MessageWaitingResult,
1410 },
1411 KnownOpaque(KnownOpaqueMessage),
1413}
1414
1415pub const CONNECTION_QUALITY_MAX_BYTES: usize = 600;
1417
1418#[derive(Clone, Eq, PartialEq)]
1423pub struct ConnectionQualityStatistics(Vec<u8>);
1424
1425impl ConnectionQualityStatistics {
1426 pub fn new(bytes: impl Into<Vec<u8>>) -> Result<Self, CodecError> {
1428 let bytes = bytes.into();
1429 if bytes.len() > CONNECTION_QUALITY_MAX_BYTES {
1430 return Err(CodecError::CountTooLarge {
1431 message_id: wire_id::CONNECTION_STATISTICS_RES,
1432 field: "quality statistics",
1433 count: bytes.len(),
1434 maximum: CONNECTION_QUALITY_MAX_BYTES,
1435 });
1436 }
1437 Ok(Self(bytes))
1438 }
1439
1440 pub fn as_bytes(&self) -> &[u8] {
1441 &self.0
1442 }
1443}
1444
1445impl fmt::Debug for ConnectionQualityStatistics {
1446 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1447 formatter
1448 .debug_struct("ConnectionQualityStatistics")
1449 .field("byte_count", &self.0.len())
1450 .finish()
1451 }
1452}
1453
1454#[derive(Clone, Eq, PartialEq)]
1455pub struct ConnectionStatistics {
1459 pub directory_number: String,
1460 pub call_reference: u32,
1461 pub processing: StatisticsProcessing,
1462 pub packets_sent: u32,
1463 pub octets_sent: u32,
1464 pub packets_received: u32,
1465 pub octets_received: u32,
1466 pub packets_lost: u32,
1467 pub jitter_millis: u32,
1469 pub latency_millis: u32,
1471 pub quality: ConnectionQualityStatistics,
1472}
1473
1474impl fmt::Debug for ConnectionStatistics {
1475 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1476 formatter
1477 .debug_struct("ConnectionStatistics")
1478 .field("directory_number", &"<redacted>")
1479 .field("call_reference", &self.call_reference)
1480 .field("processing", &self.processing)
1481 .field("packets_sent", &self.packets_sent)
1482 .field("octets_sent", &self.octets_sent)
1483 .field("packets_received", &self.packets_received)
1484 .field("octets_received", &self.octets_received)
1485 .field("packets_lost", &self.packets_lost)
1486 .field("jitter_millis", &self.jitter_millis)
1487 .field("latency_millis", &self.latency_millis)
1488 .field("quality", &self.quality)
1489 .finish()
1490 }
1491}
1492
1493#[derive(Clone, Debug, Eq, PartialEq)]
1494pub struct MediaTransmissionAckWire {
1496 pub extension: Option<[u8; 8]>,
1498}
1499
1500#[derive(Clone, Debug, Eq, PartialEq)]
1501pub struct MediaTransmissionAck {
1503 pub conference_id: u32,
1504 pub passthrough_party_id: u32,
1505 pub call_reference: u32,
1506 pub status: MediaStatus,
1507 pub address: IpAddr,
1508 pub port: u16,
1509 pub wire: Option<MediaTransmissionAckWire>,
1511}
1512
1513#[derive(Clone, Debug, Eq, PartialEq)]
1516pub struct OpenReceiveChannelWire {
1517 pub conference_id: u32,
1518 pub g723_bitrate: u32,
1520 pub stream_passthrough_id: u32,
1522 pub associated_stream_id: u32,
1524 pub dtmf_type: u32,
1526 pub mixing_mode: u32,
1528 pub direction: u32,
1530 pub requested_address_type: u32,
1532 pub audio_level_adjustment: u32,
1534 pub latent_capabilities: [u8; 36],
1536}
1537
1538#[derive(Clone, Debug, Eq, PartialEq)]
1541pub struct StartMediaTransmissionWire {
1542 pub conference_id: u32,
1543 pub g723_bitrate: u32,
1545 pub stream_passthrough_id: u32,
1547 pub associated_stream_id: u32,
1549 pub dtmf_type: u32,
1551 pub mixing_mode: u32,
1553 pub direction: u32,
1555 pub latent_capabilities: [u8; 36],
1557}
1558
1559#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1563pub enum KeypadButtonWireLayout {
1564 LegacyButtonOnly,
1566 WithCallIdentity,
1568}
1569
1570#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1572pub struct ButtonTemplateEntry {
1573 pub instance: u32,
1574 pub button_type: ButtonType,
1575}
1576
1577impl Default for ButtonTemplateEntry {
1578 fn default() -> Self {
1579 Self {
1580 instance: 0,
1581 button_type: ButtonType::Unused,
1582 }
1583 }
1584}
1585
1586#[derive(Clone, Debug, Eq, PartialEq)]
1587pub enum ClientMessage {
1595 KeepAlive,
1598 Register(RegistrationMessage),
1601 IpPort {
1604 rtp_port: u16,
1605 },
1606 KeypadButton {
1609 button: Digit,
1610 line_instance: u32,
1611 call_reference: u32,
1612 wire_layout: Option<KeypadButtonWireLayout>,
1613 },
1614 EnblocCall {
1617 called_party: String,
1618 line_instance: u32,
1619 },
1620 Stimulus {
1623 stimulus: Stimulus,
1624 instance: u32,
1625 call_reference: u32,
1626 status: u32,
1627 },
1628 OffHook {
1631 line_instance: u32,
1632 call_reference: u32,
1633 },
1634 OnHook {
1637 line_instance: u32,
1638 call_reference: u32,
1639 },
1640 OffHookWithCallingParty {
1643 calling_party_number: String,
1644 voice_mailbox: String,
1645 line_instance: u32,
1646 },
1647 LineStatRequest {
1650 line_instance: u32,
1651 },
1652 ConfigStatRequest,
1655 TimeDateRequest,
1658 ButtonTemplateRequest,
1661 VersionRequest,
1664 CapabilitiesResponse(Vec<MediaCapability>),
1667 MediaPortList(MediaPortList),
1670 CapabilitiesUpdate(CapabilityUpdate),
1673 OpenMultimediaReceiveChannelAck(OpenMultimediaReceiveChannelAck),
1676 ServerRequest,
1679 Alarm {
1682 severity: AlarmSeverity,
1683 text: String,
1684 parameters: Option<[u32; 2]>,
1687 },
1688 MulticastMediaReceptionAck {
1691 status: MediaStatus,
1692 passthrough_party_id: crate::types::PassthroughPartyId,
1693 call_reference: CallReference,
1694 },
1695 OpenReceiveChannelAck {
1698 status: MediaStatus,
1699 address: IpAddr,
1700 port: u16,
1701 passthrough_party_id: u32,
1702 call_reference: u32,
1703 },
1704 SoftKeySetRequest,
1707 SoftKeyTemplateRequest,
1710 SoftKeyEvent {
1713 event: u32,
1714 line_instance: u32,
1715 call_reference: u32,
1716 },
1717 Unregister {
1720 reason: u32,
1721 },
1722 RegisterToken(RegisterTokenMessage),
1725 SpcpRegisterToken(SpcpRegisterTokenMessage),
1728 HookFlash {
1731 line_instance: u32,
1732 call_reference: u32,
1733 },
1734 ForwardStatusRequest {
1737 line_instance: u32,
1738 },
1739 SpeedDialStatusRequest {
1742 speed_dial_instance: u32,
1743 },
1744 ConnectionStatisticsResponse(ConnectionStatistics),
1747 HeadsetStatus {
1750 enabled: bool,
1751 },
1752 MediaResourceNotification(MediaResourceNotification),
1755 MediaPathEvent {
1758 path: MediaPathId,
1759 event: MediaPathEvent,
1760 },
1761 MediaPathCapability {
1764 path: MediaPathId,
1765 capability: MediaPathCapability,
1766 },
1767 MediaTransmissionFailure {
1770 conference_id: u32,
1771 passthrough_party_id: u32,
1772 address: IpAddr,
1773 port: u16,
1774 call_reference: u32,
1775 status: MediaStatus,
1776 },
1777 RegisterAvailableLines {
1780 lines: u32,
1781 },
1782 ServiceUrlStatusRequest {
1785 index: u32,
1786 },
1787 FeatureStatusRequest {
1790 index: u32,
1791 capabilities: u32,
1793 },
1794 StartMediaTransmissionAck(MediaTransmissionAck),
1797 StartMultimediaTransmissionAck(StartMultimediaTransmissionAck),
1800 ExtensionDeviceCapabilities(ExtensionDeviceCapabilities),
1803 DeviceToUserData(UserDataMessage),
1806 DeviceToUserDataResponse(UserDataMessage),
1809 DeviceToUserDataV1(UserDataV1Message),
1812 DeviceToUserDataResponseV1(UserDataV1Message),
1815 PortResponse(PortEndpoint),
1818 SubscriptionStatusRequest(SubscriptionRequest),
1821 SubscribeDtmfPayloadResponse(DtmfPayloadIdentity),
1824 UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity),
1827 LocationInfo {
1830 xml: String,
1832 },
1833 XmlAlarm(XmlAlarmMessage),
1836 CallCountRequest {
1839 value: u32,
1841 },
1842 CreateConferenceResponse(CreateConferenceResponse),
1845 DeleteConferenceResponse {
1848 conference_id: ConferenceId,
1849 result: DeleteConferenceResult,
1850 },
1851 ModifyConferenceResponse(ModifyConferenceResponse),
1854 AuditConferenceResponse(AuditConferenceResponse),
1857 AddParticipantResponse(AddParticipantResponse),
1860 AuditParticipantResponse(AuditParticipantResponse),
1863 KnownOpaque(KnownOpaqueMessage),
1866 Unknown(RawMessage),
1869}
1870
1871#[derive(Clone, Debug, Eq, PartialEq)]
1872pub enum ServerMessage {
1879 RegisterAck {
1882 keepalive_seconds: u32,
1883 secondary_keepalive_seconds: u32,
1884 protocol: ProtocolVersion,
1885 features: PhoneFeatures,
1886 date_template: DateTemplate,
1887 },
1888 RegisterReject {
1891 reason: String,
1892 },
1893 KeepAliveAck,
1896 UnregisterAck,
1899 CapabilitiesRequest,
1902 EnunciatorCommand,
1905 ConfigStatus(ConfigurationStatus),
1908 LineStatus {
1911 instance: u32,
1912 number: String,
1913 display_name: String,
1914 },
1915 ButtonTemplate {
1918 offset: u32,
1919 total: u32,
1920 buttons: Vec<ButtonTemplateEntry>,
1921 },
1922 Version {
1925 firmware: String,
1926 },
1927 ServerResponse {
1930 servers: Vec<SignalingServerEndpoint>,
1931 },
1932 TimeDate {
1935 year: u32,
1936 month: u32,
1937 weekday: u32,
1938 day: u32,
1939 hour: u32,
1940 minute: u32,
1941 second: u32,
1942 milliseconds: u32,
1943 unix_seconds: u32,
1944 },
1945 SoftKeyTemplate {
1948 actions: Vec<values::SoftKey>,
1949 },
1950 SoftKeySet {
1953 profile: SoftKeyProfile,
1954 },
1955 SelectSoftKeys {
1958 line_instance: u32,
1959 call_reference: u32,
1960 set: KeyMode,
1961 valid_mask: u32,
1963 },
1964 CallState {
1967 state: CallState,
1968 line_instance: u32,
1969 call_reference: u32,
1970 },
1971 CallInfo {
1974 info: CallInfo,
1975 line_instance: u32,
1976 call_reference: u32,
1977 },
1978 DisplayPrompt {
1981 timeout_seconds: u32,
1982 text: String,
1983 line_instance: u32,
1984 call_reference: u32,
1985 },
1986 ClearPrompt {
1989 line_instance: u32,
1990 call_reference: u32,
1991 },
1992 DisplayNotify {
1995 timeout_seconds: u32,
1996 text: String,
1997 },
1998 ClearNotify,
2001 DisplayPriorityNotify {
2004 timeout_seconds: u32,
2005 priority: NotificationPriority,
2006 text: String,
2007 },
2008 ClearPriorityNotify {
2011 priority: NotificationPriority,
2012 },
2013 NotifyDtmfTone(DtmfToneControl),
2016 SendDtmfTone(DtmfToneControl),
2019 StartAnnouncement {
2022 announcements: Vec<AnnouncementEntry>,
2023 end_of_ack: u32,
2024 conference_id: u32,
2025 matrix_conference_party_ids: Vec<u32>,
2026 hearing_conference_party_mask: u32,
2027 play_mode: u32,
2028 },
2029 StopAnnouncement {
2032 conference_id: u32,
2033 },
2034 AnnouncementFinish {
2037 conference_id: u32,
2038 play_status: u32,
2039 },
2040 ClearConference {
2043 conference_id: ConferenceId,
2044 service_number: u32,
2045 },
2046 CreateConferenceRequest(CreateConferenceRequest),
2049 DeleteConferenceRequest {
2052 conference_id: ConferenceId,
2053 },
2054 ModifyConferenceRequest(ModifyConferenceRequest),
2057 AuditConferenceRequest,
2060 AddParticipantRequest(AddParticipantRequest),
2063 DropParticipantRequest {
2066 conference_id: ConferenceId,
2067 call_reference: CallReference,
2068 },
2069 AuditParticipantRequest {
2072 conference_id: ConferenceId,
2073 },
2074 ChangeParticipantRequest(ChangeParticipantRequest),
2077 StopMultimediaTransmission(MultimediaStreamControl),
2080 FlowControlCommand(VideoFlowControl),
2083 CloseMultimediaReceiveChannel(MultimediaStreamControl),
2086 VideoDisplayCommand {
2089 conference_id: ConferenceId,
2090 call_reference: CallReference,
2091 layout_id: u32,
2092 },
2093 FlowControlNotify(VideoFlowControl),
2096 ActivateCallPlane {
2099 line_instance: u32,
2100 },
2101 DeactivateCallPlane,
2104 BackspaceResponse {
2107 line_instance: u32,
2108 call_reference: u32,
2109 },
2110 RegisterTokenAck,
2113 RegisterTokenReject {
2116 backoff_seconds: u32,
2117 },
2118 SpcpRegisterTokenAck {
2121 features: u32,
2122 },
2123 SpcpRegisterTokenReject {
2126 backoff_seconds: u32,
2127 },
2128 SetRinger {
2131 mode: RingerMode,
2132 duration: RingDuration,
2133 line_instance: u32,
2134 call_reference: u32,
2135 },
2136 SetLamp {
2139 stimulus: ButtonType,
2140 instance: u32,
2141 mode: LampMode,
2142 },
2143 SetHookFlashDetect,
2146 StartTone {
2149 tone: Tone,
2150 direction: ToneDirection,
2151 line_instance: u32,
2152 call_reference: u32,
2153 },
2154 StopTone {
2157 line_instance: u32,
2158 call_reference: u32,
2159 },
2160 StartMulticastMediaReception(MulticastMediaReception),
2163 StartMulticastMediaTransmission(MulticastMediaTransmission),
2166 StopMulticastMediaReception {
2169 conference_id: ConferenceId,
2170 passthrough_party_id: crate::types::PassthroughPartyId,
2171 call_reference: CallReference,
2172 },
2173 StopMulticastMediaTransmission {
2176 conference_id: ConferenceId,
2177 passthrough_party_id: crate::types::PassthroughPartyId,
2178 call_reference: CallReference,
2179 },
2180 OpenReceiveChannel {
2183 call_reference: u32,
2184 passthrough_party_id: u32,
2185 packet_ms: u32,
2186 codec: Codec,
2187 echo_cancellation: EchoCancellation,
2188 telephone_event_payload: u8,
2190 source_address: IpAddr,
2191 source_port: u16,
2192 encryption: Option<MediaEncryption>,
2193 wire: Option<OpenReceiveChannelWire>,
2196 },
2197 CloseReceiveChannel(AudioStreamControl),
2200 ConnectionStatisticsRequest {
2203 directory_number: String,
2204 call_reference: u32,
2205 processing: StatisticsProcessing,
2206 },
2207 StartMediaTransmission {
2210 call_reference: u32,
2211 passthrough_party_id: u32,
2212 endpoint: MediaEndpoint,
2213 silence_suppression: SilenceSuppression,
2214 traffic_class: crate::types::MediaTrafficClass,
2216 encryption: Option<MediaEncryption>,
2217 wire: Option<StartMediaTransmissionWire>,
2220 },
2221 StopMediaTransmission(AudioStreamControl),
2224 StartMediaReception,
2227 StopMediaReception {
2230 conference_id: ConferenceId,
2231 passthrough_party_id: crate::types::PassthroughPartyId,
2232 },
2233 SubscribeDtmfPayloadRequest(DtmfPayloadRequest),
2236 SubscribeDtmfPayloadError(DtmfPayloadIdentity),
2239 UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest),
2242 UnsubscribeDtmfPayloadError(DtmfPayloadIdentity),
2245 SetSpeakerMode(SpeakerMode),
2248 SetMicrophoneMode(MicrophoneMode),
2251 Reset(ResetType),
2254 DisplayText {
2257 text: String,
2258 },
2259 ClearDisplay,
2262 ForwardStatus {
2265 line_instance: u32,
2266 forward_all: Option<String>,
2267 forward_busy: Option<String>,
2268 forward_no_answer: Option<String>,
2269 },
2270 SpeedDialStatus {
2273 instance: u32,
2274 number: String,
2275 display_name: String,
2276 },
2277 DialedNumber {
2280 number: String,
2281 line_instance: u32,
2282 call_reference: u32,
2283 },
2284 StartMediaFailureDetection(MediaFailureDetection),
2287 UserToDeviceData(UserDataMessage),
2290 UserToDeviceDataV1(UserDataV1Message),
2293 FeatureStatus {
2296 instance: u32,
2297 button_type: ButtonType,
2298 label: String,
2299 state: u32,
2301 },
2302 ServiceUrlStatus {
2305 index: u32,
2306 url: String,
2307 label: String,
2308 extension_text: String,
2310 },
2311 CallSelectStatus {
2314 status: u32,
2316 call_reference: u32,
2317 line_instance: u32,
2318 },
2319 PortRequest(PortRequest),
2322 PortClose(PortClose),
2325 OpenMultimediaChannel(OpenMultimediaChannel),
2328 StartMultimediaTransmission(StartMultimediaTransmission),
2331 MiscellaneousCommand(MiscellaneousCommand),
2334 SubscriptionStatus {
2337 transaction_id: u32,
2338 feature_id: u32,
2339 timer_seconds: u32,
2340 cause: SubscriptionCause,
2341 },
2342 Notification {
2345 transaction_id: u32,
2346 feature_id: u32,
2347 status: BusyLampFieldState,
2348 text: String,
2349 },
2350 CallHistoryDisposition {
2353 disposition: CallHistoryDisposition,
2354 line_instance: u32,
2355 call_reference: u32,
2356 },
2357 CallCountResponse,
2360 RecordingStatus {
2363 call_reference: u32,
2364 active: bool,
2365 },
2366 KnownOpaque(KnownOpaqueMessage),
2369 Unknown(RawMessage),
2372}
2373
2374#[cfg(test)]
2375mod tests {
2376 use super::wire::{CodecError, Frame, FrameDecoder};
2377 use super::*;
2378
2379 #[test]
2380 fn protocol_fillers_have_semantic_defaults() {
2381 assert_eq!(
2382 ButtonTemplateEntry::default(),
2383 ButtonTemplateEntry {
2384 instance: 0,
2385 button_type: ButtonType::Unused,
2386 }
2387 );
2388 assert_eq!(
2389 MessageWaitingCounts::default(),
2390 MessageWaitingCounts { new: 0, old: 0 }
2391 );
2392 }
2393
2394 const fn test_rtp_payload_number(value: u32) -> RtpPayloadNumber {
2395 match RtpPayloadNumber::new(value) {
2396 Ok(value) => value,
2397 Err(_) => panic!("test RTP payload number is out of range"),
2398 }
2399 }
2400
2401 fn decode_frame(bytes: &[u8]) -> Frame {
2402 FrameDecoder::new().push(bytes).unwrap().remove(0)
2403 }
2404
2405 fn assert_contract_alignment(frame: &Frame) {
2406 use super::catalog::PayloadLayout;
2407
2408 let contract = frame.message_type().contract().unwrap();
2409 if !matches!(
2410 contract.payload_layout,
2411 PayloadLayout::Opaque
2412 | PayloadLayout::BoundedOpaque
2413 | PayloadLayout::BoundedPreserved
2414 | PayloadLayout::VersionAndLengthSelected
2415 | PayloadLayout::MinimumLengthPreserved
2416 ) {
2417 assert_eq!(frame.payload.len() % 4, 0, "{}", contract.id);
2418 }
2419 }
2420
2421 fn assert_client_round_trip(message: ClientMessage, protocol: ProtocolVersion) {
2422 let frame = decode_frame(&message.encode(protocol).unwrap());
2423 assert_contract_alignment(&frame);
2424 assert_eq!(
2425 ClientMessage::decode_with_version(frame, protocol).unwrap(),
2426 message
2427 );
2428 }
2429
2430 fn assert_server_round_trip(message: ServerMessage, protocol: ProtocolVersion) {
2431 let frame = decode_frame(&message.encode(protocol).unwrap());
2432 assert_contract_alignment(&frame);
2433 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
2434 }
2435
2436 fn assert_control_round_trip(message: ControlMessage, protocol: ProtocolVersion) {
2437 let frame = decode_frame(&message.encode(protocol).unwrap());
2438 assert_contract_alignment(&frame);
2439 assert_eq!(ControlMessage::decode(frame, protocol).unwrap(), message);
2440 }
2441
2442 #[test]
2443 fn multimedia_payload_exposes_only_typed_construction() {
2444 let capability = MultimediaVideoCapability::new(
2445 1_024,
2446 [MultimediaPictureFormat {
2447 format: VideoFormat::Cif4,
2448 minimum_picture_interval: 2,
2449 }],
2450 7,
2451 MultimediaVideoCapabilityArm::H264 {
2452 profile: 100,
2453 level: 42,
2454 custom_max_mbps: 40_500,
2455 custom_max_fs: 1_620,
2456 custom_max_dpb: 8_100,
2457 custom_max_br_and_cpb: 10_000,
2458 },
2459 )
2460 .unwrap();
2461 let payload = MultimediaPayload::new(test_rtp_payload_number(97), capability.clone());
2462 assert_eq!(payload.payload_number().get(), 97);
2463 assert_eq!(payload.descriptor().rfc_number(), 0);
2464 assert_eq!(payload.codec(), Codec::H264);
2465 assert_eq!(payload.video_capability(), Some(&capability));
2466
2467 let packetized = MultimediaPayload::with_descriptor(
2468 MultimediaPayloadDescriptor::new(4, payload.payload_number()),
2469 capability.clone(),
2470 );
2471 assert_eq!(packetized.descriptor().rfc_number(), 4);
2472 assert_eq!(packetized.payload_number(), payload.payload_number());
2473
2474 let debug = format!("{capability:?}");
2475 assert!(debug.contains("bit_rate: 1024"));
2476 assert!(!debug.contains("preserved_wire"));
2477 assert_eq!(
2478 RtpPayloadNumber::new(128),
2479 Err(RtpPayloadNumberError { actual: 128 })
2480 );
2481 }
2482
2483 #[test]
2484 fn multimedia_picture_formats_are_bounded_before_payload_construction() {
2485 let formats = [MultimediaPictureFormat {
2486 format: VideoFormat::Cif,
2487 minimum_picture_interval: 1,
2488 }; MAX_MULTIMEDIA_PICTURE_FORMATS + 1];
2489 assert_eq!(
2490 MultimediaVideoCapability::new(
2491 1_024,
2492 formats,
2493 0,
2494 MultimediaVideoCapabilityArm::H261 {
2495 temporal_spatial_trade_off_capability: 0,
2496 still_image_transmission: 0,
2497 },
2498 )
2499 .unwrap_err(),
2500 MultimediaCapabilityError {
2501 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
2502 actual: MAX_MULTIMEDIA_PICTURE_FORMATS + 1,
2503 }
2504 );
2505 }
2506
2507 #[test]
2508 fn media_request_identity_is_nonzero_and_exhaustion_never_wraps() {
2509 assert_eq!(MediaRequestToken::new(0), None);
2510 let token = MediaRequestToken::new(7).unwrap();
2511 assert_eq!(MediaRequestIdentity::new(0, token), None);
2512
2513 let first = MediaRequestIdentity::new(1, token).unwrap();
2514 let second = first.checked_next().unwrap();
2515 assert_eq!(second.generation(), 2);
2516 assert_eq!(second.token().get(), 8);
2517
2518 assert_eq!(
2519 MediaRequestToken::new(u32::MAX).unwrap().checked_next(),
2520 None
2521 );
2522 let exhausted_generation =
2523 MediaRequestIdentity::new(u64::MAX, MediaRequestToken::new(1).unwrap()).unwrap();
2524 assert_eq!(exhausted_generation.checked_next(), None);
2525 }
2526
2527 #[test]
2528 fn media_request_identity_matches_only_the_current_wire_token() {
2529 let identity =
2530 MediaRequestIdentity::new(2, MediaRequestToken::new(0x1020_3040).unwrap()).unwrap();
2531
2532 assert!(identity.accepts_ack(0x1020_3040, 0, 77));
2533 assert!(identity.accepts_ack(0x1020_3040, 77, 77));
2534 assert!(!identity.accepts_ack(0x1020_3040, 78, 77));
2535 assert!(!identity.accepts_ack(0x1020_303f, 77, 77));
2536 }
2537
2538 #[test]
2539 fn zero_party_fallback_cannot_settle_a_reopened_media_generation() {
2540 let first = MediaRequestIdentity::new(1, MediaRequestToken::new(700).unwrap()).unwrap();
2541 let reopened = first.checked_next().unwrap();
2542
2543 assert!(first.accepts_ack(0, 42, 42));
2545 assert!(!first.accepts_ack(0, 0, 42));
2546
2547 assert!(!reopened.accepts_ack(0, 42, 42));
2549 assert!(!reopened.accepts_ack(first.token().get(), 42, 42));
2550 assert!(reopened.accepts_ack(reopened.token().get(), 42, 42));
2551 }
2552
2553 #[test]
2554 fn decodes_7962_off_hook_capture_shape() {
2555 let frame = Frame::new(22, wire_id::OFF_HOOK, vec![1, 0, 0, 0, 42, 0, 0, 0]);
2556 assert_eq!(
2557 ClientMessage::decode(frame).unwrap(),
2558 ClientMessage::OffHook {
2559 line_instance: 1,
2560 call_reference: 42
2561 }
2562 );
2563 }
2564
2565 #[test]
2566 fn decodes_7961_v22_three_word_keypad_capture_shape() {
2567 let payload: Vec<_> = [8_u32, 1, 1]
2568 .into_iter()
2569 .flat_map(u32::to_le_bytes)
2570 .collect();
2571 let frame = Frame::new(22, wire_id::KEYPAD_BUTTON, payload.clone());
2572 let decoded = ClientMessage::decode(frame).unwrap();
2573 assert_eq!(
2574 decoded,
2575 ClientMessage::KeypadButton {
2576 button: Digit::Number(8),
2577 line_instance: 1,
2578 call_reference: 1,
2579 wire_layout: Some(KeypadButtonWireLayout::WithCallIdentity),
2580 }
2581 );
2582 let encoded = FrameDecoder::new()
2583 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
2584 .unwrap()
2585 .remove(0);
2586 assert_eq!(encoded.payload, payload);
2587 }
2588
2589 #[test]
2590 fn register_ack_is_protocol_zero_and_has_expected_fields() {
2591 let bytes = ServerMessage::RegisterAck {
2592 keepalive_seconds: 30,
2593 secondary_keepalive_seconds: 45,
2594 protocol: ProtocolVersion::V22,
2595 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2596 date_template: DateTemplate::default(),
2597 }
2598 .encode(ProtocolVersion::V22)
2599 .unwrap();
2600 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
2601 assert_eq!(frame.protocol_version, 0);
2602 assert_eq!(frame.message_id, wire_id::REGISTER_ACK);
2603 assert_eq!(
2604 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
2605 ServerMessage::RegisterAck {
2606 keepalive_seconds: 30,
2607 secondary_keepalive_seconds: 45,
2608 protocol: ProtocolVersion::V22,
2609 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2610 date_template: DateTemplate::default(),
2611 }
2612 );
2613 }
2614
2615 #[test]
2616 fn media_layout_sizes_match_supported_wire_specs() {
2617 let endpoint = MediaEndpoint {
2618 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2619 rtp_port: 4000,
2620 rtcp_port: 4001,
2621 codec: Codec::Pcmu,
2622 packet_ms: 20,
2623 max_frames_per_packet: 1,
2624 telephone_event_payload: 101,
2625 };
2626 let start = ServerMessage::StartMediaTransmission {
2627 call_reference: 7,
2628 passthrough_party_id: 9,
2629 endpoint,
2630 silence_suppression: SilenceSuppression::Off,
2631 traffic_class: crate::types::MediaTrafficClass::from_wire(184),
2632 encryption: None,
2633 wire: None,
2634 }
2635 .encode(ProtocolVersion::V17)
2636 .unwrap();
2637 assert_eq!(start.len(), 144); assert_eq!(&start[52..56], &184_u32.to_le_bytes());
2639 assert_eq!(&start[140..144], &1_u32.to_le_bytes());
2640 let open = ServerMessage::OpenReceiveChannel {
2641 call_reference: 7,
2642 passthrough_party_id: 9,
2643 packet_ms: 20,
2644 codec: Codec::Pcmu,
2645 echo_cancellation: EchoCancellation::On,
2646 telephone_event_payload: 101,
2647 source_address: endpoint.address,
2648 source_port: endpoint.rtp_port,
2649 encryption: None,
2650 wire: None,
2651 }
2652 .encode(ProtocolVersion::V17)
2653 .unwrap();
2654 assert_eq!(open.len(), 140); assert_eq!(&open[108..112], &1_u32.to_le_bytes());
2656
2657 let start_v3 = ServerMessage::StartMediaTransmission {
2658 call_reference: 7,
2659 passthrough_party_id: 9,
2660 endpoint,
2661 silence_suppression: SilenceSuppression::Off,
2662 traffic_class: crate::types::MediaTrafficClass::default(),
2663 encryption: None,
2664 wire: None,
2665 }
2666 .encode(ProtocolVersion::V3)
2667 .unwrap();
2668 assert_eq!(start_v3.len(), 120); let open_v3 = ServerMessage::OpenReceiveChannel {
2670 call_reference: 7,
2671 passthrough_party_id: 9,
2672 packet_ms: 20,
2673 codec: Codec::Pcmu,
2674 echo_cancellation: EchoCancellation::On,
2675 telephone_event_payload: 101,
2676 source_address: endpoint.address,
2677 source_port: endpoint.rtp_port,
2678 encryption: None,
2679 wire: None,
2680 }
2681 .encode(ProtocolVersion::V3)
2682 .unwrap();
2683 assert_eq!(open_v3.len(), 104); let start_v22 = ServerMessage::StartMediaTransmission {
2686 call_reference: 7,
2687 passthrough_party_id: 9,
2688 endpoint,
2689 silence_suppression: SilenceSuppression::Off,
2690 traffic_class: crate::types::MediaTrafficClass::default(),
2691 encryption: None,
2692 wire: None,
2693 }
2694 .encode(ProtocolVersion::V22)
2695 .unwrap();
2696 assert_eq!(start_v22.len(), 180); let open_v22 = ServerMessage::OpenReceiveChannel {
2698 call_reference: 7,
2699 passthrough_party_id: 9,
2700 packet_ms: 20,
2701 codec: Codec::Pcmu,
2702 echo_cancellation: EchoCancellation::On,
2703 telephone_event_payload: 101,
2704 source_address: endpoint.address,
2705 source_port: endpoint.rtp_port,
2706 encryption: None,
2707 wire: None,
2708 }
2709 .encode(ProtocolVersion::V22)
2710 .unwrap();
2711 assert_eq!(open_v22.len(), 180); }
2713
2714 #[test]
2715 fn media_close_layouts_consume_the_reference_fields_exactly() {
2716 let close = ServerMessage::CloseReceiveChannel(AudioStreamControl {
2717 conference_id: 6.into(),
2718 passthrough_party_id: 9.into(),
2719 call_reference: 7.into(),
2720 port_handling_flag: 11,
2721 });
2722 let close_v3 = close.encode(ProtocolVersion::V3).unwrap();
2723 assert_eq!(close_v3.len(), 28);
2724 assert_eq!(
2725 ServerMessage::decode(decode_frame(&close_v3), ProtocolVersion::V3).unwrap(),
2726 close
2727 );
2728 let close_v5 = close.encode(ProtocolVersion::V5).unwrap();
2729 assert_eq!(close_v5.len(), 28);
2730 assert_eq!(
2731 ServerMessage::decode(decode_frame(&close_v5), ProtocolVersion::V5).unwrap(),
2732 close
2733 );
2734
2735 let stop = ServerMessage::StopMediaTransmission(AudioStreamControl {
2736 conference_id: 6.into(),
2737 passthrough_party_id: 9.into(),
2738 call_reference: 7.into(),
2739 port_handling_flag: 11,
2740 });
2741 let bytes = stop.encode(ProtocolVersion::V22).unwrap();
2742 assert_eq!(bytes.len(), 28);
2743 assert_eq!(
2744 ServerMessage::decode(decode_frame(&bytes), ProtocolVersion::V22).unwrap(),
2745 stop
2746 );
2747
2748 let mut trailing = decode_frame(&bytes);
2749 trailing.payload.extend_from_slice(&[0; 4]);
2750 assert!(matches!(
2751 ServerMessage::decode(trailing, ProtocolVersion::V22),
2752 Err(CodecError::TrailingBytes { count: 4, .. })
2753 ));
2754 }
2755
2756 #[test]
2757 fn audio_packetization_round_trips_without_default_substitution() {
2758 let endpoint = MediaEndpoint {
2759 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2760 rtp_port: 4000,
2761 rtcp_port: 4001,
2762 codec: Codec::G72264k,
2763 packet_ms: 30,
2764 max_frames_per_packet: 2,
2765 telephone_event_payload: 101,
2766 };
2767 for protocol in [
2768 ProtocolVersion::V3,
2769 ProtocolVersion::V17,
2770 ProtocolVersion::V22,
2771 ] {
2772 let (source_address, source_port) = if protocol.wire() < 12 {
2773 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2774 } else {
2775 (endpoint.address, endpoint.rtp_port)
2776 };
2777 assert_server_round_trip(
2778 ServerMessage::OpenReceiveChannel {
2779 call_reference: 7,
2780 passthrough_party_id: 9,
2781 packet_ms: 30,
2782 codec: Codec::G72264k,
2783 echo_cancellation: EchoCancellation::On,
2784 telephone_event_payload: 101,
2785 source_address,
2786 source_port,
2787 encryption: None,
2788 wire: None,
2789 },
2790 protocol,
2791 );
2792 assert_server_round_trip(
2793 ServerMessage::StartMediaTransmission {
2794 call_reference: 7,
2795 passthrough_party_id: 9,
2796 endpoint,
2797 silence_suppression: SilenceSuppression::On,
2798 traffic_class: crate::types::MediaTrafficClass::default(),
2799 encryption: None,
2800 wire: None,
2801 },
2802 protocol,
2803 );
2804 assert_client_round_trip(
2805 ClientMessage::MediaTransmissionFailure {
2806 conference_id: 7,
2807 passthrough_party_id: 9,
2808 address: endpoint.address,
2809 port: endpoint.rtp_port,
2810 call_reference: 7,
2811 status: MediaStatus::UnspecifiedError,
2812 },
2813 protocol,
2814 );
2815 }
2816 }
2817
2818 #[test]
2819 fn ipv6_audio_endpoints_require_and_round_trip_extended_layouts() {
2820 let address: IpAddr = "2001:db8::42".parse().unwrap();
2821 let endpoint = MediaEndpoint {
2822 address,
2823 rtp_port: 40_000,
2824 rtcp_port: 40_001,
2825 codec: Codec::G72264k,
2826 packet_ms: 20,
2827 max_frames_per_packet: 1,
2828 telephone_event_payload: 101,
2829 };
2830 let start = ServerMessage::StartMediaTransmission {
2831 call_reference: 7,
2832 passthrough_party_id: 9,
2833 endpoint,
2834 silence_suppression: SilenceSuppression::Off,
2835 traffic_class: crate::types::MediaTrafficClass::default(),
2836 encryption: None,
2837 wire: None,
2838 };
2839 let receive_ack = ClientMessage::OpenReceiveChannelAck {
2840 status: MediaStatus::Ok,
2841 address,
2842 port: endpoint.rtp_port,
2843 passthrough_party_id: 9,
2844 call_reference: 7,
2845 };
2846 let transmit_ack = ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
2847 conference_id: 6,
2848 passthrough_party_id: 9,
2849 call_reference: 7,
2850 status: MediaStatus::Ok,
2851 address,
2852 port: endpoint.rtp_port,
2853 wire: None,
2854 });
2855 let failure = ClientMessage::MediaTransmissionFailure {
2856 conference_id: 7,
2857 passthrough_party_id: 9,
2858 address,
2859 port: endpoint.rtp_port,
2860 call_reference: 7,
2861 status: MediaStatus::UnspecifiedError,
2862 };
2863
2864 for protocol in [ProtocolVersion::V17, ProtocolVersion::V22] {
2865 assert_server_round_trip(start.clone(), protocol);
2866 assert_client_round_trip(receive_ack.clone(), protocol);
2867 assert_client_round_trip(transmit_ack.clone(), protocol);
2868 assert_client_round_trip(failure.clone(), protocol);
2869 }
2870 for result in [
2871 start.encode(ProtocolVersion::V16),
2872 receive_ack.encode(ProtocolVersion::V16),
2873 transmit_ack.encode(ProtocolVersion::V16),
2874 failure.encode(ProtocolVersion::V16),
2875 failure.encode(ProtocolVersion::V3),
2876 ] {
2877 assert!(matches!(
2878 result,
2879 Err(CodecError::InvalidValue {
2880 field: "IP address family for pre-v17 protocol"
2881 | "IP address family for this protocol version",
2882 ..
2883 })
2884 ));
2885 }
2886 }
2887
2888 #[test]
2889 fn skinny_dtmf_disables_the_telephone_event_payload_in_both_directions() {
2890 let endpoint = MediaEndpoint {
2891 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2892 rtp_port: 4000,
2893 rtcp_port: 4001,
2894 codec: Codec::Pcmu,
2895 packet_ms: 20,
2896 max_frames_per_packet: 1,
2897 telephone_event_payload: 0,
2898 };
2899 for protocol in [
2900 ProtocolVersion::V3,
2901 ProtocolVersion::V17,
2902 ProtocolVersion::V22,
2903 ] {
2904 let (source_address, source_port) = if protocol.wire() < 12 {
2905 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2906 } else {
2907 (endpoint.address, endpoint.rtp_port)
2908 };
2909 assert_server_round_trip(
2910 ServerMessage::OpenReceiveChannel {
2911 call_reference: 7,
2912 passthrough_party_id: 9,
2913 packet_ms: 20,
2914 codec: Codec::Pcmu,
2915 echo_cancellation: EchoCancellation::On,
2916 telephone_event_payload: 0,
2917 source_address,
2918 source_port,
2919 encryption: None,
2920 wire: None,
2921 },
2922 protocol,
2923 );
2924 assert_server_round_trip(
2925 ServerMessage::StartMediaTransmission {
2926 call_reference: 7,
2927 passthrough_party_id: 9,
2928 endpoint,
2929 silence_suppression: SilenceSuppression::Off,
2930 traffic_class: crate::types::MediaTrafficClass::default(),
2931 encryption: None,
2932 wire: None,
2933 },
2934 protocol,
2935 );
2936 }
2937 }
2938
2939 #[test]
2940 fn open_receive_wildcard_source_round_trips_for_all_supported_layouts() {
2941 for protocol in [
2942 ProtocolVersion::V3,
2943 ProtocolVersion::V17,
2944 ProtocolVersion::V22,
2945 ] {
2946 assert_server_round_trip(
2947 ServerMessage::OpenReceiveChannel {
2948 call_reference: 1,
2949 passthrough_party_id: 1,
2950 packet_ms: 20,
2951 codec: Codec::Pcma,
2952 echo_cancellation: EchoCancellation::Off,
2953 telephone_event_payload: 101,
2954 source_address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
2955 source_port: 0,
2956 encryption: None,
2957 wire: None,
2958 },
2959 protocol,
2960 );
2961 }
2962 }
2963
2964 #[test]
2965 fn media_encryption_round_trips_without_exposing_key_material() {
2966 let key = b"private-key-1234";
2967 let salt = b"private-salt-123";
2968 let encryption =
2969 MediaEncryption::new(EncryptionMethod::Aes128HmacSha1_80, key, salt, 1, 64).unwrap();
2970 assert_eq!(encryption.key(), key);
2971 assert_eq!(encryption.salt(), salt);
2972
2973 let debug = format!("{encryption:?}");
2974 assert!(debug.contains("<redacted>"));
2975 assert!(!debug.contains("112, 114, 105, 118, 97, 116, 101"));
2976 assert!(!debug.contains("private-key"));
2977 let endpoint = MediaEndpoint {
2978 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
2979 rtp_port: 40_000,
2980 rtcp_port: 40_001,
2981 codec: Codec::Pcmu,
2982 packet_ms: 20,
2983 max_frames_per_packet: 1,
2984 telephone_event_payload: 101,
2985 };
2986
2987 for protocol in [
2988 ProtocolVersion::new(12).unwrap(),
2989 ProtocolVersion::V17,
2990 ProtocolVersion::V22,
2991 ] {
2992 let open = ServerMessage::OpenReceiveChannel {
2993 call_reference: 7,
2994 passthrough_party_id: 9,
2995 packet_ms: 20,
2996 codec: Codec::Pcmu,
2997 echo_cancellation: EchoCancellation::On,
2998 telephone_event_payload: 101,
2999 source_address: endpoint.address,
3000 source_port: endpoint.rtp_port,
3001 encryption: Some(encryption.clone()),
3002 wire: None,
3003 };
3004 let open_debug = format!("{open:?}");
3005 assert!(open_debug.contains("<redacted>"));
3006 assert!(!open_debug.contains("112, 114, 105, 118, 97, 116, 101"));
3007 assert_server_round_trip(open, protocol);
3008 assert_server_round_trip(
3009 ServerMessage::StartMediaTransmission {
3010 call_reference: 7,
3011 passthrough_party_id: 9,
3012 endpoint,
3013 silence_suppression: SilenceSuppression::Off,
3014 traffic_class: crate::types::MediaTrafficClass::default(),
3015 encryption: Some(encryption.clone()),
3016 wire: None,
3017 },
3018 protocol,
3019 );
3020 }
3021 }
3022
3023 #[test]
3024 fn media_encryption_rejects_oversized_secrets_with_metadata_only_errors() {
3025 let oversized_key = [0xa5; 17];
3026 let error = MediaEncryption::new(
3027 EncryptionMethod::Aes128HmacSha1_32,
3028 &oversized_key,
3029 &[],
3030 0,
3031 0,
3032 )
3033 .unwrap_err();
3034 assert!(matches!(
3035 error,
3036 CodecError::SecretTooLong {
3037 field: "media encryption key",
3038 actual: 17,
3039 maximum: 16,
3040 }
3041 ));
3042 assert!(!error.to_string().contains("165"));
3043
3044 let oversized_salt = [0x5a; 17];
3045 let error = MediaEncryption::new(
3046 EncryptionMethod::Aes128HmacSha1_32,
3047 &[],
3048 &oversized_salt,
3049 0,
3050 0,
3051 )
3052 .unwrap_err();
3053 assert!(matches!(
3054 error,
3055 CodecError::SecretTooLong {
3056 field: "media encryption salt",
3057 actual: 17,
3058 maximum: 16,
3059 }
3060 ));
3061 assert!(!error.to_string().contains("90"));
3062 }
3063
3064 #[test]
3065 fn common_client_messages_round_trip_semantically() {
3066 assert_client_round_trip(
3067 ClientMessage::FeatureStatusRequest {
3068 index: 7,
3069 capabilities: 1,
3070 },
3071 ProtocolVersion::V22,
3072 );
3073 assert_client_round_trip(
3074 ClientMessage::OffHookWithCallingParty {
3075 calling_party_number: "1001".into(),
3076 voice_mailbox: "5001".into(),
3077 line_instance: 1,
3078 },
3079 ProtocolVersion::V3,
3080 );
3081 assert_client_round_trip(
3082 ClientMessage::RegisterToken(RegisterTokenMessage {
3083 device_id: DeviceId::new("SEP001122334455").unwrap(),
3084 device_instance: 2,
3085 address: "2001:db8::42".parse().unwrap(),
3086 device_type: DeviceType::Cisco7962,
3087 flags: 6,
3088 }),
3089 ProtocolVersion::V22,
3090 );
3091 assert_control_round_trip(
3092 ControlMessage::MediaResourceNotification(MediaResourceNotification {
3093 device_type: DeviceType::Unknown(0xfeed),
3094 in_service_streams: 2,
3095 max_streams_per_conference: 4,
3096 out_of_service_streams: 1,
3097 }),
3098 ProtocolVersion::V17,
3099 );
3100 assert_client_round_trip(
3101 ClientMessage::SubscriptionStatusRequest(SubscriptionRequest {
3102 transaction_id: 0x4b,
3103 feature_id: 1,
3104 timer_seconds: 30,
3105 subscription_id: "4000".into(),
3106 }),
3107 ProtocolVersion::V22,
3108 );
3109 for message in [
3110 ClientMessage::SubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
3111 payload_type: 101,
3112 conference_id: 42,
3113 passthrough_party_id: 7,
3114 }),
3115 ClientMessage::UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
3116 payload_type: 102,
3117 conference_id: 43,
3118 passthrough_party_id: 8,
3119 }),
3120 ] {
3121 let encoded = message.encode(ProtocolVersion::V22).unwrap();
3122 let frame = decode_frame(&encoded);
3123 assert_eq!(frame.payload.len(), 12);
3124 assert_eq!(
3125 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
3126 message
3127 );
3128 }
3129 assert_client_round_trip(
3130 ClientMessage::DeviceToUserDataV1(UserDataV1Message {
3131 application_id: 7,
3132 line_instance: 1,
3133 call_reference: 42,
3134 transaction_id: 9,
3135 sequence_flag: 1,
3136 display_priority: 2,
3137 conference_id: 42,
3138 application_instance_id: 3,
3139 routing: 4,
3140 data: b"<CiscoIPPhoneText/>".to_vec(),
3141 }),
3142 ProtocolVersion::V17,
3143 );
3144 assert_client_round_trip(
3145 ClientMessage::DeviceToUserDataResponse(UserDataMessage {
3146 application_id: 8,
3147 line_instance: 2,
3148 call_reference: 43,
3149 transaction_id: 10,
3150 data: b"<CiscoIPPhoneResponse/>".to_vec(),
3151 }),
3152 ProtocolVersion::V17,
3153 );
3154 assert_client_round_trip(
3155 ClientMessage::DeviceToUserData(UserDataMessage {
3156 application_id: 9,
3157 line_instance: 2,
3158 call_reference: 44,
3159 transaction_id: 11,
3160 data: b"<CiscoIPPhoneInput/>".to_vec(),
3161 }),
3162 ProtocolVersion::V17,
3163 );
3164 assert_client_round_trip(
3165 ClientMessage::DeviceToUserDataResponseV1(UserDataV1Message {
3166 application_id: 9,
3167 line_instance: 2,
3168 call_reference: 44,
3169 transaction_id: 11,
3170 sequence_flag: 2,
3171 display_priority: 1,
3172 conference_id: 44,
3173 application_instance_id: 9,
3174 routing: 1,
3175 data: b"<CiscoIPPhoneResponse/>".to_vec(),
3176 }),
3177 ProtocolVersion::V17,
3178 );
3179 assert_client_round_trip(
3180 ClientMessage::LocationInfo {
3181 xml: "<location><building>west</building></location>".into(),
3182 },
3183 ProtocolVersion::V22,
3184 );
3185 assert_client_round_trip(
3186 ClientMessage::XmlAlarm(
3187 XmlAlarmMessage::from_xml(b"<alarm><severity>warning</severity></alarm>").unwrap(),
3188 ),
3189 ProtocolVersion::V22,
3190 );
3191 assert_client_round_trip(
3192 ClientMessage::CallCountRequest { value: 2 },
3193 ProtocolVersion::V22,
3194 );
3195 assert_control_round_trip(
3196 ControlMessage::PortResponse(PortEndpoint {
3197 conference_id: 42,
3198 call_reference: 42,
3199 passthrough_party_id: 8,
3200 address: "2001:db8::8".parse().unwrap(),
3201 rtp_port: 16_000,
3202 rtcp_port: 16_001,
3203 media_type: Some(MediaType::Audio),
3204 }),
3205 ProtocolVersion::V22,
3206 );
3207 assert_control_round_trip(
3208 ControlMessage::CreateConferenceResponse(CreateConferenceResponse {
3209 conference_id: ConferenceId::new(42),
3210 result: CreateConferenceResult::Ok,
3211 passthrough_data: vec![1, 2, 3],
3212 }),
3213 ProtocolVersion::V22,
3214 );
3215 assert_control_round_trip(
3216 ControlMessage::DeleteConferenceResponse {
3217 conference_id: ConferenceId::new(42),
3218 result: DeleteConferenceResult::ConferenceDoesNotExist,
3219 },
3220 ProtocolVersion::V22,
3221 );
3222 assert_control_round_trip(
3223 ControlMessage::ModifyConferenceResponse(ModifyConferenceResponse {
3224 conference_id: ConferenceId::new(42),
3225 result: ModifyConferenceResult::MoreActiveCallsThanReserved,
3226 passthrough_data: vec![4, 5],
3227 }),
3228 ProtocolVersion::V22,
3229 );
3230 assert_control_round_trip(
3231 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3232 last: 1,
3233 entries: vec![AuditConferenceEntry {
3234 conference_id: ConferenceId::new(42),
3235 resource_type: ConferenceResourceType::Conference,
3236 reserved_participants: 8,
3237 active_participants: 3,
3238 application_id: ApplicationId::new(7),
3239 application_conference_id: "festival-42".into(),
3240 application_data: "main-stage".into(),
3241 }],
3242 }),
3243 ProtocolVersion::V22,
3244 );
3245 assert_control_round_trip(
3246 ControlMessage::AddParticipantResponse(AddParticipantResponse {
3247 conference_id: ConferenceId::new(42),
3248 call_reference: CallReference::new(100),
3249 result: AddParticipantResult::Ok,
3250 bridge_participant_id: BoundedBytes::try_from(vec![3; 257]).unwrap(),
3251 }),
3252 ProtocolVersion::V22,
3253 );
3254 assert_control_round_trip(
3255 ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
3256 result: AuditParticipantResult::Ok,
3257 last: 1,
3258 conference_id: ConferenceId::new(42),
3259 number_of_entries: 2,
3260 participant_entries: vec![1, 2, 3, 4],
3261 }),
3262 ProtocolVersion::V22,
3263 );
3264 }
3265
3266 #[test]
3267 fn common_server_messages_round_trip_semantically() {
3268 assert_server_round_trip(
3269 ServerMessage::SpeedDialStatus {
3270 instance: 7,
3271 number: "2001".into(),
3272 display_name: "Reception".into(),
3273 },
3274 ProtocolVersion::V3,
3275 );
3276 assert_server_round_trip(
3277 ServerMessage::ServiceUrlStatus {
3278 index: 4,
3279 url: "http://services.invalid/directory".into(),
3280 label: "Directory".into(),
3281 extension_text: String::new(),
3282 },
3283 ProtocolVersion::V3,
3284 );
3285 for protocol in [
3286 ProtocolVersion::V3,
3287 ProtocolVersion::V17,
3288 ProtocolVersion::V22,
3289 ] {
3290 assert_server_round_trip(
3291 ServerMessage::ConnectionStatisticsRequest {
3292 directory_number: "1001".into(),
3293 call_reference: 42,
3294 processing: StatisticsProcessing::DoNotClear,
3295 },
3296 protocol,
3297 );
3298 }
3299 assert_server_round_trip(
3300 ServerMessage::DisplayPriorityNotify {
3301 timeout_seconds: 5,
3302 priority: NotificationPriority::Voicemail,
3303 text: "Incoming call".into(),
3304 },
3305 ProtocolVersion::V17,
3306 );
3307 assert_server_round_trip(
3308 ServerMessage::FeatureStatus {
3309 instance: 2,
3310 button_type: ButtonType::BlfSpeedDial,
3311 label: "Support".into(),
3312 state: 0x0002_0101,
3313 },
3314 ProtocolVersion::V22,
3315 );
3316 assert_server_round_trip(
3317 ServerMessage::PortRequest(PortRequest {
3318 conference_id: 42.into(),
3319 call_reference: 42.into(),
3320 passthrough_party_id: 9.into(),
3321 transport: MediaTransport::Rtp,
3322 address_type: Some(IpAddressType::Ipv4AndIpv6),
3323 media_type: Some(MediaType::Audio),
3324 }),
3325 ProtocolVersion::V22,
3326 );
3327 assert_server_round_trip(
3328 ServerMessage::Notification {
3329 transaction_id: 3,
3330 feature_id: 1,
3331 status: BusyLampFieldState::Unknown(77),
3332 text: "4000".into(),
3333 },
3334 ProtocolVersion::V22,
3335 );
3336 assert_server_round_trip(
3337 ServerMessage::SubscriptionStatus {
3338 transaction_id: 3,
3339 feature_id: 1,
3340 timer_seconds: 30,
3341 cause: SubscriptionCause::Ok,
3342 },
3343 ProtocolVersion::V22,
3344 );
3345 assert_server_round_trip(
3346 ServerMessage::UserToDeviceData(UserDataMessage {
3347 application_id: 7,
3348 line_instance: 1,
3349 call_reference: 42,
3350 transaction_id: 9,
3351 data: b"<CiscoIPPhoneText/>".to_vec(),
3352 }),
3353 ProtocolVersion::V17,
3354 );
3355 assert_server_round_trip(
3356 ServerMessage::UserToDeviceDataV1(UserDataV1Message {
3357 application_id: 7,
3358 line_instance: 1,
3359 call_reference: 42,
3360 transaction_id: 9,
3361 sequence_flag: 2,
3362 display_priority: 1,
3363 conference_id: 42,
3364 application_instance_id: 7,
3365 routing: 1,
3366 data: b"<CiscoIPPhoneMenu/>".to_vec(),
3367 }),
3368 ProtocolVersion::V17,
3369 );
3370 assert_server_round_trip(
3371 ServerMessage::CallHistoryDisposition {
3372 disposition: CallHistoryDisposition::Missed,
3373 line_instance: 1,
3374 call_reference: 42,
3375 },
3376 ProtocolVersion::V22,
3377 );
3378 assert_server_round_trip(ServerMessage::CallCountResponse, ProtocolVersion::V22);
3379 for message in [
3380 ServerMessage::SubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3381 payload_type: 101,
3382 conference_id: 42,
3383 passthrough_party_id: 7,
3384 dtmf_type: 2,
3385 }),
3386 ServerMessage::SubscribeDtmfPayloadError(DtmfPayloadIdentity {
3387 payload_type: 102,
3388 conference_id: 43,
3389 passthrough_party_id: 8,
3390 }),
3391 ServerMessage::UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3392 payload_type: 103,
3393 conference_id: 44,
3394 passthrough_party_id: 9,
3395 dtmf_type: 3,
3396 }),
3397 ServerMessage::UnsubscribeDtmfPayloadError(DtmfPayloadIdentity {
3398 payload_type: 104,
3399 conference_id: 45,
3400 passthrough_party_id: 10,
3401 }),
3402 ] {
3403 let encoded = message.encode(ProtocolVersion::V22).unwrap();
3404 let frame = decode_frame(&encoded);
3405 assert!(matches!(frame.payload.len(), 12 | 16));
3406 assert_eq!(
3407 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
3408 message
3409 );
3410 }
3411 assert_server_round_trip(
3412 ServerMessage::RecordingStatus {
3413 call_reference: 42,
3414 active: true,
3415 },
3416 ProtocolVersion::V22,
3417 );
3418 assert_control_round_trip(
3419 ControlMessage::StartAnnouncement {
3420 announcements: vec![
3421 AnnouncementEntry {
3422 locale: 1,
3423 country: 46,
3424 tone: Tone::Zip,
3425 },
3426 AnnouncementEntry {
3427 locale: 0,
3428 country: 0,
3429 tone: Tone::Silence,
3430 },
3431 AnnouncementEntry {
3432 locale: 2,
3433 country: 1,
3434 tone: Tone::RecorderWarning,
3435 },
3436 ],
3437 end_of_ack: EndOfAnnouncementAck::Required,
3438 conference_id: 42,
3439 matrix_conference_party_ids: vec![7, 0, 9],
3440 hearing_conference_party_mask: 0b101,
3441 play_mode: AnnouncementPlayMode::Continuous,
3442 },
3443 ProtocolVersion::V22,
3444 );
3445 assert_control_round_trip(
3446 ControlMessage::StopAnnouncement { conference_id: 42 },
3447 ProtocolVersion::V22,
3448 );
3449 assert_control_round_trip(
3450 ControlMessage::AnnouncementFinish {
3451 conference_id: 42,
3452 play_status: AnnouncementPlayStatus::Unknown(3),
3453 },
3454 ProtocolVersion::V22,
3455 );
3456 assert_control_round_trip(
3457 ControlMessage::ClearConference {
3458 conference_id: ConferenceId::new(42),
3459 service_number: 3,
3460 },
3461 ProtocolVersion::V22,
3462 );
3463 assert_control_round_trip(
3464 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3465 conference_id: ConferenceId::new(42),
3466 reserved_participants: 8,
3467 resource_type: ConferenceResourceType::Conference,
3468 application_id: ApplicationId::new(7),
3469 application_conference_id: "festival-42".into(),
3470 application_data: "main-stage".into(),
3471 passthrough_data: vec![1, 2, 3],
3472 }),
3473 ProtocolVersion::V22,
3474 );
3475 assert_control_round_trip(
3476 ControlMessage::DeleteConferenceRequest {
3477 conference_id: ConferenceId::new(42),
3478 },
3479 ProtocolVersion::V22,
3480 );
3481 assert_control_round_trip(
3482 ControlMessage::ModifyConferenceRequest(ModifyConferenceRequest {
3483 conference_id: ConferenceId::new(42),
3484 reserved_participants: 12,
3485 application_id: ApplicationId::new(7),
3486 application_conference_id: "festival-42".into(),
3487 application_data: "main-stage".into(),
3488 passthrough_data: vec![4, 5],
3489 }),
3490 ProtocolVersion::V22,
3491 );
3492 assert_control_round_trip(ControlMessage::AuditConferenceRequest, ProtocolVersion::V22);
3493 assert_control_round_trip(
3494 ControlMessage::AddParticipantRequest(AddParticipantRequest {
3495 conference_id: ConferenceId::new(42),
3496 participant: ConferenceParticipant {
3497 call_reference: CallReference::new(100),
3498 presentation_restrictions: PartyInformationRestrictions::CALLING_NUMBER,
3499 name: "Festival Caller".into(),
3500 number: "1001".into(),
3501 conference_name: "Main Stage".into(),
3502 },
3503 }),
3504 ProtocolVersion::V22,
3505 );
3506 assert_control_round_trip(
3507 ControlMessage::DropParticipantRequest {
3508 conference_id: ConferenceId::new(42),
3509 call_reference: CallReference::new(100),
3510 },
3511 ProtocolVersion::V22,
3512 );
3513 assert_control_round_trip(
3514 ControlMessage::AuditParticipantRequest {
3515 conference_id: ConferenceId::new(42),
3516 },
3517 ProtocolVersion::V22,
3518 );
3519 }
3520
3521 #[test]
3522 fn connection_statistics_round_trip_all_layouts_and_redact_opaque_fields() {
3523 let statistics = ConnectionStatistics {
3524 directory_number: "2002".into(),
3525 call_reference: 42,
3526 processing: StatisticsProcessing::Clear,
3527 packets_sent: 100,
3528 octets_sent: 8_000,
3529 packets_received: 98,
3530 octets_received: 7_840,
3531 packets_lost: 2,
3532 jitter_millis: 7,
3533 latency_millis: 18,
3534 quality: ConnectionQualityStatistics::new(b"MLQK=4.5;Secret=opaque".to_vec()).unwrap(),
3535 };
3536 for protocol in [
3537 ProtocolVersion::V3,
3538 ProtocolVersion::V19,
3539 ProtocolVersion::V22,
3540 ] {
3541 assert_client_round_trip(
3542 ClientMessage::ConnectionStatisticsResponse(statistics.clone()),
3543 protocol,
3544 );
3545 }
3546 let debug = format!("{statistics:?}");
3547 assert!(!debug.contains("2002"));
3548 assert!(!debug.contains("Secret"));
3549 assert!(debug.contains("byte_count"));
3550 assert!(matches!(
3551 ConnectionQualityStatistics::new(vec![0; CONNECTION_QUALITY_MAX_BYTES + 1]),
3552 Err(CodecError::CountTooLarge {
3553 field: "quality statistics",
3554 maximum: CONNECTION_QUALITY_MAX_BYTES,
3555 ..
3556 })
3557 ));
3558 }
3559
3560 #[test]
3561 fn dtmf_subscription_messages_require_their_exact_word_layouts() {
3562 for message_id in [
3563 wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES,
3564 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_RES,
3565 ] {
3566 assert!(ClientMessage::decode(Frame::new(22, message_id, Vec::new())).is_err());
3567 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 11])).is_err());
3568 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 12])).is_ok());
3569 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 13])).is_err());
3570 }
3571 for (message_id, size) in [
3572 (wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3573 (wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3574 (wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3575 (wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3576 ] {
3577 assert!(
3578 ServerMessage::decode(
3579 Frame::new(22, message_id, vec![0; size - 1]),
3580 ProtocolVersion::V22,
3581 )
3582 .is_err()
3583 );
3584 assert!(
3585 ServerMessage::decode(
3586 Frame::new(22, message_id, vec![0; size]),
3587 ProtocolVersion::V22,
3588 )
3589 .is_ok()
3590 );
3591 assert!(
3592 ServerMessage::decode(
3593 Frame::new(22, message_id, vec![0; size + 1]),
3594 ProtocolVersion::V22,
3595 )
3596 .is_err()
3597 );
3598 }
3599 }
3600
3601 #[test]
3602 fn announcement_lists_enforce_station_bounds() {
3603 let error = ServerMessage::StartAnnouncement {
3604 announcements: vec![
3605 AnnouncementEntry {
3606 locale: 1,
3607 country: 1,
3608 tone: Tone::Zip,
3609 };
3610 33
3611 ],
3612 end_of_ack: 0,
3613 conference_id: 1,
3614 matrix_conference_party_ids: Vec::new(),
3615 hearing_conference_party_mask: 0,
3616 play_mode: 0,
3617 }
3618 .encode(ProtocolVersion::V22)
3619 .unwrap_err();
3620 assert!(matches!(
3621 error,
3622 CodecError::CountTooLarge {
3623 field: "announcements",
3624 count: 33,
3625 maximum: 32,
3626 ..
3627 }
3628 ));
3629
3630 let error = ServerMessage::StartAnnouncement {
3631 announcements: Vec::new(),
3632 end_of_ack: 0,
3633 conference_id: 1,
3634 matrix_conference_party_ids: (1..=17).collect(),
3635 hearing_conference_party_mask: 0,
3636 play_mode: 0,
3637 }
3638 .encode(ProtocolVersion::V22)
3639 .unwrap_err();
3640 assert!(matches!(
3641 error,
3642 CodecError::CountTooLarge {
3643 field: "matrix conference party identifiers",
3644 count: 17,
3645 maximum: 16,
3646 ..
3647 }
3648 ));
3649 }
3650
3651 #[test]
3652 fn enbloc_uses_the_protocol_19_alignment_boundary() {
3653 for (protocol, payload_len, line_offset) in [
3654 (ProtocolVersion::V18, 28, 24),
3655 (ProtocolVersion::V19, 32, 28),
3656 ] {
3657 let message = ClientMessage::EnblocCall {
3658 called_party: "9801".into(),
3659 line_instance: 3,
3660 };
3661 let frame = FrameDecoder::new()
3662 .push(&message.encode(protocol).unwrap())
3663 .unwrap()
3664 .remove(0);
3665 assert_eq!(frame.payload.len(), payload_len);
3666 assert_eq!(
3667 &frame.payload[line_offset..line_offset + 4],
3668 &3_u32.to_le_bytes()
3669 );
3670 assert_eq!(
3671 ClientMessage::decode_with_version(frame, protocol).unwrap(),
3672 message
3673 );
3674 }
3675 }
3676
3677 #[test]
3678 fn supplemental_client_messages_have_typed_layouts() {
3679 let ports = ClientMessage::MediaPortList(MediaPortList {
3680 rtp_ports: vec![16_000, 16_002],
3681 });
3682 let frame = decode_frame(&ports.encode(ProtocolVersion::V22).unwrap());
3683 assert_eq!(frame.message_id, wire_id::MEDIA_PORT_LIST);
3684 assert_eq!(frame.payload.len(), 68);
3685 assert_eq!(
3686 &frame.payload[..12],
3687 &[2, 0, 0, 0, 0x80, 0x3e, 0, 0, 0x82, 0x3e, 0, 0]
3688 );
3689 assert_eq!(
3690 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
3691 ports
3692 );
3693
3694 let token = ClientMessage::SpcpRegisterToken(SpcpRegisterTokenMessage {
3695 device_id: DeviceId::new("SEP001122334455").unwrap(),
3696 device_instance: 2,
3697 address: Ipv4Addr::new(192, 0, 2, 10),
3698 device_type: DeviceType::Cisco7962,
3699 max_streams: 0x0102_0304,
3700 });
3701 let frame = decode_frame(&token.encode(ProtocolVersion::V22).unwrap());
3702 assert_eq!(frame.message_id, wire_id::SPCP_REGISTER_TOKEN_REQ);
3703 assert_eq!(frame.payload.len(), 36);
3704 assert_eq!(&frame.payload[16..20], &[0; 4]);
3705 assert_eq!(&frame.payload[24..28], &[10, 2, 0, 192]);
3706 assert_eq!(&frame.payload[32..36], &[4, 3, 2, 1]);
3707 assert_eq!(
3708 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
3709 token
3710 );
3711
3712 let oversized = ClientMessage::MediaPortList(MediaPortList {
3713 rtp_ports: vec![16_000; MEDIA_PORT_LIST_MAX_PORTS + 1],
3714 });
3715 assert!(matches!(
3716 oversized.encode(ProtocolVersion::V22),
3717 Err(CodecError::CountTooLarge { .. })
3718 ));
3719
3720 let mut invalid_port = vec![0; 68];
3721 invalid_port[..4].copy_from_slice(&1_u32.to_le_bytes());
3722 invalid_port[4..8].copy_from_slice(&65_536_u32.to_le_bytes());
3723 assert!(matches!(
3724 ClientMessage::decode_with_version(
3725 Frame::new(22, wire_id::MEDIA_PORT_LIST, invalid_port),
3726 ProtocolVersion::V22,
3727 ),
3728 Err(CodecError::InvalidValue {
3729 field: "RTP port",
3730 ..
3731 })
3732 ));
3733 }
3734
3735 #[test]
3736 fn supplemental_server_messages_have_typed_layouts() {
3737 for (message, id, payload) in [
3738 (
3739 ServerMessage::SetHookFlashDetect,
3740 wire_id::SET_HOOK_FLASH_DETECT,
3741 vec![],
3742 ),
3743 (
3744 ServerMessage::StartMediaReception,
3745 wire_id::START_MEDIA_RECEPTION,
3746 vec![],
3747 ),
3748 (
3749 ServerMessage::StopMediaReception {
3750 conference_id: 0x0102_0304.into(),
3751 passthrough_party_id: 0x0506_0708.into(),
3752 },
3753 wire_id::STOP_MEDIA_RECEPTION,
3754 vec![4, 3, 2, 1, 8, 7, 6, 5],
3755 ),
3756 (
3757 ServerMessage::EnunciatorCommand,
3758 wire_id::ENUNCIATOR_COMMAND,
3759 vec![],
3760 ),
3761 (
3762 ServerMessage::SpcpRegisterTokenAck {
3763 features: 0x0102_0304,
3764 },
3765 wire_id::SPCP_REGISTER_TOKEN_ACK,
3766 vec![4, 3, 2, 1],
3767 ),
3768 (
3769 ServerMessage::SpcpRegisterTokenReject {
3770 backoff_seconds: 60,
3771 },
3772 wire_id::SPCP_REGISTER_TOKEN_REJECT,
3773 vec![60, 0, 0, 0],
3774 ),
3775 ] {
3776 let frame = decode_frame(&message.encode(ProtocolVersion::V22).unwrap());
3777 assert_eq!(frame.message_id, id);
3778 assert_eq!(frame.payload, payload);
3779 assert_eq!(
3780 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
3781 message
3782 );
3783 }
3784
3785 assert!(
3786 ServerMessage::decode(
3787 Frame::new(22, wire_id::SET_HOOK_FLASH_DETECT, vec![0; 4]),
3788 ProtocolVersion::V22,
3789 )
3790 .is_err()
3791 );
3792 }
3793
3794 #[test]
3795 fn unknown_messages_are_byte_lossless() {
3796 let unknown_payload = vec![9, 8, 7, 6];
3797 let unknown = ServerMessage::decode(
3798 Frame::new(19, 0xdead_beef, unknown_payload.clone()),
3799 ProtocolVersion::V19,
3800 )
3801 .unwrap();
3802 assert!(matches!(unknown, ServerMessage::Unknown(_)));
3803 let unknown_frame = decode_frame(&unknown.encode(ProtocolVersion::V22).unwrap());
3804 assert_eq!(unknown_frame.message_id, 0xdead_beef);
3805 assert_eq!(unknown_frame.protocol_version, 19);
3806 assert_eq!(unknown_frame.payload, unknown_payload);
3807 }
3808
3809 #[test]
3810 fn opaque_encoding_cannot_bypass_a_typed_contract() {
3811 let message = ClientMessage::KnownOpaque(KnownOpaqueMessage {
3812 id: MessageId::IpPort,
3813 protocol_version: ProtocolVersion::V22.wire(),
3814 payload: BoundedBytes::default(),
3815 });
3816
3817 assert!(matches!(
3818 message.encode(ProtocolVersion::V22),
3819 Err(CodecError::InvalidValue {
3820 message_id: wire_id::IP_PORT,
3821 field: "opaque preservation requires an opaque-only contract",
3822 ..
3823 })
3824 ));
3825 }
3826
3827 #[test]
3828 fn malformed_counts_and_oversized_text_are_rejected() {
3829 assert!(matches!(
3830 ClientMessage::decode(Frame::new(
3831 22,
3832 wire_id::CAPABILITIES_RES,
3833 19_u32.to_le_bytes().to_vec(),
3834 )),
3835 Err(CodecError::CountTooLarge { .. })
3836 ));
3837 assert!(matches!(
3838 ServerMessage::DisplayText {
3839 text: "x".repeat(32),
3840 }
3841 .encode(ProtocolVersion::V22),
3842 Err(CodecError::TextTooLong { .. })
3843 ));
3844 assert!(matches!(
3845 ClientMessage::DeviceToUserData(UserDataMessage {
3846 application_id: 1,
3847 line_instance: 1,
3848 call_reference: 1,
3849 transaction_id: 1,
3850 data: vec![0; 2001],
3851 })
3852 .encode(ProtocolVersion::V22),
3853 Err(CodecError::CountTooLarge { .. })
3854 ));
3855 assert!(matches!(
3856 ClientMessage::decode(Frame::new(
3857 22,
3858 wire_id::IP_PORT,
3859 70_000_u32.to_le_bytes().to_vec(),
3860 )),
3861 Err(CodecError::InvalidValue { .. })
3862 ));
3863 assert!(matches!(
3864 ServerMessage::StartMediaTransmission {
3865 call_reference: 1,
3866 passthrough_party_id: 1,
3867 endpoint: MediaEndpoint {
3868 address: "2001:db8::1".parse().unwrap(),
3869 rtp_port: 4000,
3870 rtcp_port: 4001,
3871 codec: Codec::Pcmu,
3872 packet_ms: 20,
3873 max_frames_per_packet: 1,
3874 telephone_event_payload: 101,
3875 },
3876 silence_suppression: SilenceSuppression::Off,
3877 traffic_class: crate::types::MediaTrafficClass::default(),
3878 encryption: None,
3879 wire: None,
3880 }
3881 .encode(ProtocolVersion::V3),
3882 Err(CodecError::InvalidValue { .. })
3883 ));
3884 assert!(matches!(
3885 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3886 conference_id: ConferenceId::new(1),
3887 reserved_participants: 2,
3888 resource_type: ConferenceResourceType::Conference,
3889 application_id: ApplicationId::new(1),
3890 application_conference_id: "conference-1".into(),
3891 application_data: String::new(),
3892 passthrough_data: vec![0; 2001],
3893 })
3894 .encode(ProtocolVersion::V22),
3895 Err(CodecError::CountTooLarge {
3896 field: "conference passthrough data",
3897 count: 2001,
3898 maximum: 2000,
3899 ..
3900 })
3901 ));
3902 assert!(matches!(
3903 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3904 last: 1,
3905 entries: vec![
3906 AuditConferenceEntry {
3907 conference_id: ConferenceId::new(1),
3908 resource_type: ConferenceResourceType::Conference,
3909 reserved_participants: 2,
3910 active_participants: 1,
3911 application_id: ApplicationId::new(1),
3912 application_conference_id: String::new(),
3913 application_data: String::new(),
3914 };
3915 33
3916 ],
3917 })
3918 .encode(ProtocolVersion::V22),
3919 Err(CodecError::CountTooLarge {
3920 field: "conference audit entries",
3921 count: 33,
3922 maximum: 32,
3923 ..
3924 })
3925 ));
3926
3927 let mut oversized_conference_data = vec![0; 12];
3928 oversized_conference_data[8..12].copy_from_slice(&2001_u32.to_le_bytes());
3929 assert!(matches!(
3930 ControlMessage::decode(
3931 Frame::new(
3932 22,
3933 wire_id::CREATE_CONFERENCE_RES,
3934 oversized_conference_data
3935 ),
3936 ProtocolVersion::V22,
3937 ),
3938 Err(CodecError::CountTooLarge {
3939 field: "conference passthrough data",
3940 count: 2001,
3941 maximum: 2000,
3942 ..
3943 })
3944 ));
3945
3946 let mut oversized_audit = vec![0; 8];
3947 oversized_audit[4..8].copy_from_slice(&33_u32.to_le_bytes());
3948 assert!(matches!(
3949 ControlMessage::decode(
3950 Frame::new(22, wire_id::AUDIT_CONFERENCE_RES, oversized_audit),
3951 ProtocolVersion::V22,
3952 ),
3953 Err(CodecError::CountTooLarge {
3954 field: "conference audit entries",
3955 count: 33,
3956 maximum: 32,
3957 ..
3958 })
3959 ));
3960 }
3961
3962 #[test]
3963 fn server_response_uses_the_negotiated_address_layout() {
3964 let message = ServerMessage::ServerResponse {
3965 servers: vec![
3966 SignalingServerEndpoint {
3967 name: "primary".into(),
3968 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
3969 port: NonZeroU16::new(2000).unwrap(),
3970 },
3971 SignalingServerEndpoint {
3972 name: "secondary".into(),
3973 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 20)),
3974 port: NonZeroU16::new(2001).unwrap(),
3975 },
3976 ],
3977 };
3978 let v3 = message.encode(ProtocolVersion::V3).unwrap();
3979 let v17 = message.encode(ProtocolVersion::V17).unwrap();
3980 assert_eq!(v3.len(), 292);
3981 assert_eq!(v17.len(), 372);
3982 assert_server_round_trip(message.clone(), ProtocolVersion::V3);
3983 assert_server_round_trip(message, ProtocolVersion::V17);
3984
3985 let mut zero_port = v3;
3986 zero_port[12 + 5 * 48..12 + 5 * 48 + 4].fill(0);
3987 assert!(matches!(
3988 ServerMessage::decode(decode_frame(&zero_port), ProtocolVersion::V3),
3989 Err(CodecError::InvalidValue {
3990 field: "server endpoint",
3991 value: 0,
3992 ..
3993 })
3994 ));
3995 assert_server_round_trip(
3996 ServerMessage::ServerResponse {
3997 servers: vec![SignalingServerEndpoint {
3998 name: "sccp-v6".into(),
3999 address: "2001:db8::20".parse().unwrap(),
4000 port: NonZeroU16::new(2000).unwrap(),
4001 }],
4002 },
4003 ProtocolVersion::V17,
4004 );
4005
4006 let unspecified = ServerMessage::ServerResponse {
4007 servers: vec![SignalingServerEndpoint {
4008 name: "unroutable".into(),
4009 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
4010 port: NonZeroU16::new(2000).unwrap(),
4011 }],
4012 };
4013 assert!(matches!(
4014 unspecified.encode(ProtocolVersion::V17),
4015 Err(CodecError::InvalidValue {
4016 field: "server address",
4017 value: 0,
4018 ..
4019 })
4020 ));
4021
4022 let endpoints = |count: u8| {
4023 (0..count)
4024 .map(|index| SignalingServerEndpoint {
4025 name: format!("node-{index}"),
4026 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, index + 1)),
4027 port: NonZeroU16::new(2000).unwrap(),
4028 })
4029 .collect()
4030 };
4031 let empty = ServerMessage::ServerResponse {
4032 servers: Vec::new(),
4033 };
4034 assert!(matches!(
4035 empty.encode(ProtocolVersion::V17),
4036 Err(CodecError::InvalidValue {
4037 field: "server endpoints",
4038 value: 0,
4039 ..
4040 })
4041 ));
4042 assert_server_round_trip(
4043 ServerMessage::ServerResponse {
4044 servers: endpoints(5),
4045 },
4046 ProtocolVersion::V17,
4047 );
4048 let too_many = ServerMessage::ServerResponse {
4049 servers: endpoints(6),
4050 };
4051 assert!(matches!(
4052 too_many.encode(ProtocolVersion::V17),
4053 Err(CodecError::CountTooLarge {
4054 field: "server endpoints",
4055 count: 6,
4056 maximum: 5,
4057 ..
4058 })
4059 ));
4060 }
4061}