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
224#[derive(Clone, Eq, PartialEq)]
226pub struct MediaEncryption {
227 pub algorithm: EncryptionMethod,
228 key: [u8; 16],
229 key_length: u8,
230 salt: [u8; 16],
231 salt_length: u8,
232 pub mki_present: u32,
234 pub key_derivation_rate: u32,
236}
237
238impl MediaEncryption {
239 pub fn new(
243 algorithm: EncryptionMethod,
244 key: &[u8],
245 salt: &[u8],
246 mki_present: u32,
247 key_derivation_rate: u32,
248 ) -> Result<Self, CodecError> {
249 if key.len() > 16 {
250 return Err(CodecError::SecretTooLong {
251 field: "media encryption key",
252 actual: key.len(),
253 maximum: 16,
254 });
255 }
256 if salt.len() > 16 {
257 return Err(CodecError::SecretTooLong {
258 field: "media encryption salt",
259 actual: salt.len(),
260 maximum: 16,
261 });
262 }
263 let mut wire_key = [0; 16];
264 wire_key[..key.len()].copy_from_slice(key);
265 let mut wire_salt = [0; 16];
266 wire_salt[..salt.len()].copy_from_slice(salt);
267 Ok(Self {
268 algorithm,
269 key: wire_key,
270 key_length: key.len() as u8,
271 salt: wire_salt,
272 salt_length: salt.len() as u8,
273 mki_present,
274 key_derivation_rate,
275 })
276 }
277
278 pub(crate) const fn from_wire(
279 algorithm: EncryptionMethod,
280 key: [u8; 16],
281 key_length: u8,
282 salt: [u8; 16],
283 salt_length: u8,
284 mki_present: u32,
285 key_derivation_rate: u32,
286 ) -> Self {
287 Self {
288 algorithm,
289 key,
290 key_length,
291 salt,
292 salt_length,
293 mki_present,
294 key_derivation_rate,
295 }
296 }
297
298 pub fn key(&self) -> &[u8] {
299 &self.key[..usize::from(self.key_length)]
300 }
301
302 pub fn salt(&self) -> &[u8] {
303 &self.salt[..usize::from(self.salt_length)]
304 }
305}
306
307impl fmt::Debug for MediaEncryption {
308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
309 formatter
310 .debug_struct("MediaEncryption")
311 .field("algorithm", &self.algorithm)
312 .field("key", &"<redacted>")
313 .field("key_len", &self.key_length)
314 .field("salt", &"<redacted>")
315 .field("salt_len", &self.salt_length)
316 .field("mki_present", &self.mki_present)
317 .field("key_derivation_rate", &self.key_derivation_rate)
318 .finish()
319 }
320}
321
322impl Drop for MediaEncryption {
323 fn drop(&mut self) {
324 self.key.fill(0);
325 self.salt.fill(0);
326 }
327}
328
329#[derive(Clone, Copy, Debug, Eq, PartialEq)]
331pub struct AnnouncementEntry {
332 pub locale: u32,
333 pub country: u32,
334 pub tone: Tone,
335}
336
337#[derive(Clone, Debug, Eq, PartialEq)]
339pub struct CreateConferenceRequest {
340 pub conference_id: ConferenceId,
341 pub reserved_participants: u32,
342 pub resource_type: ConferenceResourceType,
343 pub application_id: ApplicationId,
344 pub application_conference_id: String,
345 pub application_data: String,
346 pub passthrough_data: Vec<u8>,
347}
348
349#[derive(Clone, Debug, Eq, PartialEq)]
350pub struct CreateConferenceResponse {
352 pub conference_id: ConferenceId,
353 pub result: CreateConferenceResult,
354 pub passthrough_data: Vec<u8>,
355}
356
357#[derive(Clone, Debug, Eq, PartialEq)]
359pub struct ModifyConferenceRequest {
360 pub conference_id: ConferenceId,
361 pub reserved_participants: u32,
362 pub application_id: ApplicationId,
363 pub application_conference_id: String,
364 pub application_data: String,
365 pub passthrough_data: Vec<u8>,
366}
367
368#[derive(Clone, Debug, Eq, PartialEq)]
369pub struct ModifyConferenceResponse {
371 pub conference_id: ConferenceId,
372 pub result: ModifyConferenceResult,
373 pub passthrough_data: Vec<u8>,
374}
375
376#[derive(Clone, Debug, Eq, PartialEq)]
377pub struct AuditConferenceEntry {
379 pub conference_id: ConferenceId,
380 pub resource_type: ConferenceResourceType,
381 pub reserved_participants: u32,
382 pub active_participants: u32,
383 pub application_id: ApplicationId,
384 pub application_conference_id: String,
385 pub application_data: String,
386}
387
388#[derive(Clone, Debug, Eq, PartialEq)]
389pub struct AuditConferenceResponse {
391 pub last: u32,
393 pub entries: Vec<AuditConferenceEntry>,
394}
395
396#[derive(Clone, Debug, Eq, PartialEq)]
397pub struct ConferenceParticipant {
399 pub call_reference: CallReference,
400 pub presentation_restrictions: PartyInformationRestrictions,
401 pub name: String,
402 pub number: String,
403 pub conference_name: String,
404}
405
406#[derive(Clone, Debug, Eq, PartialEq)]
407pub struct AddParticipantRequest {
409 pub conference_id: ConferenceId,
410 pub participant: ConferenceParticipant,
411}
412
413#[derive(Clone, Debug, Eq, PartialEq)]
418pub struct ChangeParticipantRequest {
419 pub conference_id: ConferenceId,
420 pub participant: ConferenceParticipant,
421}
422
423#[derive(Clone, Debug, Eq, PartialEq)]
424pub struct AddParticipantResponse {
426 pub conference_id: ConferenceId,
427 pub call_reference: CallReference,
428 pub result: AddParticipantResult,
429 pub bridge_participant_id: BoundedBytes<257>,
431}
432
433#[derive(Clone, Debug, Eq, PartialEq)]
436pub struct AuditParticipantResponse {
437 pub result: AuditParticipantResult,
438 pub last: u32,
439 pub conference_id: ConferenceId,
440 pub number_of_entries: u32,
442 pub participant_entries: Vec<u8>,
444}
445
446#[derive(Clone, Copy, Debug, Eq, PartialEq)]
449pub struct ParticipantChangeRouting {
450 pub application_id: ApplicationId,
451 pub line_instance: u32,
452 pub transaction_id: TransactionId,
453 pub sequence_flag: u32,
454 pub display_priority: u32,
455 pub application_instance_id: ApplicationId,
456 pub routing: u32,
457}
458
459#[derive(Clone, Debug, Eq, PartialEq)]
460pub struct ConferenceParticipantChange {
462 pub conference_id: ConferenceId,
463 pub participant: ConferenceParticipant,
464}
465
466#[derive(Clone, Debug, Eq, PartialEq)]
467pub struct MulticastMediaReception {
469 pub conference_id: ConferenceId,
470 pub passthrough_party_id: crate::types::PassthroughPartyId,
471 pub call_reference: CallReference,
472 pub address: IpAddr,
473 pub port: u16,
474 pub packet_millis: u32,
475 pub codec: Codec,
476 pub echo_cancellation: EchoCancellation,
477 pub g723_bitrate: G723BitRate,
478}
479
480#[derive(Clone, Debug, Eq, PartialEq)]
481pub struct MulticastMediaTransmission {
483 pub conference_id: ConferenceId,
484 pub passthrough_party_id: crate::types::PassthroughPartyId,
485 pub call_reference: CallReference,
486 pub address: IpAddr,
487 pub port: u16,
488 pub packet_millis: u32,
489 pub codec: Codec,
490 pub precedence: u32,
491 pub silence_suppression: u32,
492 pub max_frames_per_packet: u32,
493 pub g723_bitrate: G723BitRate,
494}
495
496#[derive(Clone, Debug, Eq, PartialEq)]
497pub struct KnownOpaqueMessage {
499 pub id: MessageId,
500 pub protocol_version: u32,
501 pub payload: BoundedBytes<MAX_OPAQUE_MESSAGE_BYTES>,
502}
503
504#[derive(Clone, Debug, Eq, PartialEq)]
505pub struct UserDataMessage {
507 pub application_id: u32,
508 pub line_instance: u32,
509 pub call_reference: u32,
510 pub transaction_id: u32,
511 pub data: Vec<u8>,
512}
513
514#[derive(Clone, Debug, Eq, PartialEq)]
516pub struct UserDataV1Message {
517 pub application_id: u32,
518 pub line_instance: u32,
519 pub call_reference: u32,
520 pub transaction_id: u32,
521 pub sequence_flag: u32,
522 pub display_priority: u32,
523 pub conference_id: u32,
524 pub application_instance_id: u32,
525 pub routing: u32,
526 pub data: Vec<u8>,
527}
528
529#[derive(Clone, Debug, Eq, PartialEq)]
530pub struct RegisterTokenMessage {
532 pub device_id: DeviceId,
533 pub device_instance: u32,
534 pub address: IpAddr,
535 pub device_type: DeviceType,
536 pub flags: u32,
538}
539
540pub const MAX_SIGNALING_SERVERS: usize = 5;
542
543#[derive(Clone, Debug, Eq, PartialEq)]
545pub struct SignalingServerEndpoint {
546 pub name: String,
547 pub address: IpAddr,
548 pub port: NonZeroU16,
549}
550
551#[derive(Clone, Debug, Eq, PartialEq)]
552pub struct MediaResourceNotification {
554 pub device_type: DeviceType,
555 pub in_service_streams: u32,
556 pub max_streams_per_conference: u32,
557 pub out_of_service_streams: u32,
558}
559
560#[derive(Clone, Debug, Eq, PartialEq)]
561pub struct SubscriptionRequest {
563 pub transaction_id: u32,
564 pub feature_id: u32,
565 pub timer_seconds: u32,
566 pub subscription_id: String,
567}
568
569#[derive(Clone, Debug, Eq, PartialEq)]
570pub struct PortEndpoint {
572 pub conference_id: u32,
573 pub call_reference: u32,
574 pub passthrough_party_id: u32,
575 pub address: IpAddr,
576 pub rtp_port: u16,
577 pub rtcp_port: u16,
578 pub media_type: Option<MediaType>,
579}
580
581#[derive(Clone, Copy, Debug, Eq, PartialEq)]
582pub struct PortRequest {
584 pub conference_id: ConferenceId,
585 pub call_reference: CallReference,
586 pub passthrough_party_id: crate::types::PassthroughPartyId,
587 pub transport: MediaTransport,
588 pub address_type: Option<IpAddressType>,
589 pub media_type: Option<MediaType>,
590}
591
592#[derive(Clone, Copy, Debug, Eq, PartialEq)]
593pub struct PortClose {
595 pub conference_id: ConferenceId,
596 pub call_reference: CallReference,
597 pub passthrough_party_id: crate::types::PassthroughPartyId,
598 pub media_type: Option<MediaType>,
599}
600
601#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
603pub struct QosFlow {
604 pub conference_id: ConferenceId,
605 pub call_reference: CallReference,
606 pub passthrough_party_id: crate::types::PassthroughPartyId,
607 pub address: Ipv4Addr,
608 pub port: u16,
609}
610
611#[derive(Clone, Copy, Debug, Eq, PartialEq)]
616pub struct QosTrafficSpecification {
617 pub codec: Codec,
618 pub average_bit_rate: u32,
619 pub burst_size: u32,
620 pub peak_rate: u32,
621}
622
623#[derive(Clone, Debug, Eq, PartialEq)]
625pub struct QosApplicationIdentifier {
626 pub vendor_id: String,
627 pub version: String,
628 pub application_name: String,
629 pub sub_application_id: String,
630}
631
632#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
633pub struct MessageWaitingCounts {
635 pub new: u32,
636 pub old: u32,
637}
638
639#[derive(Clone, Debug, Eq, PartialEq)]
640pub struct MessageWaitingNotification {
642 pub target_number: String,
643 pub control_number: String,
644 pub messages_waiting: bool,
645 pub total_voicemail: MessageWaitingCounts,
646 pub priority_voicemail: MessageWaitingCounts,
647 pub total_fax: MessageWaitingCounts,
648 pub priority_fax: MessageWaitingCounts,
649}
650
651#[derive(Clone, Copy, Debug, Eq, PartialEq)]
652pub struct OpenMultimediaReceiveChannelAck {
654 pub status: MediaStatus,
655 pub endpoint: MediaEndpointAddress,
656 pub passthrough_party_id: crate::types::PassthroughPartyId,
657 pub call_reference: CallReference,
658}
659
660#[derive(Clone, Copy, Debug, Eq, PartialEq)]
661pub struct StartMultimediaTransmissionAck {
663 pub conference_id: ConferenceId,
664 pub passthrough_party_id: crate::types::PassthroughPartyId,
665 pub call_reference: CallReference,
666 pub endpoint: MediaEndpointAddress,
667 pub status: MediaStatus,
668}
669
670#[derive(Clone, Copy, Debug, Eq, PartialEq)]
671pub struct MediaEndpointAddress {
673 pub address: IpAddr,
674 pub port: u16,
675}
676
677#[derive(Clone, Copy, Debug, Eq, PartialEq)]
678pub struct MultimediaStreamControl {
680 pub conference_id: ConferenceId,
681 pub passthrough_party_id: crate::types::PassthroughPartyId,
682 pub call_reference: CallReference,
683 pub port_handling_flag: u32,
684}
685
686#[derive(Clone, Copy, Debug, Eq, PartialEq)]
687pub struct AudioStreamControl {
689 pub conference_id: ConferenceId,
690 pub passthrough_party_id: crate::types::PassthroughPartyId,
691 pub call_reference: CallReference,
692 pub port_handling_flag: u32,
693}
694
695#[derive(Clone, Copy, Debug, Eq, PartialEq)]
696pub struct SessionTransmission {
698 pub remote_address: IpAddr,
699 pub session_type: u32,
700}
701
702#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
704pub struct RtpPayloadNumber(u8);
705
706impl RtpPayloadNumber {
707 pub const MAX: u32 = 127;
708
709 pub const fn new(value: u32) -> Result<Self, RtpPayloadNumberError> {
710 if value <= Self::MAX {
711 Ok(Self(value as u8))
712 } else {
713 Err(RtpPayloadNumberError { actual: value })
714 }
715 }
716
717 pub const fn get(self) -> u8 {
718 self.0
719 }
720}
721
722impl TryFrom<u32> for RtpPayloadNumber {
723 type Error = RtpPayloadNumberError;
724
725 fn try_from(value: u32) -> Result<Self, Self::Error> {
726 Self::new(value)
727 }
728}
729
730impl From<RtpPayloadNumber> for u32 {
731 fn from(value: RtpPayloadNumber) -> Self {
732 u32::from(value.get())
733 }
734}
735
736#[derive(Clone, Copy, Debug, Eq, PartialEq)]
738pub struct RtpPayloadNumberError {
739 pub actual: u32,
740}
741
742impl fmt::Display for RtpPayloadNumberError {
743 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
744 write!(
745 formatter,
746 "RTP payload number {} exceeds {}",
747 self.actual,
748 RtpPayloadNumber::MAX
749 )
750 }
751}
752
753impl std::error::Error for RtpPayloadNumberError {}
754
755#[derive(Clone, Copy, Debug, Eq, PartialEq)]
757pub struct MultimediaPayloadDescriptor {
758 rfc_number: u32,
759 payload_number: RtpPayloadNumber,
760}
761
762impl MultimediaPayloadDescriptor {
763 pub const fn new(rfc_number: u32, payload_number: RtpPayloadNumber) -> Self {
765 Self {
766 rfc_number,
767 payload_number,
768 }
769 }
770
771 pub const fn rfc_number(self) -> u32 {
773 self.rfc_number
774 }
775
776 pub const fn payload_number(self) -> RtpPayloadNumber {
777 self.payload_number
778 }
779}
780
781#[derive(Clone, Copy, Debug, Eq, PartialEq)]
783pub struct MultimediaPictureFormat {
784 pub format: VideoFormat,
785 pub minimum_picture_interval: u32,
786}
787
788#[derive(Clone, Copy, Debug, Eq, PartialEq)]
790pub enum MultimediaVideoCapabilityArm {
791 H261 {
792 temporal_spatial_trade_off_capability: u32,
793 still_image_transmission: u32,
794 },
795 H263 {
796 capability_bitfield: u32,
797 annex_n_and_w_future_use: u32,
798 },
799 H263Plus {
800 model_number: u32,
801 bandwidth: u32,
802 },
803 H264 {
804 profile: u32,
805 level: u32,
806 custom_max_mbps: u32,
807 custom_max_fs: u32,
808 custom_max_dpb: u32,
809 custom_max_br_and_cpb: u32,
810 },
811}
812
813impl MultimediaVideoCapabilityArm {
814 pub const fn codec(self) -> Codec {
815 match self {
816 Self::H261 { .. } => Codec::H261,
817 Self::H263 { .. } => Codec::H263,
818 Self::H263Plus { .. } => Codec::H263Plus,
819 Self::H264 { .. } => Codec::H264,
820 }
821 }
822}
823
824#[derive(Clone)]
826pub struct MultimediaVideoCapability {
827 bit_rate: u32,
828 picture_formats: Box<[MultimediaPictureFormat]>,
829 conference_service_number: u32,
830 arm: MultimediaVideoCapabilityArm,
831 preserved_wire: Option<[u8; MULTIMEDIA_CAPABILITY_BYTES]>,
832}
833
834impl MultimediaVideoCapability {
835 pub fn new(
837 bit_rate: u32,
838 picture_formats: impl IntoIterator<Item = MultimediaPictureFormat>,
839 conference_service_number: u32,
840 arm: MultimediaVideoCapabilityArm,
841 ) -> Result<Self, MultimediaCapabilityError> {
842 let picture_formats = picture_formats.into_iter().collect::<Box<[_]>>();
843 if picture_formats.len() > MAX_MULTIMEDIA_PICTURE_FORMATS {
844 return Err(MultimediaCapabilityError {
845 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
846 actual: picture_formats.len(),
847 });
848 }
849 Ok(Self {
850 bit_rate,
851 picture_formats,
852 conference_service_number,
853 arm,
854 preserved_wire: None,
855 })
856 }
857
858 pub const fn bit_rate(&self) -> u32 {
859 self.bit_rate
860 }
861
862 pub fn picture_formats(&self) -> &[MultimediaPictureFormat] {
863 &self.picture_formats
864 }
865
866 pub const fn conference_service_number(&self) -> u32 {
867 self.conference_service_number
868 }
869
870 pub const fn arm(&self) -> MultimediaVideoCapabilityArm {
871 self.arm
872 }
873
874 pub const fn codec(&self) -> Codec {
875 self.arm.codec()
876 }
877}
878
879impl fmt::Debug for MultimediaVideoCapability {
880 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
881 formatter
882 .debug_struct("MultimediaVideoCapability")
883 .field("bit_rate", &self.bit_rate)
884 .field("picture_formats", &self.picture_formats)
885 .field("conference_service_number", &self.conference_service_number)
886 .field("arm", &self.arm)
887 .finish()
888 }
889}
890
891impl PartialEq for MultimediaVideoCapability {
892 fn eq(&self, other: &Self) -> bool {
893 self.bit_rate == other.bit_rate
894 && self.picture_formats == other.picture_formats
895 && self.conference_service_number == other.conference_service_number
896 && self.arm == other.arm
897 && self.preserved_wire == other.preserved_wire
898 }
899}
900
901impl Eq for MultimediaVideoCapability {}
902
903#[derive(Clone, Copy, Debug, Eq, PartialEq)]
904pub struct MultimediaCapabilityError {
906 pub maximum: usize,
907 pub actual: usize,
908}
909
910impl fmt::Display for MultimediaCapabilityError {
911 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
912 write!(
913 formatter,
914 "video capability contains {} picture formats, exceeding the maximum of {}",
915 self.actual, self.maximum
916 )
917 }
918}
919
920impl std::error::Error for MultimediaCapabilityError {}
921
922#[derive(Clone, Copy, Debug, Eq, PartialEq)]
923pub(crate) enum MultimediaPayloadDirection {
924 Receive,
925 Transmit,
926}
927
928#[derive(Clone, Eq, PartialEq)]
929enum MultimediaCapabilityState {
930 Video(MultimediaVideoCapability),
931 Preserved([u8; MULTIMEDIA_CAPABILITY_BYTES]),
932}
933
934#[derive(Clone, Copy, Debug, Eq, PartialEq)]
935enum MultimediaPayloadOrigin {
936 Constructed,
937 Decoded {
938 direction: MultimediaPayloadDirection,
939 protocol: ProtocolVersion,
940 compression_codec: Codec,
941 },
942}
943
944#[derive(Clone)]
946pub struct MultimediaPayload {
947 descriptor: MultimediaPayloadDescriptor,
948 capability: MultimediaCapabilityState,
949 origin: MultimediaPayloadOrigin,
950}
951
952impl MultimediaPayload {
953 pub fn new(payload_number: RtpPayloadNumber, capability: MultimediaVideoCapability) -> Self {
955 Self::with_descriptor(
956 MultimediaPayloadDescriptor::new(0, payload_number),
957 capability,
958 )
959 }
960
961 pub fn with_descriptor(
963 descriptor: MultimediaPayloadDescriptor,
964 capability: MultimediaVideoCapability,
965 ) -> Self {
966 Self {
967 descriptor,
968 capability: MultimediaCapabilityState::Video(capability),
969 origin: MultimediaPayloadOrigin::Constructed,
970 }
971 }
972
973 const fn from_decoded(
974 descriptor: MultimediaPayloadDescriptor,
975 capability: MultimediaCapabilityState,
976 direction: MultimediaPayloadDirection,
977 protocol: ProtocolVersion,
978 compression_codec: Codec,
979 ) -> Self {
980 Self {
981 descriptor,
982 capability,
983 origin: MultimediaPayloadOrigin::Decoded {
984 direction,
985 protocol,
986 compression_codec,
987 },
988 }
989 }
990
991 #[cfg(test)]
992 pub(crate) fn from_wire(
993 rfc_number: u32,
994 payload_number: RtpPayloadNumber,
995 capability: [u8; MULTIMEDIA_CAPABILITY_BYTES],
996 codec: Codec,
997 direction: MultimediaPayloadDirection,
998 protocol: ProtocolVersion,
999 ) -> Self {
1000 Self::from_decoded(
1001 MultimediaPayloadDescriptor::new(rfc_number, payload_number),
1002 MultimediaCapabilityState::Preserved(capability),
1003 direction,
1004 protocol,
1005 codec,
1006 )
1007 }
1008
1009 pub const fn descriptor(&self) -> MultimediaPayloadDescriptor {
1010 self.descriptor
1011 }
1012
1013 pub const fn codec(&self) -> Codec {
1014 self.compression_codec()
1015 }
1016
1017 pub const fn payload_number(&self) -> RtpPayloadNumber {
1018 self.descriptor.payload_number()
1019 }
1020
1021 pub const fn video_capability(&self) -> Option<&MultimediaVideoCapability> {
1023 match &self.capability {
1024 MultimediaCapabilityState::Video(capability) => Some(capability),
1025 MultimediaCapabilityState::Preserved(_) => None,
1026 }
1027 }
1028
1029 pub(crate) fn is_valid_for(
1030 &self,
1031 direction: MultimediaPayloadDirection,
1032 protocol: ProtocolVersion,
1033 ) -> bool {
1034 match self.origin {
1035 MultimediaPayloadOrigin::Constructed => true,
1036 MultimediaPayloadOrigin::Decoded {
1037 direction: decoded_direction,
1038 protocol: decoded_protocol,
1039 ..
1040 } => decoded_direction == direction && decoded_protocol.wire() == protocol.wire(),
1041 }
1042 }
1043
1044 pub(crate) fn is_direction(&self, direction: MultimediaPayloadDirection) -> bool {
1045 match self.origin {
1046 MultimediaPayloadOrigin::Constructed => true,
1047 MultimediaPayloadOrigin::Decoded {
1048 direction: decoded_direction,
1049 ..
1050 } => decoded_direction == direction,
1051 }
1052 }
1053
1054 pub(crate) const fn compression_codec(&self) -> Codec {
1055 match self.origin {
1056 MultimediaPayloadOrigin::Constructed => match &self.capability {
1057 MultimediaCapabilityState::Video(capability) => capability.codec(),
1058 MultimediaCapabilityState::Preserved(_) => unreachable!(),
1059 },
1060 MultimediaPayloadOrigin::Decoded {
1061 compression_codec, ..
1062 } => compression_codec,
1063 }
1064 }
1065}
1066
1067impl PartialEq for MultimediaPayload {
1068 fn eq(&self, other: &Self) -> bool {
1069 self.descriptor == other.descriptor
1070 && self.capability == other.capability
1071 && self.origin == other.origin
1072 }
1073}
1074
1075impl Eq for MultimediaPayload {}
1076
1077impl fmt::Debug for MultimediaPayload {
1078 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1079 formatter
1080 .debug_struct("MultimediaPayload")
1081 .field("descriptor", &self.descriptor)
1082 .field("codec", &self.codec())
1083 .field("video_capability", &self.video_capability())
1084 .finish()
1085 }
1086}
1087
1088#[derive(Clone, Debug, Eq, PartialEq)]
1089pub struct OpenMultimediaChannel {
1091 pub conference_id: ConferenceId,
1092 pub passthrough_party_id: crate::types::PassthroughPartyId,
1093 pub line_instance: u32,
1094 pub call_reference: CallReference,
1095 pub payload: MultimediaPayload,
1096 pub conference_creator: bool,
1097 pub encryption: Option<MediaEncryption>,
1099 pub stream_passthrough_id: u32,
1101 pub associated_stream_id: u32,
1103 pub source: MediaEndpointAddress,
1104 pub requested_address_type: IpAddressType,
1105}
1106
1107#[derive(Clone, Debug, Eq, PartialEq)]
1108pub struct StartMultimediaTransmission {
1110 pub conference_id: ConferenceId,
1111 pub passthrough_party_id: crate::types::PassthroughPartyId,
1112 pub endpoint: MediaEndpointAddress,
1113 pub call_reference: CallReference,
1114 pub payload: MultimediaPayload,
1115 pub traffic_class: crate::types::MediaTrafficClass,
1116 pub encryption: Option<MediaEncryption>,
1118 pub stream_passthrough_id: u32,
1120 pub associated_stream_id: u32,
1122}
1123
1124#[derive(Clone, Debug, Eq, PartialEq)]
1125pub struct MiscellaneousCommand {
1127 pub conference_id: ConferenceId,
1128 pub passthrough_party_id: crate::types::PassthroughPartyId,
1129 pub call_reference: CallReference,
1130 pub command: values::MiscCommandType,
1131 pub data: BoundedBytes<36>,
1133}
1134
1135#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1136pub struct VideoFlowControl {
1138 pub conference_id: ConferenceId,
1139 pub passthrough_party_id: crate::types::PassthroughPartyId,
1140 pub call_reference: CallReference,
1141 pub maximum_bit_rate: u32,
1142}
1143
1144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1145pub struct DtmfToneControl {
1147 pub tone: Tone,
1148 pub conference_id: ConferenceId,
1149 pub passthrough_party_id: u32,
1150}
1151
1152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1153pub struct DtmfPayloadIdentity {
1155 pub payload_type: u32,
1157 pub conference_id: u32,
1158 pub passthrough_party_id: u32,
1159}
1160
1161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1162pub struct DtmfPayloadRequest {
1164 pub payload_type: u32,
1166 pub conference_id: u32,
1167 pub passthrough_party_id: u32,
1168 pub dtmf_type: u32,
1170}
1171
1172pub const XML_ALARM_MAX_WIRE_BYTES: usize = 2_048;
1174pub const XML_ALARM_CANONICAL_WIRE_BYTES: usize = 2_004;
1176pub const XML_ALARM_CANONICAL_DOCUMENT_BYTES: usize = 2_000;
1178
1179#[derive(Clone, Debug, Eq, PartialEq)]
1180pub struct XmlAlarmMessage {
1185 wire_payload: BoundedBytes<XML_ALARM_MAX_WIRE_BYTES>,
1186}
1187
1188impl XmlAlarmMessage {
1189 pub fn from_xml(xml: impl AsRef<[u8]>) -> Result<Self, CodecError> {
1191 let xml = xml.as_ref();
1192 if xml.contains(&0) {
1193 return Err(CodecError::InvalidText);
1194 }
1195 if xml.len() > XML_ALARM_CANONICAL_DOCUMENT_BYTES {
1196 return Err(CodecError::TextTooLong {
1197 message_id: wire_id::XML_ALARM,
1198 field: "alarm XML",
1199 actual: xml.len(),
1200 maximum: XML_ALARM_CANONICAL_DOCUMENT_BYTES,
1201 });
1202 }
1203 let mut wire_payload = vec![0; XML_ALARM_CANONICAL_WIRE_BYTES];
1204 wire_payload[..xml.len()].copy_from_slice(xml);
1205 Self::from_wire_payload(wire_payload)
1206 }
1207
1208 pub fn from_wire_payload(payload: impl Into<Box<[u8]>>) -> Result<Self, CodecError> {
1210 let payload = payload.into();
1211 let wire_payload =
1212 BoundedBytes::new(payload).map_err(|error| CodecError::CountTooLarge {
1213 message_id: wire_id::XML_ALARM,
1214 field: "alarm payload",
1215 count: error.actual,
1216 maximum: error.maximum,
1217 })?;
1218 Ok(Self { wire_payload })
1219 }
1220
1221 pub fn xml_bytes(&self) -> &[u8] {
1223 let bytes = self.wire_payload.as_bytes();
1224 let end = bytes
1225 .iter()
1226 .position(|byte| *byte == 0)
1227 .unwrap_or(bytes.len());
1228 &bytes[..end]
1229 }
1230
1231 pub fn wire_payload(&self) -> &[u8] {
1233 self.wire_payload.as_bytes()
1234 }
1235}
1236
1237#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1243pub struct MediaFailureDetection {
1244 pub conference_id: ConferenceId,
1245 pub passthrough_party_id: u32,
1246 pub packet_millis: u32,
1247 pub codec: Codec,
1248 pub echo_cancellation: EchoCancellation,
1249 pub codec_qualifier: [u8; 4],
1250 pub call_reference: CallReference,
1251}
1252
1253#[derive(Clone, Debug, Eq, PartialEq)]
1256pub struct ExtensionDeviceCapabilities {
1257 pub unknown_1: u32,
1258 pub unknown_2: u32,
1259 pub unknown_3: u32,
1260 pub description: String,
1261}
1262
1263#[derive(Clone, Debug, Eq, PartialEq)]
1264pub struct ConfigurationStatus {
1266 pub device_name: String,
1267 pub station_user_id: u32,
1268 pub station_instance: u32,
1269 pub line_count: u32,
1270 pub speed_dial_count: u32,
1271 pub user_name: String,
1272 pub server_name: String,
1273}
1274
1275#[derive(Clone, Debug, Eq, PartialEq)]
1280pub enum ControlMessage {
1281 MediaResourceNotification(MediaResourceNotification),
1282 PortResponse(PortEndpoint),
1283 StartSessionTransmission(SessionTransmission),
1284 StopSessionTransmission(SessionTransmission),
1285 ClearConference {
1286 conference_id: ConferenceId,
1287 service_number: u32,
1288 },
1289 CreateConferenceRequest(CreateConferenceRequest),
1290 DeleteConferenceRequest {
1291 conference_id: ConferenceId,
1292 },
1293 ModifyConferenceRequest(ModifyConferenceRequest),
1294 AddParticipantRequest(AddParticipantRequest),
1295 DropParticipantRequest {
1296 conference_id: ConferenceId,
1297 call_reference: CallReference,
1298 },
1299 AuditConferenceRequest,
1300 AuditParticipantRequest {
1301 conference_id: ConferenceId,
1302 },
1303 ChangeParticipantRequest(ChangeParticipantRequest),
1304 CreateConferenceResponse(CreateConferenceResponse),
1305 DeleteConferenceResponse {
1306 conference_id: ConferenceId,
1307 result: DeleteConferenceResult,
1308 },
1309 ModifyConferenceResponse(ModifyConferenceResponse),
1310 AddParticipantResponse(AddParticipantResponse),
1311 AuditConferenceResponse(AuditConferenceResponse),
1312 AuditParticipantResponse(AuditParticipantResponse),
1313 StartAnnouncement {
1315 announcements: Vec<AnnouncementEntry>,
1316 end_of_ack: EndOfAnnouncementAck,
1318 conference_id: u32,
1319 matrix_conference_party_ids: Vec<u32>,
1321 hearing_conference_party_mask: u32,
1323 play_mode: AnnouncementPlayMode,
1324 },
1325 StopAnnouncement {
1326 conference_id: u32,
1327 },
1328 AnnouncementFinish {
1329 conference_id: u32,
1330 play_status: AnnouncementPlayStatus,
1331 },
1332 QosReservationNotify {
1333 flow: QosFlow,
1334 direction: QosDirection,
1335 },
1336 QosErrorNotify {
1338 flow: QosFlow,
1339 direction: QosDirection,
1340 error_code: QosErrorCode,
1341 failure_node: Ipv4Addr,
1343 rsvp_error_code: RsvpErrorCode,
1344 rsvp_error_subcode: u32,
1345 rsvp_error_flags: u32,
1346 },
1347 QosListen {
1349 flow: QosFlow,
1350 reservation_style: QosReservationStyle,
1351 maximum_retries: u32,
1352 retry_timer: u32,
1353 confirmation_required: bool,
1355 preemption_priority: u32,
1357 defending_priority: u32,
1359 traffic: QosTrafficSpecification,
1360 application: QosApplicationIdentifier,
1361 },
1362 QosPath {
1364 flow: QosFlow,
1365 reservation_style: QosReservationStyle,
1366 maximum_retries: u32,
1367 retry_timer: u32,
1368 preemption_priority: u32,
1369 defending_priority: u32,
1370 traffic: QosTrafficSpecification,
1371 application: QosApplicationIdentifier,
1372 },
1373 QosTeardown {
1375 flow: QosFlow,
1376 direction: QosDirection,
1377 },
1378 UpdateDscp {
1380 flow: QosFlow,
1381 dscp: u8,
1382 },
1383 QosModify {
1385 flow: QosFlow,
1386 direction: QosDirection,
1387 traffic: QosTrafficSpecification,
1388 application: QosApplicationIdentifier,
1389 },
1390 MessageWaitingNotification(MessageWaitingNotification),
1391 MessageWaitingResponse {
1392 target_number: String,
1393 result: MessageWaitingResult,
1394 },
1395 KnownOpaque(KnownOpaqueMessage),
1397}
1398
1399pub const CONNECTION_QUALITY_MAX_BYTES: usize = 600;
1401
1402#[derive(Clone, Eq, PartialEq)]
1407pub struct ConnectionQualityStatistics(Vec<u8>);
1408
1409impl ConnectionQualityStatistics {
1410 pub fn new(bytes: impl Into<Vec<u8>>) -> Result<Self, CodecError> {
1412 let bytes = bytes.into();
1413 if bytes.len() > CONNECTION_QUALITY_MAX_BYTES {
1414 return Err(CodecError::CountTooLarge {
1415 message_id: wire_id::CONNECTION_STATISTICS_RES,
1416 field: "quality statistics",
1417 count: bytes.len(),
1418 maximum: CONNECTION_QUALITY_MAX_BYTES,
1419 });
1420 }
1421 Ok(Self(bytes))
1422 }
1423
1424 pub fn as_bytes(&self) -> &[u8] {
1425 &self.0
1426 }
1427}
1428
1429impl fmt::Debug for ConnectionQualityStatistics {
1430 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1431 formatter
1432 .debug_struct("ConnectionQualityStatistics")
1433 .field("byte_count", &self.0.len())
1434 .finish()
1435 }
1436}
1437
1438#[derive(Clone, Eq, PartialEq)]
1439pub struct ConnectionStatistics {
1443 pub directory_number: String,
1444 pub call_reference: u32,
1445 pub processing: StatisticsProcessing,
1446 pub packets_sent: u32,
1447 pub octets_sent: u32,
1448 pub packets_received: u32,
1449 pub octets_received: u32,
1450 pub packets_lost: u32,
1451 pub jitter_millis: u32,
1453 pub latency_millis: u32,
1455 pub quality: ConnectionQualityStatistics,
1456}
1457
1458impl fmt::Debug for ConnectionStatistics {
1459 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1460 formatter
1461 .debug_struct("ConnectionStatistics")
1462 .field("directory_number", &"<redacted>")
1463 .field("call_reference", &self.call_reference)
1464 .field("processing", &self.processing)
1465 .field("packets_sent", &self.packets_sent)
1466 .field("octets_sent", &self.octets_sent)
1467 .field("packets_received", &self.packets_received)
1468 .field("octets_received", &self.octets_received)
1469 .field("packets_lost", &self.packets_lost)
1470 .field("jitter_millis", &self.jitter_millis)
1471 .field("latency_millis", &self.latency_millis)
1472 .field("quality", &self.quality)
1473 .finish()
1474 }
1475}
1476
1477#[derive(Clone, Debug, Eq, PartialEq)]
1478pub struct MediaTransmissionAckWire {
1480 pub extension: Option<[u8; 8]>,
1482}
1483
1484#[derive(Clone, Debug, Eq, PartialEq)]
1485pub struct MediaTransmissionAck {
1487 pub conference_id: u32,
1488 pub passthrough_party_id: u32,
1489 pub call_reference: u32,
1490 pub status: MediaStatus,
1491 pub address: IpAddr,
1492 pub port: u16,
1493 pub wire: Option<MediaTransmissionAckWire>,
1495}
1496
1497#[derive(Clone, Debug, Eq, PartialEq)]
1500pub struct OpenReceiveChannelWire {
1501 pub conference_id: u32,
1502 pub g723_bitrate: u32,
1504 pub stream_passthrough_id: u32,
1506 pub associated_stream_id: u32,
1508 pub dtmf_type: u32,
1510 pub mixing_mode: u32,
1512 pub direction: u32,
1514 pub requested_address_type: u32,
1516 pub audio_level_adjustment: u32,
1518 pub latent_capabilities: [u8; 36],
1520}
1521
1522#[derive(Clone, Debug, Eq, PartialEq)]
1525pub struct StartMediaTransmissionWire {
1526 pub conference_id: u32,
1527 pub g723_bitrate: u32,
1529 pub stream_passthrough_id: u32,
1531 pub associated_stream_id: u32,
1533 pub dtmf_type: u32,
1535 pub mixing_mode: u32,
1537 pub direction: u32,
1539 pub latent_capabilities: [u8; 36],
1541}
1542
1543#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1547pub enum KeypadButtonWireLayout {
1548 LegacyButtonOnly,
1550 WithCallIdentity,
1552}
1553
1554#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1556pub struct ButtonTemplateEntry {
1557 pub instance: u32,
1558 pub button_type: ButtonType,
1559}
1560
1561impl Default for ButtonTemplateEntry {
1562 fn default() -> Self {
1563 Self {
1564 instance: 0,
1565 button_type: ButtonType::Unused,
1566 }
1567 }
1568}
1569
1570#[derive(Clone, Debug, Eq, PartialEq)]
1571pub enum ClientMessage {
1579 KeepAlive,
1580 Register(RegistrationMessage),
1581 IpPort {
1582 rtp_port: u16,
1583 },
1584 KeypadButton {
1585 button: Digit,
1586 line_instance: u32,
1587 call_reference: u32,
1588 wire_layout: Option<KeypadButtonWireLayout>,
1589 },
1590 EnblocCall {
1591 called_party: String,
1592 line_instance: u32,
1593 },
1594 Stimulus {
1595 stimulus: Stimulus,
1596 instance: u32,
1597 call_reference: u32,
1598 status: u32,
1599 },
1600 OffHook {
1601 line_instance: u32,
1602 call_reference: u32,
1603 },
1604 OnHook {
1605 line_instance: u32,
1606 call_reference: u32,
1607 },
1608 OffHookWithCallingParty {
1609 calling_party_number: String,
1610 voice_mailbox: String,
1611 line_instance: u32,
1612 },
1613 LineStatRequest {
1614 line_instance: u32,
1615 },
1616 ConfigStatRequest,
1617 TimeDateRequest,
1618 ButtonTemplateRequest,
1619 VersionRequest,
1620 CapabilitiesResponse(Vec<MediaCapability>),
1621 CapabilitiesUpdate(CapabilityUpdate),
1622 OpenMultimediaReceiveChannelAck(OpenMultimediaReceiveChannelAck),
1623 ServerRequest,
1624 Alarm {
1625 severity: AlarmSeverity,
1626 text: String,
1627 parameters: Option<[u32; 2]>,
1630 },
1631 MulticastMediaReceptionAck {
1632 status: MediaStatus,
1633 passthrough_party_id: crate::types::PassthroughPartyId,
1634 call_reference: CallReference,
1635 },
1636 OpenReceiveChannelAck {
1637 status: MediaStatus,
1638 address: IpAddr,
1639 port: u16,
1640 passthrough_party_id: u32,
1641 call_reference: u32,
1642 },
1643 SoftKeySetRequest,
1644 SoftKeyTemplateRequest,
1645 SoftKeyEvent {
1646 event: u32,
1647 line_instance: u32,
1648 call_reference: u32,
1649 },
1650 Unregister {
1651 reason: u32,
1652 },
1653 RegisterToken(RegisterTokenMessage),
1654 HookFlash {
1655 line_instance: u32,
1656 call_reference: u32,
1657 },
1658 ForwardStatusRequest {
1659 line_instance: u32,
1660 },
1661 SpeedDialStatusRequest {
1662 speed_dial_instance: u32,
1663 },
1664 ConnectionStatisticsResponse(ConnectionStatistics),
1665 HeadsetStatus {
1666 enabled: bool,
1667 },
1668 MediaResourceNotification(MediaResourceNotification),
1669 MediaPathEvent {
1670 path: MediaPathId,
1671 event: MediaPathEvent,
1672 },
1673 MediaPathCapability {
1674 path: MediaPathId,
1675 capability: MediaPathCapability,
1676 },
1677 MediaTransmissionFailure {
1678 conference_id: u32,
1679 passthrough_party_id: u32,
1680 address: IpAddr,
1681 port: u16,
1682 call_reference: u32,
1683 status: MediaStatus,
1684 },
1685 RegisterAvailableLines {
1686 lines: u32,
1687 },
1688 ServiceUrlStatusRequest {
1689 index: u32,
1690 },
1691 FeatureStatusRequest {
1692 index: u32,
1693 capabilities: u32,
1695 },
1696 StartMediaTransmissionAck(MediaTransmissionAck),
1697 StartMultimediaTransmissionAck(StartMultimediaTransmissionAck),
1698 ExtensionDeviceCapabilities(ExtensionDeviceCapabilities),
1699 DeviceToUserData(UserDataMessage),
1700 DeviceToUserDataResponse(UserDataMessage),
1701 DeviceToUserDataV1(UserDataV1Message),
1702 DeviceToUserDataResponseV1(UserDataV1Message),
1703 PortResponse(PortEndpoint),
1704 SubscriptionStatusRequest(SubscriptionRequest),
1705 SubscribeDtmfPayloadResponse(DtmfPayloadIdentity),
1706 UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity),
1707 LocationInfo {
1708 xml: String,
1710 },
1711 XmlAlarm(XmlAlarmMessage),
1712 CallCountRequest {
1713 value: u32,
1715 },
1716 CreateConferenceResponse(CreateConferenceResponse),
1717 DeleteConferenceResponse {
1718 conference_id: ConferenceId,
1719 result: DeleteConferenceResult,
1720 },
1721 ModifyConferenceResponse(ModifyConferenceResponse),
1722 AuditConferenceResponse(AuditConferenceResponse),
1723 AddParticipantResponse(AddParticipantResponse),
1724 AuditParticipantResponse(AuditParticipantResponse),
1725 KnownOpaque(KnownOpaqueMessage),
1726 Unknown(RawMessage),
1727}
1728
1729#[derive(Clone, Debug, Eq, PartialEq)]
1730pub enum ServerMessage {
1737 RegisterAck {
1738 keepalive_seconds: u32,
1739 secondary_keepalive_seconds: u32,
1740 protocol: ProtocolVersion,
1741 features: PhoneFeatures,
1742 date_template: DateTemplate,
1743 },
1744 RegisterReject {
1745 reason: String,
1746 },
1747 KeepAliveAck,
1748 UnregisterAck,
1749 CapabilitiesRequest,
1750 ConfigStatus(ConfigurationStatus),
1751 LineStatus {
1752 instance: u32,
1753 number: String,
1754 display_name: String,
1755 },
1756 ButtonTemplate {
1761 offset: u32,
1762 total: u32,
1763 buttons: Vec<ButtonTemplateEntry>,
1764 },
1765 Version {
1766 firmware: String,
1767 },
1768 ServerResponse {
1769 servers: Vec<SignalingServerEndpoint>,
1770 },
1771 TimeDate {
1772 year: u32,
1773 month: u32,
1774 weekday: u32,
1775 day: u32,
1776 hour: u32,
1777 minute: u32,
1778 second: u32,
1779 milliseconds: u32,
1780 unix_seconds: u32,
1781 },
1782 SoftKeyTemplate {
1783 actions: Vec<values::SoftKey>,
1784 },
1785 SoftKeySet {
1786 profile: SoftKeyProfile,
1787 },
1788 SelectSoftKeys {
1789 line_instance: u32,
1790 call_reference: u32,
1791 set: KeyMode,
1792 valid_mask: u32,
1794 },
1795 CallState {
1796 state: CallState,
1797 line_instance: u32,
1798 call_reference: u32,
1799 },
1800 CallInfo {
1801 info: CallInfo,
1802 line_instance: u32,
1803 call_reference: u32,
1804 },
1805 DisplayPrompt {
1806 timeout_seconds: u32,
1807 text: String,
1808 line_instance: u32,
1809 call_reference: u32,
1810 },
1811 ClearPrompt {
1812 line_instance: u32,
1813 call_reference: u32,
1814 },
1815 DisplayNotify {
1816 timeout_seconds: u32,
1817 text: String,
1818 },
1819 ClearNotify,
1820 DisplayPriorityNotify {
1821 timeout_seconds: u32,
1822 priority: NotificationPriority,
1823 text: String,
1824 },
1825 ClearPriorityNotify {
1826 priority: NotificationPriority,
1827 },
1828 NotifyDtmfTone(DtmfToneControl),
1829 SendDtmfTone(DtmfToneControl),
1830 StartAnnouncement {
1831 announcements: Vec<AnnouncementEntry>,
1832 end_of_ack: u32,
1833 conference_id: u32,
1834 matrix_conference_party_ids: Vec<u32>,
1835 hearing_conference_party_mask: u32,
1836 play_mode: u32,
1837 },
1838 StopAnnouncement {
1839 conference_id: u32,
1840 },
1841 AnnouncementFinish {
1842 conference_id: u32,
1843 play_status: u32,
1844 },
1845 ClearConference {
1846 conference_id: ConferenceId,
1847 service_number: u32,
1848 },
1849 CreateConferenceRequest(CreateConferenceRequest),
1850 DeleteConferenceRequest {
1851 conference_id: ConferenceId,
1852 },
1853 ModifyConferenceRequest(ModifyConferenceRequest),
1854 AuditConferenceRequest,
1855 AddParticipantRequest(AddParticipantRequest),
1856 DropParticipantRequest {
1857 conference_id: ConferenceId,
1858 call_reference: CallReference,
1859 },
1860 AuditParticipantRequest {
1861 conference_id: ConferenceId,
1862 },
1863 ChangeParticipantRequest(ChangeParticipantRequest),
1864 StopMultimediaTransmission(MultimediaStreamControl),
1865 FlowControlCommand(VideoFlowControl),
1866 CloseMultimediaReceiveChannel(MultimediaStreamControl),
1867 VideoDisplayCommand {
1868 conference_id: ConferenceId,
1869 call_reference: CallReference,
1870 layout_id: u32,
1871 },
1872 FlowControlNotify(VideoFlowControl),
1873 ActivateCallPlane {
1874 line_instance: u32,
1875 },
1876 DeactivateCallPlane,
1877 BackspaceResponse {
1878 line_instance: u32,
1879 call_reference: u32,
1880 },
1881 RegisterTokenAck,
1882 RegisterTokenReject {
1883 backoff_seconds: u32,
1884 },
1885 SetRinger {
1886 mode: RingerMode,
1887 duration: RingDuration,
1888 line_instance: u32,
1889 call_reference: u32,
1890 },
1891 SetLamp {
1892 stimulus: ButtonType,
1893 instance: u32,
1894 mode: LampMode,
1895 },
1896 StartTone {
1897 tone: Tone,
1898 direction: ToneDirection,
1899 line_instance: u32,
1900 call_reference: u32,
1901 },
1902 StopTone {
1903 line_instance: u32,
1904 call_reference: u32,
1905 },
1906 StartMulticastMediaReception(MulticastMediaReception),
1907 StartMulticastMediaTransmission(MulticastMediaTransmission),
1908 StopMulticastMediaReception {
1909 conference_id: ConferenceId,
1910 passthrough_party_id: crate::types::PassthroughPartyId,
1911 call_reference: CallReference,
1912 },
1913 StopMulticastMediaTransmission {
1914 conference_id: ConferenceId,
1915 passthrough_party_id: crate::types::PassthroughPartyId,
1916 call_reference: CallReference,
1917 },
1918 OpenReceiveChannel {
1919 call_reference: u32,
1920 passthrough_party_id: u32,
1921 packet_ms: u32,
1922 codec: Codec,
1923 echo_cancellation: EchoCancellation,
1924 telephone_event_payload: u8,
1926 source_address: IpAddr,
1927 source_port: u16,
1928 encryption: Option<MediaEncryption>,
1929 wire: Option<OpenReceiveChannelWire>,
1932 },
1933 CloseReceiveChannel(AudioStreamControl),
1934 ConnectionStatisticsRequest {
1935 directory_number: String,
1936 call_reference: u32,
1937 processing: StatisticsProcessing,
1938 },
1939 StartMediaTransmission {
1940 call_reference: u32,
1941 passthrough_party_id: u32,
1942 endpoint: MediaEndpoint,
1943 silence_suppression: SilenceSuppression,
1944 traffic_class: crate::types::MediaTrafficClass,
1946 encryption: Option<MediaEncryption>,
1947 wire: Option<StartMediaTransmissionWire>,
1950 },
1951 StopMediaTransmission(AudioStreamControl),
1952 SubscribeDtmfPayloadRequest(DtmfPayloadRequest),
1953 SubscribeDtmfPayloadError(DtmfPayloadIdentity),
1954 UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest),
1955 UnsubscribeDtmfPayloadError(DtmfPayloadIdentity),
1956 SetSpeakerMode(SpeakerMode),
1957 SetMicrophoneMode(MicrophoneMode),
1958 Reset(ResetType),
1959 DisplayText {
1960 text: String,
1961 },
1962 ClearDisplay,
1963 ForwardStatus {
1964 line_instance: u32,
1965 forward_all: Option<String>,
1966 forward_busy: Option<String>,
1967 forward_no_answer: Option<String>,
1968 },
1969 SpeedDialStatus {
1970 instance: u32,
1971 number: String,
1972 display_name: String,
1973 },
1974 DialedNumber {
1975 number: String,
1976 line_instance: u32,
1977 call_reference: u32,
1978 },
1979 StartMediaFailureDetection(MediaFailureDetection),
1980 UserToDeviceData(UserDataMessage),
1981 UserToDeviceDataV1(UserDataV1Message),
1982 FeatureStatus {
1983 instance: u32,
1984 button_type: ButtonType,
1985 label: String,
1986 state: u32,
1988 },
1989 ServiceUrlStatus {
1990 index: u32,
1991 url: String,
1992 label: String,
1993 extension_text: String,
1995 },
1996 CallSelectStatus {
1997 status: u32,
1999 call_reference: u32,
2000 line_instance: u32,
2001 },
2002 PortRequest(PortRequest),
2003 PortClose(PortClose),
2004 OpenMultimediaChannel(OpenMultimediaChannel),
2005 StartMultimediaTransmission(StartMultimediaTransmission),
2006 MiscellaneousCommand(MiscellaneousCommand),
2007 SubscriptionStatus {
2008 transaction_id: u32,
2009 feature_id: u32,
2010 timer_seconds: u32,
2011 cause: SubscriptionCause,
2012 },
2013 Notification {
2014 transaction_id: u32,
2015 feature_id: u32,
2016 status: BusyLampFieldState,
2017 text: String,
2018 },
2019 CallHistoryDisposition {
2020 disposition: CallHistoryDisposition,
2021 line_instance: u32,
2022 call_reference: u32,
2023 },
2024 CallCountResponse,
2025 RecordingStatus {
2026 call_reference: u32,
2027 active: bool,
2028 },
2029 KnownOpaque(KnownOpaqueMessage),
2030 Unknown(RawMessage),
2031}
2032
2033#[cfg(test)]
2034mod tests {
2035 use super::wire::{CodecError, Frame, FrameDecoder};
2036 use super::*;
2037
2038 #[test]
2039 fn protocol_fillers_have_semantic_defaults() {
2040 assert_eq!(
2041 ButtonTemplateEntry::default(),
2042 ButtonTemplateEntry {
2043 instance: 0,
2044 button_type: ButtonType::Unused,
2045 }
2046 );
2047 assert_eq!(
2048 MessageWaitingCounts::default(),
2049 MessageWaitingCounts { new: 0, old: 0 }
2050 );
2051 }
2052
2053 const fn test_rtp_payload_number(value: u32) -> RtpPayloadNumber {
2054 match RtpPayloadNumber::new(value) {
2055 Ok(value) => value,
2056 Err(_) => panic!("test RTP payload number is out of range"),
2057 }
2058 }
2059
2060 fn decode_frame(bytes: &[u8]) -> Frame {
2061 FrameDecoder::new().push(bytes).unwrap().remove(0)
2062 }
2063
2064 fn assert_contract_alignment(frame: &Frame) {
2065 use super::catalog::PayloadLayout;
2066
2067 let contract = frame.message_type().contract().unwrap();
2068 if !matches!(
2069 contract.payload_layout,
2070 PayloadLayout::Opaque
2071 | PayloadLayout::BoundedOpaque
2072 | PayloadLayout::BoundedPreserved
2073 | PayloadLayout::VersionAndLengthSelected
2074 | PayloadLayout::MinimumLengthPreserved
2075 ) {
2076 assert_eq!(frame.payload.len() % 4, 0, "{}", contract.id);
2077 }
2078 }
2079
2080 fn assert_client_round_trip(message: ClientMessage, protocol: ProtocolVersion) {
2081 let frame = decode_frame(&message.encode(protocol).unwrap());
2082 assert_contract_alignment(&frame);
2083 assert_eq!(
2084 ClientMessage::decode_with_version(frame, protocol).unwrap(),
2085 message
2086 );
2087 }
2088
2089 fn assert_server_round_trip(message: ServerMessage, protocol: ProtocolVersion) {
2090 let frame = decode_frame(&message.encode(protocol).unwrap());
2091 assert_contract_alignment(&frame);
2092 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
2093 }
2094
2095 fn assert_control_round_trip(message: ControlMessage, protocol: ProtocolVersion) {
2096 let frame = decode_frame(&message.encode(protocol).unwrap());
2097 assert_contract_alignment(&frame);
2098 assert_eq!(ControlMessage::decode(frame, protocol).unwrap(), message);
2099 }
2100
2101 #[test]
2102 fn multimedia_payload_exposes_only_typed_construction() {
2103 let capability = MultimediaVideoCapability::new(
2104 1_024,
2105 [MultimediaPictureFormat {
2106 format: VideoFormat::Cif4,
2107 minimum_picture_interval: 2,
2108 }],
2109 7,
2110 MultimediaVideoCapabilityArm::H264 {
2111 profile: 100,
2112 level: 42,
2113 custom_max_mbps: 40_500,
2114 custom_max_fs: 1_620,
2115 custom_max_dpb: 8_100,
2116 custom_max_br_and_cpb: 10_000,
2117 },
2118 )
2119 .unwrap();
2120 let payload = MultimediaPayload::new(test_rtp_payload_number(97), capability.clone());
2121 assert_eq!(payload.payload_number().get(), 97);
2122 assert_eq!(payload.descriptor().rfc_number(), 0);
2123 assert_eq!(payload.codec(), Codec::H264);
2124 assert_eq!(payload.video_capability(), Some(&capability));
2125
2126 let packetized = MultimediaPayload::with_descriptor(
2127 MultimediaPayloadDescriptor::new(4, payload.payload_number()),
2128 capability.clone(),
2129 );
2130 assert_eq!(packetized.descriptor().rfc_number(), 4);
2131 assert_eq!(packetized.payload_number(), payload.payload_number());
2132
2133 let debug = format!("{capability:?}");
2134 assert!(debug.contains("bit_rate: 1024"));
2135 assert!(!debug.contains("preserved_wire"));
2136 assert_eq!(
2137 RtpPayloadNumber::new(128),
2138 Err(RtpPayloadNumberError { actual: 128 })
2139 );
2140 }
2141
2142 #[test]
2143 fn multimedia_picture_formats_are_bounded_before_payload_construction() {
2144 let formats = [MultimediaPictureFormat {
2145 format: VideoFormat::Cif,
2146 minimum_picture_interval: 1,
2147 }; MAX_MULTIMEDIA_PICTURE_FORMATS + 1];
2148 assert_eq!(
2149 MultimediaVideoCapability::new(
2150 1_024,
2151 formats,
2152 0,
2153 MultimediaVideoCapabilityArm::H261 {
2154 temporal_spatial_trade_off_capability: 0,
2155 still_image_transmission: 0,
2156 },
2157 )
2158 .unwrap_err(),
2159 MultimediaCapabilityError {
2160 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
2161 actual: MAX_MULTIMEDIA_PICTURE_FORMATS + 1,
2162 }
2163 );
2164 }
2165
2166 #[test]
2167 fn media_request_identity_is_nonzero_and_exhaustion_never_wraps() {
2168 assert_eq!(MediaRequestToken::new(0), None);
2169 let token = MediaRequestToken::new(7).unwrap();
2170 assert_eq!(MediaRequestIdentity::new(0, token), None);
2171
2172 let first = MediaRequestIdentity::new(1, token).unwrap();
2173 let second = first.checked_next().unwrap();
2174 assert_eq!(second.generation(), 2);
2175 assert_eq!(second.token().get(), 8);
2176
2177 assert_eq!(
2178 MediaRequestToken::new(u32::MAX).unwrap().checked_next(),
2179 None
2180 );
2181 let exhausted_generation =
2182 MediaRequestIdentity::new(u64::MAX, MediaRequestToken::new(1).unwrap()).unwrap();
2183 assert_eq!(exhausted_generation.checked_next(), None);
2184 }
2185
2186 #[test]
2187 fn media_request_identity_matches_only_the_current_wire_token() {
2188 let identity =
2189 MediaRequestIdentity::new(2, MediaRequestToken::new(0x1020_3040).unwrap()).unwrap();
2190
2191 assert!(identity.accepts_ack(0x1020_3040, 0, 77));
2192 assert!(identity.accepts_ack(0x1020_3040, 77, 77));
2193 assert!(!identity.accepts_ack(0x1020_3040, 78, 77));
2194 assert!(!identity.accepts_ack(0x1020_303f, 77, 77));
2195 }
2196
2197 #[test]
2198 fn zero_party_fallback_cannot_settle_a_reopened_media_generation() {
2199 let first = MediaRequestIdentity::new(1, MediaRequestToken::new(700).unwrap()).unwrap();
2200 let reopened = first.checked_next().unwrap();
2201
2202 assert!(first.accepts_ack(0, 42, 42));
2204 assert!(!first.accepts_ack(0, 0, 42));
2205
2206 assert!(!reopened.accepts_ack(0, 42, 42));
2208 assert!(!reopened.accepts_ack(first.token().get(), 42, 42));
2209 assert!(reopened.accepts_ack(reopened.token().get(), 42, 42));
2210 }
2211
2212 #[test]
2213 fn decodes_7962_off_hook_capture_shape() {
2214 let frame = Frame::new(22, wire_id::OFF_HOOK, vec![1, 0, 0, 0, 42, 0, 0, 0]);
2215 assert_eq!(
2216 ClientMessage::decode(frame).unwrap(),
2217 ClientMessage::OffHook {
2218 line_instance: 1,
2219 call_reference: 42
2220 }
2221 );
2222 }
2223
2224 #[test]
2225 fn decodes_7961_v22_three_word_keypad_capture_shape() {
2226 let payload: Vec<_> = [8_u32, 1, 1]
2227 .into_iter()
2228 .flat_map(u32::to_le_bytes)
2229 .collect();
2230 let frame = Frame::new(22, wire_id::KEYPAD_BUTTON, payload.clone());
2231 let decoded = ClientMessage::decode(frame).unwrap();
2232 assert_eq!(
2233 decoded,
2234 ClientMessage::KeypadButton {
2235 button: Digit::Number(8),
2236 line_instance: 1,
2237 call_reference: 1,
2238 wire_layout: Some(KeypadButtonWireLayout::WithCallIdentity),
2239 }
2240 );
2241 let encoded = FrameDecoder::new()
2242 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
2243 .unwrap()
2244 .remove(0);
2245 assert_eq!(encoded.payload, payload);
2246 }
2247
2248 #[test]
2249 fn register_ack_is_protocol_zero_and_has_expected_fields() {
2250 let bytes = ServerMessage::RegisterAck {
2251 keepalive_seconds: 30,
2252 secondary_keepalive_seconds: 45,
2253 protocol: ProtocolVersion::V22,
2254 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2255 date_template: DateTemplate::default(),
2256 }
2257 .encode(ProtocolVersion::V22)
2258 .unwrap();
2259 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
2260 assert_eq!(frame.protocol_version, 0);
2261 assert_eq!(frame.message_id, wire_id::REGISTER_ACK);
2262 assert_eq!(
2263 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
2264 ServerMessage::RegisterAck {
2265 keepalive_seconds: 30,
2266 secondary_keepalive_seconds: 45,
2267 protocol: ProtocolVersion::V22,
2268 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2269 date_template: DateTemplate::default(),
2270 }
2271 );
2272 }
2273
2274 #[test]
2275 fn media_layout_sizes_match_supported_wire_specs() {
2276 let endpoint = MediaEndpoint {
2277 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2278 rtp_port: 4000,
2279 rtcp_port: 4001,
2280 codec: Codec::Pcmu,
2281 packet_ms: 20,
2282 max_frames_per_packet: 1,
2283 telephone_event_payload: 101,
2284 };
2285 let start = ServerMessage::StartMediaTransmission {
2286 call_reference: 7,
2287 passthrough_party_id: 9,
2288 endpoint,
2289 silence_suppression: SilenceSuppression::Off,
2290 traffic_class: crate::types::MediaTrafficClass::from_wire(184),
2291 encryption: None,
2292 wire: None,
2293 }
2294 .encode(ProtocolVersion::V17)
2295 .unwrap();
2296 assert_eq!(start.len(), 144); assert_eq!(&start[52..56], &184_u32.to_le_bytes());
2298 assert_eq!(&start[140..144], &1_u32.to_le_bytes());
2299 let open = ServerMessage::OpenReceiveChannel {
2300 call_reference: 7,
2301 passthrough_party_id: 9,
2302 packet_ms: 20,
2303 codec: Codec::Pcmu,
2304 echo_cancellation: EchoCancellation::On,
2305 telephone_event_payload: 101,
2306 source_address: endpoint.address,
2307 source_port: endpoint.rtp_port,
2308 encryption: None,
2309 wire: None,
2310 }
2311 .encode(ProtocolVersion::V17)
2312 .unwrap();
2313 assert_eq!(open.len(), 140); assert_eq!(&open[108..112], &1_u32.to_le_bytes());
2315
2316 let start_v3 = ServerMessage::StartMediaTransmission {
2317 call_reference: 7,
2318 passthrough_party_id: 9,
2319 endpoint,
2320 silence_suppression: SilenceSuppression::Off,
2321 traffic_class: crate::types::MediaTrafficClass::default(),
2322 encryption: None,
2323 wire: None,
2324 }
2325 .encode(ProtocolVersion::V3)
2326 .unwrap();
2327 assert_eq!(start_v3.len(), 120); let open_v3 = ServerMessage::OpenReceiveChannel {
2329 call_reference: 7,
2330 passthrough_party_id: 9,
2331 packet_ms: 20,
2332 codec: Codec::Pcmu,
2333 echo_cancellation: EchoCancellation::On,
2334 telephone_event_payload: 101,
2335 source_address: endpoint.address,
2336 source_port: endpoint.rtp_port,
2337 encryption: None,
2338 wire: None,
2339 }
2340 .encode(ProtocolVersion::V3)
2341 .unwrap();
2342 assert_eq!(open_v3.len(), 104); let start_v22 = ServerMessage::StartMediaTransmission {
2345 call_reference: 7,
2346 passthrough_party_id: 9,
2347 endpoint,
2348 silence_suppression: SilenceSuppression::Off,
2349 traffic_class: crate::types::MediaTrafficClass::default(),
2350 encryption: None,
2351 wire: None,
2352 }
2353 .encode(ProtocolVersion::V22)
2354 .unwrap();
2355 assert_eq!(start_v22.len(), 180); let open_v22 = ServerMessage::OpenReceiveChannel {
2357 call_reference: 7,
2358 passthrough_party_id: 9,
2359 packet_ms: 20,
2360 codec: Codec::Pcmu,
2361 echo_cancellation: EchoCancellation::On,
2362 telephone_event_payload: 101,
2363 source_address: endpoint.address,
2364 source_port: endpoint.rtp_port,
2365 encryption: None,
2366 wire: None,
2367 }
2368 .encode(ProtocolVersion::V22)
2369 .unwrap();
2370 assert_eq!(open_v22.len(), 180); }
2372
2373 #[test]
2374 fn media_close_layouts_consume_the_reference_fields_exactly() {
2375 let close = ServerMessage::CloseReceiveChannel(AudioStreamControl {
2376 conference_id: 6.into(),
2377 passthrough_party_id: 9.into(),
2378 call_reference: 7.into(),
2379 port_handling_flag: 11,
2380 });
2381 let close_v3 = close.encode(ProtocolVersion::V3).unwrap();
2382 assert_eq!(close_v3.len(), 28);
2383 assert_eq!(
2384 ServerMessage::decode(decode_frame(&close_v3), ProtocolVersion::V3).unwrap(),
2385 close
2386 );
2387 let close_v5 = close.encode(ProtocolVersion::V5).unwrap();
2388 assert_eq!(close_v5.len(), 28);
2389 assert_eq!(
2390 ServerMessage::decode(decode_frame(&close_v5), ProtocolVersion::V5).unwrap(),
2391 close
2392 );
2393
2394 let stop = ServerMessage::StopMediaTransmission(AudioStreamControl {
2395 conference_id: 6.into(),
2396 passthrough_party_id: 9.into(),
2397 call_reference: 7.into(),
2398 port_handling_flag: 11,
2399 });
2400 let bytes = stop.encode(ProtocolVersion::V22).unwrap();
2401 assert_eq!(bytes.len(), 28);
2402 assert_eq!(
2403 ServerMessage::decode(decode_frame(&bytes), ProtocolVersion::V22).unwrap(),
2404 stop
2405 );
2406
2407 let mut trailing = decode_frame(&bytes);
2408 trailing.payload.extend_from_slice(&[0; 4]);
2409 assert!(matches!(
2410 ServerMessage::decode(trailing, ProtocolVersion::V22),
2411 Err(CodecError::TrailingBytes { count: 4, .. })
2412 ));
2413 }
2414
2415 #[test]
2416 fn audio_packetization_round_trips_without_default_substitution() {
2417 let endpoint = MediaEndpoint {
2418 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2419 rtp_port: 4000,
2420 rtcp_port: 4001,
2421 codec: Codec::G72264k,
2422 packet_ms: 30,
2423 max_frames_per_packet: 2,
2424 telephone_event_payload: 101,
2425 };
2426 for protocol in [
2427 ProtocolVersion::V3,
2428 ProtocolVersion::V17,
2429 ProtocolVersion::V22,
2430 ] {
2431 let (source_address, source_port) = if protocol.wire() < 12 {
2432 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2433 } else {
2434 (endpoint.address, endpoint.rtp_port)
2435 };
2436 assert_server_round_trip(
2437 ServerMessage::OpenReceiveChannel {
2438 call_reference: 7,
2439 passthrough_party_id: 9,
2440 packet_ms: 30,
2441 codec: Codec::G72264k,
2442 echo_cancellation: EchoCancellation::On,
2443 telephone_event_payload: 101,
2444 source_address,
2445 source_port,
2446 encryption: None,
2447 wire: None,
2448 },
2449 protocol,
2450 );
2451 assert_server_round_trip(
2452 ServerMessage::StartMediaTransmission {
2453 call_reference: 7,
2454 passthrough_party_id: 9,
2455 endpoint,
2456 silence_suppression: SilenceSuppression::On,
2457 traffic_class: crate::types::MediaTrafficClass::default(),
2458 encryption: None,
2459 wire: None,
2460 },
2461 protocol,
2462 );
2463 assert_client_round_trip(
2464 ClientMessage::MediaTransmissionFailure {
2465 conference_id: 7,
2466 passthrough_party_id: 9,
2467 address: endpoint.address,
2468 port: endpoint.rtp_port,
2469 call_reference: 7,
2470 status: MediaStatus::UnspecifiedError,
2471 },
2472 protocol,
2473 );
2474 }
2475 }
2476
2477 #[test]
2478 fn ipv6_audio_endpoints_require_and_round_trip_extended_layouts() {
2479 let address: IpAddr = "2001:db8::42".parse().unwrap();
2480 let endpoint = MediaEndpoint {
2481 address,
2482 rtp_port: 40_000,
2483 rtcp_port: 40_001,
2484 codec: Codec::G72264k,
2485 packet_ms: 20,
2486 max_frames_per_packet: 1,
2487 telephone_event_payload: 101,
2488 };
2489 let start = ServerMessage::StartMediaTransmission {
2490 call_reference: 7,
2491 passthrough_party_id: 9,
2492 endpoint,
2493 silence_suppression: SilenceSuppression::Off,
2494 traffic_class: crate::types::MediaTrafficClass::default(),
2495 encryption: None,
2496 wire: None,
2497 };
2498 let receive_ack = ClientMessage::OpenReceiveChannelAck {
2499 status: MediaStatus::Ok,
2500 address,
2501 port: endpoint.rtp_port,
2502 passthrough_party_id: 9,
2503 call_reference: 7,
2504 };
2505 let transmit_ack = ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
2506 conference_id: 6,
2507 passthrough_party_id: 9,
2508 call_reference: 7,
2509 status: MediaStatus::Ok,
2510 address,
2511 port: endpoint.rtp_port,
2512 wire: None,
2513 });
2514 let failure = ClientMessage::MediaTransmissionFailure {
2515 conference_id: 7,
2516 passthrough_party_id: 9,
2517 address,
2518 port: endpoint.rtp_port,
2519 call_reference: 7,
2520 status: MediaStatus::UnspecifiedError,
2521 };
2522
2523 for protocol in [ProtocolVersion::V17, ProtocolVersion::V22] {
2524 assert_server_round_trip(start.clone(), protocol);
2525 assert_client_round_trip(receive_ack.clone(), protocol);
2526 assert_client_round_trip(transmit_ack.clone(), protocol);
2527 assert_client_round_trip(failure.clone(), protocol);
2528 }
2529 for result in [
2530 start.encode(ProtocolVersion::V16),
2531 receive_ack.encode(ProtocolVersion::V16),
2532 transmit_ack.encode(ProtocolVersion::V16),
2533 failure.encode(ProtocolVersion::V16),
2534 failure.encode(ProtocolVersion::V3),
2535 ] {
2536 assert!(matches!(
2537 result,
2538 Err(CodecError::InvalidValue {
2539 field: "IP address family for pre-v17 protocol"
2540 | "IP address family for this protocol version",
2541 ..
2542 })
2543 ));
2544 }
2545 }
2546
2547 #[test]
2548 fn skinny_dtmf_disables_the_telephone_event_payload_in_both_directions() {
2549 let endpoint = MediaEndpoint {
2550 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2551 rtp_port: 4000,
2552 rtcp_port: 4001,
2553 codec: Codec::Pcmu,
2554 packet_ms: 20,
2555 max_frames_per_packet: 1,
2556 telephone_event_payload: 0,
2557 };
2558 for protocol in [
2559 ProtocolVersion::V3,
2560 ProtocolVersion::V17,
2561 ProtocolVersion::V22,
2562 ] {
2563 let (source_address, source_port) = if protocol.wire() < 12 {
2564 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2565 } else {
2566 (endpoint.address, endpoint.rtp_port)
2567 };
2568 assert_server_round_trip(
2569 ServerMessage::OpenReceiveChannel {
2570 call_reference: 7,
2571 passthrough_party_id: 9,
2572 packet_ms: 20,
2573 codec: Codec::Pcmu,
2574 echo_cancellation: EchoCancellation::On,
2575 telephone_event_payload: 0,
2576 source_address,
2577 source_port,
2578 encryption: None,
2579 wire: None,
2580 },
2581 protocol,
2582 );
2583 assert_server_round_trip(
2584 ServerMessage::StartMediaTransmission {
2585 call_reference: 7,
2586 passthrough_party_id: 9,
2587 endpoint,
2588 silence_suppression: SilenceSuppression::Off,
2589 traffic_class: crate::types::MediaTrafficClass::default(),
2590 encryption: None,
2591 wire: None,
2592 },
2593 protocol,
2594 );
2595 }
2596 }
2597
2598 #[test]
2599 fn open_receive_wildcard_source_round_trips_for_all_supported_layouts() {
2600 for protocol in [
2601 ProtocolVersion::V3,
2602 ProtocolVersion::V17,
2603 ProtocolVersion::V22,
2604 ] {
2605 assert_server_round_trip(
2606 ServerMessage::OpenReceiveChannel {
2607 call_reference: 1,
2608 passthrough_party_id: 1,
2609 packet_ms: 20,
2610 codec: Codec::Pcma,
2611 echo_cancellation: EchoCancellation::Off,
2612 telephone_event_payload: 101,
2613 source_address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
2614 source_port: 0,
2615 encryption: None,
2616 wire: None,
2617 },
2618 protocol,
2619 );
2620 }
2621 }
2622
2623 #[test]
2624 fn media_encryption_round_trips_without_exposing_key_material() {
2625 let key = b"private-key-1234";
2626 let salt = b"private-salt-123";
2627 let encryption =
2628 MediaEncryption::new(EncryptionMethod::Aes128HmacSha1_80, key, salt, 1, 64).unwrap();
2629 assert_eq!(encryption.key(), key);
2630 assert_eq!(encryption.salt(), salt);
2631
2632 let debug = format!("{encryption:?}");
2633 assert!(debug.contains("<redacted>"));
2634 assert!(!debug.contains("112, 114, 105, 118, 97, 116, 101"));
2635 assert!(!debug.contains("private-key"));
2636 let endpoint = MediaEndpoint {
2637 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
2638 rtp_port: 40_000,
2639 rtcp_port: 40_001,
2640 codec: Codec::Pcmu,
2641 packet_ms: 20,
2642 max_frames_per_packet: 1,
2643 telephone_event_payload: 101,
2644 };
2645
2646 for protocol in [
2647 ProtocolVersion::new(12).unwrap(),
2648 ProtocolVersion::V17,
2649 ProtocolVersion::V22,
2650 ] {
2651 let open = ServerMessage::OpenReceiveChannel {
2652 call_reference: 7,
2653 passthrough_party_id: 9,
2654 packet_ms: 20,
2655 codec: Codec::Pcmu,
2656 echo_cancellation: EchoCancellation::On,
2657 telephone_event_payload: 101,
2658 source_address: endpoint.address,
2659 source_port: endpoint.rtp_port,
2660 encryption: Some(encryption.clone()),
2661 wire: None,
2662 };
2663 let open_debug = format!("{open:?}");
2664 assert!(open_debug.contains("<redacted>"));
2665 assert!(!open_debug.contains("112, 114, 105, 118, 97, 116, 101"));
2666 assert_server_round_trip(open, protocol);
2667 assert_server_round_trip(
2668 ServerMessage::StartMediaTransmission {
2669 call_reference: 7,
2670 passthrough_party_id: 9,
2671 endpoint,
2672 silence_suppression: SilenceSuppression::Off,
2673 traffic_class: crate::types::MediaTrafficClass::default(),
2674 encryption: Some(encryption.clone()),
2675 wire: None,
2676 },
2677 protocol,
2678 );
2679 }
2680 }
2681
2682 #[test]
2683 fn media_encryption_rejects_oversized_secrets_with_metadata_only_errors() {
2684 let oversized_key = [0xa5; 17];
2685 let error = MediaEncryption::new(
2686 EncryptionMethod::Aes128HmacSha1_32,
2687 &oversized_key,
2688 &[],
2689 0,
2690 0,
2691 )
2692 .unwrap_err();
2693 assert!(matches!(
2694 error,
2695 CodecError::SecretTooLong {
2696 field: "media encryption key",
2697 actual: 17,
2698 maximum: 16,
2699 }
2700 ));
2701 assert!(!error.to_string().contains("165"));
2702
2703 let oversized_salt = [0x5a; 17];
2704 let error = MediaEncryption::new(
2705 EncryptionMethod::Aes128HmacSha1_32,
2706 &[],
2707 &oversized_salt,
2708 0,
2709 0,
2710 )
2711 .unwrap_err();
2712 assert!(matches!(
2713 error,
2714 CodecError::SecretTooLong {
2715 field: "media encryption salt",
2716 actual: 17,
2717 maximum: 16,
2718 }
2719 ));
2720 assert!(!error.to_string().contains("90"));
2721 }
2722
2723 #[test]
2724 fn common_client_messages_round_trip_semantically() {
2725 assert_client_round_trip(
2726 ClientMessage::FeatureStatusRequest {
2727 index: 7,
2728 capabilities: 1,
2729 },
2730 ProtocolVersion::V22,
2731 );
2732 assert_client_round_trip(
2733 ClientMessage::OffHookWithCallingParty {
2734 calling_party_number: "1001".into(),
2735 voice_mailbox: "5001".into(),
2736 line_instance: 1,
2737 },
2738 ProtocolVersion::V3,
2739 );
2740 assert_client_round_trip(
2741 ClientMessage::RegisterToken(RegisterTokenMessage {
2742 device_id: DeviceId::new("SEP001122334455").unwrap(),
2743 device_instance: 2,
2744 address: "2001:db8::42".parse().unwrap(),
2745 device_type: DeviceType::Cisco7962,
2746 flags: 6,
2747 }),
2748 ProtocolVersion::V22,
2749 );
2750 assert_control_round_trip(
2751 ControlMessage::MediaResourceNotification(MediaResourceNotification {
2752 device_type: DeviceType::Unknown(0xfeed),
2753 in_service_streams: 2,
2754 max_streams_per_conference: 4,
2755 out_of_service_streams: 1,
2756 }),
2757 ProtocolVersion::V17,
2758 );
2759 assert_client_round_trip(
2760 ClientMessage::SubscriptionStatusRequest(SubscriptionRequest {
2761 transaction_id: 0x4b,
2762 feature_id: 1,
2763 timer_seconds: 30,
2764 subscription_id: "4000".into(),
2765 }),
2766 ProtocolVersion::V22,
2767 );
2768 for message in [
2769 ClientMessage::SubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
2770 payload_type: 101,
2771 conference_id: 42,
2772 passthrough_party_id: 7,
2773 }),
2774 ClientMessage::UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
2775 payload_type: 102,
2776 conference_id: 43,
2777 passthrough_party_id: 8,
2778 }),
2779 ] {
2780 let encoded = message.encode(ProtocolVersion::V22).unwrap();
2781 let frame = decode_frame(&encoded);
2782 assert_eq!(frame.payload.len(), 12);
2783 assert_eq!(
2784 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
2785 message
2786 );
2787 }
2788 assert_client_round_trip(
2789 ClientMessage::DeviceToUserDataV1(UserDataV1Message {
2790 application_id: 7,
2791 line_instance: 1,
2792 call_reference: 42,
2793 transaction_id: 9,
2794 sequence_flag: 1,
2795 display_priority: 2,
2796 conference_id: 42,
2797 application_instance_id: 3,
2798 routing: 4,
2799 data: b"<CiscoIPPhoneText/>".to_vec(),
2800 }),
2801 ProtocolVersion::V17,
2802 );
2803 assert_client_round_trip(
2804 ClientMessage::DeviceToUserDataResponse(UserDataMessage {
2805 application_id: 8,
2806 line_instance: 2,
2807 call_reference: 43,
2808 transaction_id: 10,
2809 data: b"<CiscoIPPhoneResponse/>".to_vec(),
2810 }),
2811 ProtocolVersion::V17,
2812 );
2813 assert_client_round_trip(
2814 ClientMessage::DeviceToUserData(UserDataMessage {
2815 application_id: 9,
2816 line_instance: 2,
2817 call_reference: 44,
2818 transaction_id: 11,
2819 data: b"<CiscoIPPhoneInput/>".to_vec(),
2820 }),
2821 ProtocolVersion::V17,
2822 );
2823 assert_client_round_trip(
2824 ClientMessage::DeviceToUserDataResponseV1(UserDataV1Message {
2825 application_id: 9,
2826 line_instance: 2,
2827 call_reference: 44,
2828 transaction_id: 11,
2829 sequence_flag: 2,
2830 display_priority: 1,
2831 conference_id: 44,
2832 application_instance_id: 9,
2833 routing: 1,
2834 data: b"<CiscoIPPhoneResponse/>".to_vec(),
2835 }),
2836 ProtocolVersion::V17,
2837 );
2838 assert_client_round_trip(
2839 ClientMessage::LocationInfo {
2840 xml: "<location><building>west</building></location>".into(),
2841 },
2842 ProtocolVersion::V22,
2843 );
2844 assert_client_round_trip(
2845 ClientMessage::XmlAlarm(
2846 XmlAlarmMessage::from_xml(b"<alarm><severity>warning</severity></alarm>").unwrap(),
2847 ),
2848 ProtocolVersion::V22,
2849 );
2850 assert_client_round_trip(
2851 ClientMessage::CallCountRequest { value: 2 },
2852 ProtocolVersion::V22,
2853 );
2854 assert_control_round_trip(
2855 ControlMessage::PortResponse(PortEndpoint {
2856 conference_id: 42,
2857 call_reference: 42,
2858 passthrough_party_id: 8,
2859 address: "2001:db8::8".parse().unwrap(),
2860 rtp_port: 16_000,
2861 rtcp_port: 16_001,
2862 media_type: Some(MediaType::Audio),
2863 }),
2864 ProtocolVersion::V22,
2865 );
2866 assert_control_round_trip(
2867 ControlMessage::CreateConferenceResponse(CreateConferenceResponse {
2868 conference_id: ConferenceId::new(42),
2869 result: CreateConferenceResult::Ok,
2870 passthrough_data: vec![1, 2, 3],
2871 }),
2872 ProtocolVersion::V22,
2873 );
2874 assert_control_round_trip(
2875 ControlMessage::DeleteConferenceResponse {
2876 conference_id: ConferenceId::new(42),
2877 result: DeleteConferenceResult::ConferenceDoesNotExist,
2878 },
2879 ProtocolVersion::V22,
2880 );
2881 assert_control_round_trip(
2882 ControlMessage::ModifyConferenceResponse(ModifyConferenceResponse {
2883 conference_id: ConferenceId::new(42),
2884 result: ModifyConferenceResult::MoreActiveCallsThanReserved,
2885 passthrough_data: vec![4, 5],
2886 }),
2887 ProtocolVersion::V22,
2888 );
2889 assert_control_round_trip(
2890 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
2891 last: 1,
2892 entries: vec![AuditConferenceEntry {
2893 conference_id: ConferenceId::new(42),
2894 resource_type: ConferenceResourceType::Conference,
2895 reserved_participants: 8,
2896 active_participants: 3,
2897 application_id: ApplicationId::new(7),
2898 application_conference_id: "festival-42".into(),
2899 application_data: "main-stage".into(),
2900 }],
2901 }),
2902 ProtocolVersion::V22,
2903 );
2904 assert_control_round_trip(
2905 ControlMessage::AddParticipantResponse(AddParticipantResponse {
2906 conference_id: ConferenceId::new(42),
2907 call_reference: CallReference::new(100),
2908 result: AddParticipantResult::Ok,
2909 bridge_participant_id: BoundedBytes::try_from(vec![3; 257]).unwrap(),
2910 }),
2911 ProtocolVersion::V22,
2912 );
2913 assert_control_round_trip(
2914 ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
2915 result: AuditParticipantResult::Ok,
2916 last: 1,
2917 conference_id: ConferenceId::new(42),
2918 number_of_entries: 2,
2919 participant_entries: vec![1, 2, 3, 4],
2920 }),
2921 ProtocolVersion::V22,
2922 );
2923 }
2924
2925 #[test]
2926 fn common_server_messages_round_trip_semantically() {
2927 assert_server_round_trip(
2928 ServerMessage::SpeedDialStatus {
2929 instance: 7,
2930 number: "2001".into(),
2931 display_name: "Reception".into(),
2932 },
2933 ProtocolVersion::V3,
2934 );
2935 assert_server_round_trip(
2936 ServerMessage::ServiceUrlStatus {
2937 index: 4,
2938 url: "http://services.invalid/directory".into(),
2939 label: "Directory".into(),
2940 extension_text: String::new(),
2941 },
2942 ProtocolVersion::V3,
2943 );
2944 for protocol in [
2945 ProtocolVersion::V3,
2946 ProtocolVersion::V17,
2947 ProtocolVersion::V22,
2948 ] {
2949 assert_server_round_trip(
2950 ServerMessage::ConnectionStatisticsRequest {
2951 directory_number: "1001".into(),
2952 call_reference: 42,
2953 processing: StatisticsProcessing::DoNotClear,
2954 },
2955 protocol,
2956 );
2957 }
2958 assert_server_round_trip(
2959 ServerMessage::DisplayPriorityNotify {
2960 timeout_seconds: 5,
2961 priority: NotificationPriority::Voicemail,
2962 text: "Incoming call".into(),
2963 },
2964 ProtocolVersion::V17,
2965 );
2966 assert_server_round_trip(
2967 ServerMessage::FeatureStatus {
2968 instance: 2,
2969 button_type: ButtonType::BlfSpeedDial,
2970 label: "Support".into(),
2971 state: 0x0002_0101,
2972 },
2973 ProtocolVersion::V22,
2974 );
2975 assert_server_round_trip(
2976 ServerMessage::PortRequest(PortRequest {
2977 conference_id: 42.into(),
2978 call_reference: 42.into(),
2979 passthrough_party_id: 9.into(),
2980 transport: MediaTransport::Rtp,
2981 address_type: Some(IpAddressType::Ipv4AndIpv6),
2982 media_type: Some(MediaType::Audio),
2983 }),
2984 ProtocolVersion::V22,
2985 );
2986 assert_server_round_trip(
2987 ServerMessage::Notification {
2988 transaction_id: 3,
2989 feature_id: 1,
2990 status: BusyLampFieldState::Unknown(77),
2991 text: "4000".into(),
2992 },
2993 ProtocolVersion::V22,
2994 );
2995 assert_server_round_trip(
2996 ServerMessage::SubscriptionStatus {
2997 transaction_id: 3,
2998 feature_id: 1,
2999 timer_seconds: 30,
3000 cause: SubscriptionCause::Ok,
3001 },
3002 ProtocolVersion::V22,
3003 );
3004 assert_server_round_trip(
3005 ServerMessage::UserToDeviceData(UserDataMessage {
3006 application_id: 7,
3007 line_instance: 1,
3008 call_reference: 42,
3009 transaction_id: 9,
3010 data: b"<CiscoIPPhoneText/>".to_vec(),
3011 }),
3012 ProtocolVersion::V17,
3013 );
3014 assert_server_round_trip(
3015 ServerMessage::UserToDeviceDataV1(UserDataV1Message {
3016 application_id: 7,
3017 line_instance: 1,
3018 call_reference: 42,
3019 transaction_id: 9,
3020 sequence_flag: 2,
3021 display_priority: 1,
3022 conference_id: 42,
3023 application_instance_id: 7,
3024 routing: 1,
3025 data: b"<CiscoIPPhoneMenu/>".to_vec(),
3026 }),
3027 ProtocolVersion::V17,
3028 );
3029 assert_server_round_trip(
3030 ServerMessage::CallHistoryDisposition {
3031 disposition: CallHistoryDisposition::Missed,
3032 line_instance: 1,
3033 call_reference: 42,
3034 },
3035 ProtocolVersion::V22,
3036 );
3037 assert_server_round_trip(ServerMessage::CallCountResponse, ProtocolVersion::V22);
3038 for message in [
3039 ServerMessage::SubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3040 payload_type: 101,
3041 conference_id: 42,
3042 passthrough_party_id: 7,
3043 dtmf_type: 2,
3044 }),
3045 ServerMessage::SubscribeDtmfPayloadError(DtmfPayloadIdentity {
3046 payload_type: 102,
3047 conference_id: 43,
3048 passthrough_party_id: 8,
3049 }),
3050 ServerMessage::UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3051 payload_type: 103,
3052 conference_id: 44,
3053 passthrough_party_id: 9,
3054 dtmf_type: 3,
3055 }),
3056 ServerMessage::UnsubscribeDtmfPayloadError(DtmfPayloadIdentity {
3057 payload_type: 104,
3058 conference_id: 45,
3059 passthrough_party_id: 10,
3060 }),
3061 ] {
3062 let encoded = message.encode(ProtocolVersion::V22).unwrap();
3063 let frame = decode_frame(&encoded);
3064 assert!(matches!(frame.payload.len(), 12 | 16));
3065 assert_eq!(
3066 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
3067 message
3068 );
3069 }
3070 assert_server_round_trip(
3071 ServerMessage::RecordingStatus {
3072 call_reference: 42,
3073 active: true,
3074 },
3075 ProtocolVersion::V22,
3076 );
3077 assert_control_round_trip(
3078 ControlMessage::StartAnnouncement {
3079 announcements: vec![
3080 AnnouncementEntry {
3081 locale: 1,
3082 country: 46,
3083 tone: Tone::Zip,
3084 },
3085 AnnouncementEntry {
3086 locale: 0,
3087 country: 0,
3088 tone: Tone::Silence,
3089 },
3090 AnnouncementEntry {
3091 locale: 2,
3092 country: 1,
3093 tone: Tone::RecorderWarning,
3094 },
3095 ],
3096 end_of_ack: EndOfAnnouncementAck::Required,
3097 conference_id: 42,
3098 matrix_conference_party_ids: vec![7, 0, 9],
3099 hearing_conference_party_mask: 0b101,
3100 play_mode: AnnouncementPlayMode::Continuous,
3101 },
3102 ProtocolVersion::V22,
3103 );
3104 assert_control_round_trip(
3105 ControlMessage::StopAnnouncement { conference_id: 42 },
3106 ProtocolVersion::V22,
3107 );
3108 assert_control_round_trip(
3109 ControlMessage::AnnouncementFinish {
3110 conference_id: 42,
3111 play_status: AnnouncementPlayStatus::Unknown(3),
3112 },
3113 ProtocolVersion::V22,
3114 );
3115 assert_control_round_trip(
3116 ControlMessage::ClearConference {
3117 conference_id: ConferenceId::new(42),
3118 service_number: 3,
3119 },
3120 ProtocolVersion::V22,
3121 );
3122 assert_control_round_trip(
3123 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3124 conference_id: ConferenceId::new(42),
3125 reserved_participants: 8,
3126 resource_type: ConferenceResourceType::Conference,
3127 application_id: ApplicationId::new(7),
3128 application_conference_id: "festival-42".into(),
3129 application_data: "main-stage".into(),
3130 passthrough_data: vec![1, 2, 3],
3131 }),
3132 ProtocolVersion::V22,
3133 );
3134 assert_control_round_trip(
3135 ControlMessage::DeleteConferenceRequest {
3136 conference_id: ConferenceId::new(42),
3137 },
3138 ProtocolVersion::V22,
3139 );
3140 assert_control_round_trip(
3141 ControlMessage::ModifyConferenceRequest(ModifyConferenceRequest {
3142 conference_id: ConferenceId::new(42),
3143 reserved_participants: 12,
3144 application_id: ApplicationId::new(7),
3145 application_conference_id: "festival-42".into(),
3146 application_data: "main-stage".into(),
3147 passthrough_data: vec![4, 5],
3148 }),
3149 ProtocolVersion::V22,
3150 );
3151 assert_control_round_trip(ControlMessage::AuditConferenceRequest, ProtocolVersion::V22);
3152 assert_control_round_trip(
3153 ControlMessage::AddParticipantRequest(AddParticipantRequest {
3154 conference_id: ConferenceId::new(42),
3155 participant: ConferenceParticipant {
3156 call_reference: CallReference::new(100),
3157 presentation_restrictions: PartyInformationRestrictions::CALLING_NUMBER,
3158 name: "Festival Caller".into(),
3159 number: "1001".into(),
3160 conference_name: "Main Stage".into(),
3161 },
3162 }),
3163 ProtocolVersion::V22,
3164 );
3165 assert_control_round_trip(
3166 ControlMessage::DropParticipantRequest {
3167 conference_id: ConferenceId::new(42),
3168 call_reference: CallReference::new(100),
3169 },
3170 ProtocolVersion::V22,
3171 );
3172 assert_control_round_trip(
3173 ControlMessage::AuditParticipantRequest {
3174 conference_id: ConferenceId::new(42),
3175 },
3176 ProtocolVersion::V22,
3177 );
3178 }
3179
3180 #[test]
3181 fn connection_statistics_round_trip_all_layouts_and_redact_opaque_fields() {
3182 let statistics = ConnectionStatistics {
3183 directory_number: "2002".into(),
3184 call_reference: 42,
3185 processing: StatisticsProcessing::Clear,
3186 packets_sent: 100,
3187 octets_sent: 8_000,
3188 packets_received: 98,
3189 octets_received: 7_840,
3190 packets_lost: 2,
3191 jitter_millis: 7,
3192 latency_millis: 18,
3193 quality: ConnectionQualityStatistics::new(b"MLQK=4.5;Secret=opaque".to_vec()).unwrap(),
3194 };
3195 for protocol in [
3196 ProtocolVersion::V3,
3197 ProtocolVersion::V19,
3198 ProtocolVersion::V22,
3199 ] {
3200 assert_client_round_trip(
3201 ClientMessage::ConnectionStatisticsResponse(statistics.clone()),
3202 protocol,
3203 );
3204 }
3205 let debug = format!("{statistics:?}");
3206 assert!(!debug.contains("2002"));
3207 assert!(!debug.contains("Secret"));
3208 assert!(debug.contains("byte_count"));
3209 assert!(matches!(
3210 ConnectionQualityStatistics::new(vec![0; CONNECTION_QUALITY_MAX_BYTES + 1]),
3211 Err(CodecError::CountTooLarge {
3212 field: "quality statistics",
3213 maximum: CONNECTION_QUALITY_MAX_BYTES,
3214 ..
3215 })
3216 ));
3217 }
3218
3219 #[test]
3220 fn dtmf_subscription_messages_require_their_exact_word_layouts() {
3221 for message_id in [
3222 wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES,
3223 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_RES,
3224 ] {
3225 assert!(ClientMessage::decode(Frame::new(22, message_id, Vec::new())).is_err());
3226 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 11])).is_err());
3227 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 12])).is_ok());
3228 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 13])).is_err());
3229 }
3230 for (message_id, size) in [
3231 (wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3232 (wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3233 (wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3234 (wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3235 ] {
3236 assert!(
3237 ServerMessage::decode(
3238 Frame::new(22, message_id, vec![0; size - 1]),
3239 ProtocolVersion::V22,
3240 )
3241 .is_err()
3242 );
3243 assert!(
3244 ServerMessage::decode(
3245 Frame::new(22, message_id, vec![0; size]),
3246 ProtocolVersion::V22,
3247 )
3248 .is_ok()
3249 );
3250 assert!(
3251 ServerMessage::decode(
3252 Frame::new(22, message_id, vec![0; size + 1]),
3253 ProtocolVersion::V22,
3254 )
3255 .is_err()
3256 );
3257 }
3258 }
3259
3260 #[test]
3261 fn announcement_lists_enforce_station_bounds() {
3262 let error = ServerMessage::StartAnnouncement {
3263 announcements: vec![
3264 AnnouncementEntry {
3265 locale: 1,
3266 country: 1,
3267 tone: Tone::Zip,
3268 };
3269 33
3270 ],
3271 end_of_ack: 0,
3272 conference_id: 1,
3273 matrix_conference_party_ids: Vec::new(),
3274 hearing_conference_party_mask: 0,
3275 play_mode: 0,
3276 }
3277 .encode(ProtocolVersion::V22)
3278 .unwrap_err();
3279 assert!(matches!(
3280 error,
3281 CodecError::CountTooLarge {
3282 field: "announcements",
3283 count: 33,
3284 maximum: 32,
3285 ..
3286 }
3287 ));
3288
3289 let error = ServerMessage::StartAnnouncement {
3290 announcements: Vec::new(),
3291 end_of_ack: 0,
3292 conference_id: 1,
3293 matrix_conference_party_ids: (1..=17).collect(),
3294 hearing_conference_party_mask: 0,
3295 play_mode: 0,
3296 }
3297 .encode(ProtocolVersion::V22)
3298 .unwrap_err();
3299 assert!(matches!(
3300 error,
3301 CodecError::CountTooLarge {
3302 field: "matrix conference party identifiers",
3303 count: 17,
3304 maximum: 16,
3305 ..
3306 }
3307 ));
3308 }
3309
3310 #[test]
3311 fn enbloc_uses_the_protocol_19_alignment_boundary() {
3312 for (protocol, payload_len, line_offset) in [
3313 (ProtocolVersion::V18, 28, 24),
3314 (ProtocolVersion::V19, 32, 28),
3315 ] {
3316 let message = ClientMessage::EnblocCall {
3317 called_party: "9801".into(),
3318 line_instance: 3,
3319 };
3320 let frame = FrameDecoder::new()
3321 .push(&message.encode(protocol).unwrap())
3322 .unwrap()
3323 .remove(0);
3324 assert_eq!(frame.payload.len(), payload_len);
3325 assert_eq!(
3326 &frame.payload[line_offset..line_offset + 4],
3327 &3_u32.to_le_bytes()
3328 );
3329 assert_eq!(
3330 ClientMessage::decode_with_version(frame, protocol).unwrap(),
3331 message
3332 );
3333 }
3334 }
3335
3336 #[test]
3337 fn opaque_and_unknown_messages_are_byte_lossless() {
3338 let known_payload = vec![0, 1, 2, 0xff, 4];
3339 for id in [
3340 MessageId::MediaPortList,
3341 MessageId::SpcpRegisterTokenRequest,
3342 ] {
3343 let known = ClientMessage::decode_with_version(
3344 Frame::new(19, id.wire_value(), known_payload.clone()),
3345 ProtocolVersion::V22,
3346 )
3347 .unwrap();
3348 assert!(matches!(known, ClientMessage::KnownOpaque(_)));
3349 let known_frame = decode_frame(&known.encode(ProtocolVersion::V22).unwrap());
3350 assert_eq!(known_frame.protocol_version, 19);
3351 assert_eq!(known_frame.message_id, id.wire_value());
3352 assert_eq!(known_frame.payload, known_payload);
3353 }
3354
3355 for id in [
3356 MessageId::SetHookFlashDetect,
3357 MessageId::StartMediaReception,
3358 MessageId::StopMediaReception,
3359 MessageId::EnunciatorCommand,
3360 MessageId::SpcpRegisterTokenAck,
3361 MessageId::SpcpRegisterTokenReject,
3362 ] {
3363 let known = ServerMessage::decode(
3364 Frame::new(19, id.wire_value(), known_payload.clone()),
3365 ProtocolVersion::V22,
3366 )
3367 .unwrap();
3368 assert!(matches!(known, ServerMessage::KnownOpaque(_)));
3369 let known_frame = decode_frame(&known.encode(ProtocolVersion::V22).unwrap());
3370 assert_eq!(known_frame.protocol_version, 19);
3371 assert_eq!(known_frame.message_id, id.wire_value());
3372 assert_eq!(known_frame.payload, known_payload);
3373 }
3374
3375 let unknown_payload = vec![9, 8, 7, 6];
3376 let unknown = ServerMessage::decode(
3377 Frame::new(19, 0xdead_beef, unknown_payload.clone()),
3378 ProtocolVersion::V19,
3379 )
3380 .unwrap();
3381 assert!(matches!(unknown, ServerMessage::Unknown(_)));
3382 let unknown_frame = decode_frame(&unknown.encode(ProtocolVersion::V22).unwrap());
3383 assert_eq!(unknown_frame.message_id, 0xdead_beef);
3384 assert_eq!(unknown_frame.protocol_version, 19);
3385 assert_eq!(unknown_frame.payload, unknown_payload);
3386 }
3387
3388 #[test]
3389 fn preserve_only_payloads_obey_the_frame_bound() {
3390 let error = ClientMessage::decode_with_version(
3391 Frame::new(
3392 ProtocolVersion::V22.wire(),
3393 MessageId::MediaPortList.wire_value(),
3394 vec![0; MAX_OPAQUE_MESSAGE_BYTES + 1],
3395 ),
3396 ProtocolVersion::V22,
3397 )
3398 .unwrap_err();
3399
3400 assert_eq!(error, CodecError::FrameTooLarge(wire::MAX_FRAME_SIZE + 1));
3401 }
3402
3403 #[test]
3404 fn opaque_encoding_cannot_bypass_a_typed_contract() {
3405 let message = ClientMessage::KnownOpaque(KnownOpaqueMessage {
3406 id: MessageId::IpPort,
3407 protocol_version: ProtocolVersion::V22.wire(),
3408 payload: BoundedBytes::default(),
3409 });
3410
3411 assert!(matches!(
3412 message.encode(ProtocolVersion::V22),
3413 Err(CodecError::InvalidValue {
3414 message_id: wire_id::IP_PORT,
3415 field: "opaque preservation requires an opaque-only contract",
3416 ..
3417 })
3418 ));
3419 }
3420
3421 #[test]
3422 fn malformed_counts_and_oversized_text_are_rejected() {
3423 assert!(matches!(
3424 ClientMessage::decode(Frame::new(
3425 22,
3426 wire_id::CAPABILITIES_RES,
3427 19_u32.to_le_bytes().to_vec(),
3428 )),
3429 Err(CodecError::CountTooLarge { .. })
3430 ));
3431 assert!(matches!(
3432 ServerMessage::DisplayText {
3433 text: "x".repeat(32),
3434 }
3435 .encode(ProtocolVersion::V22),
3436 Err(CodecError::TextTooLong { .. })
3437 ));
3438 assert!(matches!(
3439 ClientMessage::DeviceToUserData(UserDataMessage {
3440 application_id: 1,
3441 line_instance: 1,
3442 call_reference: 1,
3443 transaction_id: 1,
3444 data: vec![0; 2001],
3445 })
3446 .encode(ProtocolVersion::V22),
3447 Err(CodecError::CountTooLarge { .. })
3448 ));
3449 assert!(matches!(
3450 ClientMessage::decode(Frame::new(
3451 22,
3452 wire_id::IP_PORT,
3453 70_000_u32.to_le_bytes().to_vec(),
3454 )),
3455 Err(CodecError::InvalidValue { .. })
3456 ));
3457 assert!(matches!(
3458 ServerMessage::StartMediaTransmission {
3459 call_reference: 1,
3460 passthrough_party_id: 1,
3461 endpoint: MediaEndpoint {
3462 address: "2001:db8::1".parse().unwrap(),
3463 rtp_port: 4000,
3464 rtcp_port: 4001,
3465 codec: Codec::Pcmu,
3466 packet_ms: 20,
3467 max_frames_per_packet: 1,
3468 telephone_event_payload: 101,
3469 },
3470 silence_suppression: SilenceSuppression::Off,
3471 traffic_class: crate::types::MediaTrafficClass::default(),
3472 encryption: None,
3473 wire: None,
3474 }
3475 .encode(ProtocolVersion::V3),
3476 Err(CodecError::InvalidValue { .. })
3477 ));
3478 assert!(matches!(
3479 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3480 conference_id: ConferenceId::new(1),
3481 reserved_participants: 2,
3482 resource_type: ConferenceResourceType::Conference,
3483 application_id: ApplicationId::new(1),
3484 application_conference_id: "conference-1".into(),
3485 application_data: String::new(),
3486 passthrough_data: vec![0; 2001],
3487 })
3488 .encode(ProtocolVersion::V22),
3489 Err(CodecError::CountTooLarge {
3490 field: "conference passthrough data",
3491 count: 2001,
3492 maximum: 2000,
3493 ..
3494 })
3495 ));
3496 assert!(matches!(
3497 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3498 last: 1,
3499 entries: vec![
3500 AuditConferenceEntry {
3501 conference_id: ConferenceId::new(1),
3502 resource_type: ConferenceResourceType::Conference,
3503 reserved_participants: 2,
3504 active_participants: 1,
3505 application_id: ApplicationId::new(1),
3506 application_conference_id: String::new(),
3507 application_data: String::new(),
3508 };
3509 33
3510 ],
3511 })
3512 .encode(ProtocolVersion::V22),
3513 Err(CodecError::CountTooLarge {
3514 field: "conference audit entries",
3515 count: 33,
3516 maximum: 32,
3517 ..
3518 })
3519 ));
3520
3521 let mut oversized_conference_data = vec![0; 12];
3522 oversized_conference_data[8..12].copy_from_slice(&2001_u32.to_le_bytes());
3523 assert!(matches!(
3524 ControlMessage::decode(
3525 Frame::new(
3526 22,
3527 wire_id::CREATE_CONFERENCE_RES,
3528 oversized_conference_data
3529 ),
3530 ProtocolVersion::V22,
3531 ),
3532 Err(CodecError::CountTooLarge {
3533 field: "conference passthrough data",
3534 count: 2001,
3535 maximum: 2000,
3536 ..
3537 })
3538 ));
3539
3540 let mut oversized_audit = vec![0; 8];
3541 oversized_audit[4..8].copy_from_slice(&33_u32.to_le_bytes());
3542 assert!(matches!(
3543 ControlMessage::decode(
3544 Frame::new(22, wire_id::AUDIT_CONFERENCE_RES, oversized_audit),
3545 ProtocolVersion::V22,
3546 ),
3547 Err(CodecError::CountTooLarge {
3548 field: "conference audit entries",
3549 count: 33,
3550 maximum: 32,
3551 ..
3552 })
3553 ));
3554 }
3555
3556 #[test]
3557 fn server_response_uses_the_negotiated_address_layout() {
3558 let message = ServerMessage::ServerResponse {
3559 servers: vec![
3560 SignalingServerEndpoint {
3561 name: "primary".into(),
3562 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
3563 port: NonZeroU16::new(2000).unwrap(),
3564 },
3565 SignalingServerEndpoint {
3566 name: "secondary".into(),
3567 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 20)),
3568 port: NonZeroU16::new(2001).unwrap(),
3569 },
3570 ],
3571 };
3572 let v3 = message.encode(ProtocolVersion::V3).unwrap();
3573 let v17 = message.encode(ProtocolVersion::V17).unwrap();
3574 assert_eq!(v3.len(), 292);
3575 assert_eq!(v17.len(), 372);
3576 assert_server_round_trip(message.clone(), ProtocolVersion::V3);
3577 assert_server_round_trip(message, ProtocolVersion::V17);
3578
3579 let mut zero_port = v3;
3580 zero_port[12 + 5 * 48..12 + 5 * 48 + 4].fill(0);
3581 assert!(matches!(
3582 ServerMessage::decode(decode_frame(&zero_port), ProtocolVersion::V3),
3583 Err(CodecError::InvalidValue {
3584 field: "server endpoint",
3585 value: 0,
3586 ..
3587 })
3588 ));
3589 assert_server_round_trip(
3590 ServerMessage::ServerResponse {
3591 servers: vec![SignalingServerEndpoint {
3592 name: "sccp-v6".into(),
3593 address: "2001:db8::20".parse().unwrap(),
3594 port: NonZeroU16::new(2000).unwrap(),
3595 }],
3596 },
3597 ProtocolVersion::V17,
3598 );
3599
3600 let unspecified = ServerMessage::ServerResponse {
3601 servers: vec![SignalingServerEndpoint {
3602 name: "unroutable".into(),
3603 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
3604 port: NonZeroU16::new(2000).unwrap(),
3605 }],
3606 };
3607 assert!(matches!(
3608 unspecified.encode(ProtocolVersion::V17),
3609 Err(CodecError::InvalidValue {
3610 field: "server address",
3611 value: 0,
3612 ..
3613 })
3614 ));
3615
3616 let endpoints = |count: u8| {
3617 (0..count)
3618 .map(|index| SignalingServerEndpoint {
3619 name: format!("node-{index}"),
3620 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, index + 1)),
3621 port: NonZeroU16::new(2000).unwrap(),
3622 })
3623 .collect()
3624 };
3625 let empty = ServerMessage::ServerResponse {
3626 servers: Vec::new(),
3627 };
3628 assert!(matches!(
3629 empty.encode(ProtocolVersion::V17),
3630 Err(CodecError::InvalidValue {
3631 field: "server endpoints",
3632 value: 0,
3633 ..
3634 })
3635 ));
3636 assert_server_round_trip(
3637 ServerMessage::ServerResponse {
3638 servers: endpoints(5),
3639 },
3640 ProtocolVersion::V17,
3641 );
3642 let too_many = ServerMessage::ServerResponse {
3643 servers: endpoints(6),
3644 };
3645 assert!(matches!(
3646 too_many.encode(ProtocolVersion::V17),
3647 Err(CodecError::CountTooLarge {
3648 field: "server endpoints",
3649 count: 6,
3650 maximum: 5,
3651 ..
3652 })
3653 ));
3654 }
3655}