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