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 directory_number: String,
1935 fully_qualified_display_name: String,
1936 display_label: String,
1937 },
1938 ButtonTemplate {
1941 offset: u32,
1942 total: u32,
1943 buttons: Vec<ButtonTemplateEntry>,
1944 },
1945 Version { firmware: String },
1948 ServerResponse {
1951 servers: Vec<SignalingServerEndpoint>,
1952 },
1953 TimeDate {
1956 year: u32,
1957 month: u32,
1958 weekday: u32,
1959 day: u32,
1960 hour: u32,
1961 minute: u32,
1962 second: u32,
1963 milliseconds: u32,
1964 unix_seconds: u32,
1965 },
1966 SoftKeyTemplate { actions: Vec<values::SoftKey> },
1969 SoftKeySet { profile: SoftKeyProfile },
1972 SelectSoftKeys {
1975 line_instance: u32,
1976 call_reference: u32,
1977 set: KeyMode,
1978 valid_mask: u32,
1980 },
1981 CallState {
1984 state: CallState,
1985 line_instance: u32,
1986 call_reference: u32,
1987 },
1988 CallInfo {
1991 info: CallInfo,
1992 line_instance: u32,
1993 call_reference: u32,
1994 },
1995 DisplayPrompt {
1998 timeout_seconds: u32,
1999 text: String,
2000 line_instance: u32,
2001 call_reference: u32,
2002 },
2003 ClearPrompt {
2006 line_instance: u32,
2007 call_reference: u32,
2008 },
2009 DisplayNotify { timeout_seconds: u32, text: String },
2012 ClearNotify,
2015 DisplayPriorityNotify {
2018 timeout_seconds: u32,
2019 priority: NotificationPriority,
2020 text: String,
2021 },
2022 ClearPriorityNotify { priority: NotificationPriority },
2025 NotifyDtmfTone(DtmfToneControl),
2028 SendDtmfTone(DtmfToneControl),
2031 StartAnnouncement {
2034 announcements: Vec<AnnouncementEntry>,
2035 end_of_ack: u32,
2036 conference_id: u32,
2037 matrix_conference_party_ids: Vec<u32>,
2038 hearing_conference_party_mask: u32,
2039 play_mode: u32,
2040 },
2041 StopAnnouncement { conference_id: u32 },
2044 AnnouncementFinish {
2047 conference_id: u32,
2048 play_status: u32,
2049 },
2050 ClearConference {
2053 conference_id: ConferenceId,
2054 service_number: u32,
2055 },
2056 CreateConferenceRequest(CreateConferenceRequest),
2059 DeleteConferenceRequest { conference_id: ConferenceId },
2062 ModifyConferenceRequest(ModifyConferenceRequest),
2065 AuditConferenceRequest,
2068 AddParticipantRequest(AddParticipantRequest),
2071 DropParticipantRequest {
2074 conference_id: ConferenceId,
2075 call_reference: CallReference,
2076 },
2077 AuditParticipantRequest { conference_id: ConferenceId },
2080 ChangeParticipantRequest(ChangeParticipantRequest),
2083 StopMultimediaTransmission(MultimediaStreamControl),
2086 FlowControlCommand(VideoFlowControl),
2089 CloseMultimediaReceiveChannel(MultimediaStreamControl),
2092 VideoDisplayCommand {
2095 conference_id: ConferenceId,
2096 call_reference: CallReference,
2097 layout_id: u32,
2098 },
2099 FlowControlNotify(VideoFlowControl),
2102 ActivateCallPlane { line_instance: u32 },
2105 DeactivateCallPlane,
2108 BackspaceResponse {
2111 line_instance: u32,
2112 call_reference: u32,
2113 },
2114 RegisterTokenAck,
2117 RegisterTokenReject { backoff_seconds: u32 },
2120 SpcpRegisterTokenAck { features: u32 },
2123 SpcpRegisterTokenReject { backoff_seconds: u32 },
2126 SetRinger {
2129 mode: RingerMode,
2130 duration: RingDuration,
2131 line_instance: u32,
2132 call_reference: u32,
2133 },
2134 SetLamp {
2137 stimulus: ButtonType,
2138 instance: u32,
2139 mode: LampMode,
2140 },
2141 SetHookFlashDetect,
2144 StartTone {
2147 tone: Tone,
2148 direction: ToneDirection,
2149 line_instance: u32,
2150 call_reference: u32,
2151 },
2152 StopTone {
2155 line_instance: u32,
2156 call_reference: u32,
2157 },
2158 StartMulticastMediaReception(MulticastMediaReception),
2161 StartMulticastMediaTransmission(MulticastMediaTransmission),
2164 StopMulticastMediaReception {
2167 conference_id: ConferenceId,
2168 passthrough_party_id: crate::types::PassthroughPartyId,
2169 call_reference: CallReference,
2170 },
2171 StopMulticastMediaTransmission {
2174 conference_id: ConferenceId,
2175 passthrough_party_id: crate::types::PassthroughPartyId,
2176 call_reference: CallReference,
2177 },
2178 OpenReceiveChannel {
2181 call_reference: u32,
2182 passthrough_party_id: u32,
2183 packet_ms: u32,
2184 codec: Codec,
2185 echo_cancellation: EchoCancellation,
2186 telephone_event_payload: u8,
2188 source_address: IpAddr,
2189 source_port: u16,
2190 encryption: Option<MediaEncryption>,
2191 wire: Option<OpenReceiveChannelWire>,
2194 },
2195 CloseReceiveChannel(AudioStreamControl),
2198 ConnectionStatisticsRequest {
2201 directory_number: String,
2202 call_reference: u32,
2203 processing: StatisticsProcessing,
2204 },
2205 StartMediaTransmission {
2208 call_reference: u32,
2209 passthrough_party_id: u32,
2210 endpoint: MediaEndpoint,
2211 silence_suppression: SilenceSuppression,
2212 traffic_class: crate::types::MediaTrafficClass,
2214 encryption: Option<MediaEncryption>,
2215 wire: Option<StartMediaTransmissionWire>,
2218 },
2219 StopMediaTransmission(AudioStreamControl),
2222 StartMediaReception,
2225 StopMediaReception {
2228 conference_id: ConferenceId,
2229 passthrough_party_id: crate::types::PassthroughPartyId,
2230 },
2231 SubscribeDtmfPayloadRequest(DtmfPayloadRequest),
2234 SubscribeDtmfPayloadError(DtmfPayloadIdentity),
2237 UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest),
2240 UnsubscribeDtmfPayloadError(DtmfPayloadIdentity),
2243 SetSpeakerMode(SpeakerMode),
2246 SetMicrophoneMode(MicrophoneMode),
2249 Reset(ResetType),
2252 DisplayText { text: String },
2255 ClearDisplay,
2258 ForwardStatus {
2261 line_instance: u32,
2262 forward_all: Option<String>,
2263 forward_busy: Option<String>,
2264 forward_no_answer: Option<String>,
2265 },
2266 SpeedDialStatus {
2269 instance: u32,
2270 number: String,
2271 display_name: String,
2272 },
2273 DialedNumber {
2276 number: String,
2277 line_instance: u32,
2278 call_reference: u32,
2279 },
2280 StartMediaFailureDetection(MediaFailureDetection),
2283 UserToDeviceData(UserDataMessage),
2286 UserToDeviceDataV1(UserDataV1Message),
2289 FeatureStatus {
2292 instance: u32,
2293 button_type: ButtonType,
2294 label: String,
2295 state: u32,
2297 },
2298 ServiceUrlStatus {
2301 index: u32,
2302 url: String,
2303 label: String,
2304 extension_text: String,
2306 },
2307 CallSelectStatus {
2310 status: u32,
2312 call_reference: u32,
2313 line_instance: u32,
2314 },
2315 PortRequest(PortRequest),
2318 PortClose(PortClose),
2321 OpenMultimediaChannel(OpenMultimediaChannel),
2324 StartMultimediaTransmission(StartMultimediaTransmission),
2327 MiscellaneousCommand(MiscellaneousCommand),
2330 SubscriptionStatus {
2333 transaction_id: u32,
2334 feature_id: u32,
2335 timer_seconds: u32,
2336 cause: SubscriptionCause,
2337 },
2338 Notification {
2341 transaction_id: u32,
2342 feature_id: u32,
2343 status: BusyLampFieldState,
2344 text: String,
2345 },
2346 CallHistoryDisposition {
2349 disposition: CallHistoryDisposition,
2350 line_instance: u32,
2351 call_reference: u32,
2352 },
2353 CallCountResponse(CallCountResponse),
2355 RecordingStatus { call_reference: u32, active: bool },
2358 KnownOpaque(KnownOpaqueMessage),
2361 Unknown(RawMessage),
2364}
2365
2366#[cfg(test)]
2367mod tests {
2368 use super::wire::{CodecError, Frame, FrameDecoder};
2369 use super::*;
2370
2371 #[test]
2372 fn protocol_fillers_have_semantic_defaults() {
2373 assert_eq!(
2374 ButtonTemplateEntry::default(),
2375 ButtonTemplateEntry {
2376 instance: 0,
2377 button_type: ButtonType::Unused,
2378 }
2379 );
2380 assert_eq!(
2381 MessageWaitingCounts::default(),
2382 MessageWaitingCounts { new: 0, old: 0 }
2383 );
2384 }
2385
2386 const fn test_rtp_payload_number(value: u32) -> RtpPayloadNumber {
2387 match RtpPayloadNumber::new(value) {
2388 Ok(value) => value,
2389 Err(_) => panic!("test RTP payload number is out of range"),
2390 }
2391 }
2392
2393 fn decode_frame(bytes: &[u8]) -> Frame {
2394 FrameDecoder::new().push(bytes).unwrap().remove(0)
2395 }
2396
2397 fn assert_contract_alignment(frame: &Frame) {
2398 use super::catalog::PayloadLayout;
2399
2400 let contract = frame.message_type().contract().unwrap();
2401 if !matches!(
2402 contract.payload_layout,
2403 PayloadLayout::Opaque
2404 | PayloadLayout::BoundedOpaque
2405 | PayloadLayout::BoundedPreserved
2406 | PayloadLayout::VersionAndLengthSelected
2407 | PayloadLayout::MinimumLengthPreserved
2408 ) {
2409 assert_eq!(frame.payload.len() % 4, 0, "{}", contract.id);
2410 }
2411 }
2412
2413 fn assert_client_round_trip(message: ClientMessage, protocol: ProtocolVersion) {
2414 let frame = decode_frame(&message.encode(protocol).unwrap());
2415 assert_contract_alignment(&frame);
2416 assert_eq!(
2417 ClientMessage::decode_with_version(frame, protocol).unwrap(),
2418 message
2419 );
2420 }
2421
2422 fn assert_server_round_trip(message: ServerMessage, protocol: ProtocolVersion) {
2423 let frame = decode_frame(&message.encode(protocol).unwrap());
2424 assert_contract_alignment(&frame);
2425 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
2426 }
2427
2428 fn assert_control_round_trip(message: ControlMessage, protocol: ProtocolVersion) {
2429 let frame = decode_frame(&message.encode(protocol).unwrap());
2430 assert_contract_alignment(&frame);
2431 assert_eq!(ControlMessage::decode(frame, protocol).unwrap(), message);
2432 }
2433
2434 #[test]
2435 fn multimedia_payload_exposes_only_typed_construction() {
2436 let capability = MultimediaVideoCapability::new(
2437 1_024,
2438 [MultimediaPictureFormat {
2439 format: VideoFormat::Cif4,
2440 minimum_picture_interval: 2,
2441 }],
2442 7,
2443 MultimediaVideoCapabilityArm::H264 {
2444 profile: 100,
2445 level: 42,
2446 custom_max_mbps: 40_500,
2447 custom_max_fs: 1_620,
2448 custom_max_dpb: 8_100,
2449 custom_max_br_and_cpb: 10_000,
2450 },
2451 )
2452 .unwrap();
2453 let payload = MultimediaPayload::new(test_rtp_payload_number(97), capability.clone());
2454 assert_eq!(payload.payload_number().get(), 97);
2455 assert_eq!(payload.descriptor().rfc_number(), 0);
2456 assert_eq!(payload.codec(), Codec::H264);
2457 assert_eq!(payload.video_capability(), Some(&capability));
2458
2459 let packetized = MultimediaPayload::with_descriptor(
2460 MultimediaPayloadDescriptor::new(4, payload.payload_number()),
2461 capability.clone(),
2462 );
2463 assert_eq!(packetized.descriptor().rfc_number(), 4);
2464 assert_eq!(packetized.payload_number(), payload.payload_number());
2465
2466 let debug = format!("{capability:?}");
2467 assert!(debug.contains("bit_rate: 1024"));
2468 assert!(!debug.contains("preserved_wire"));
2469 assert_eq!(
2470 RtpPayloadNumber::new(128),
2471 Err(RtpPayloadNumberError { actual: 128 })
2472 );
2473 }
2474
2475 #[test]
2476 fn multimedia_picture_formats_are_bounded_before_payload_construction() {
2477 let formats = [MultimediaPictureFormat {
2478 format: VideoFormat::Cif,
2479 minimum_picture_interval: 1,
2480 }; MAX_MULTIMEDIA_PICTURE_FORMATS + 1];
2481 assert_eq!(
2482 MultimediaVideoCapability::new(
2483 1_024,
2484 formats,
2485 0,
2486 MultimediaVideoCapabilityArm::H261 {
2487 temporal_spatial_trade_off_capability: 0,
2488 still_image_transmission: 0,
2489 },
2490 )
2491 .unwrap_err(),
2492 MultimediaCapabilityError {
2493 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
2494 actual: MAX_MULTIMEDIA_PICTURE_FORMATS + 1,
2495 }
2496 );
2497 }
2498
2499 #[test]
2500 fn media_request_identity_is_nonzero_and_exhaustion_never_wraps() {
2501 assert_eq!(MediaRequestToken::new(0), None);
2502 let token = MediaRequestToken::new(7).unwrap();
2503 assert_eq!(MediaRequestIdentity::new(0, token), None);
2504
2505 let first = MediaRequestIdentity::new(1, token).unwrap();
2506 let second = first.checked_next().unwrap();
2507 assert_eq!(second.generation(), 2);
2508 assert_eq!(second.token().get(), 8);
2509
2510 assert_eq!(
2511 MediaRequestToken::new(u32::MAX).unwrap().checked_next(),
2512 None
2513 );
2514 let exhausted_generation =
2515 MediaRequestIdentity::new(u64::MAX, MediaRequestToken::new(1).unwrap()).unwrap();
2516 assert_eq!(exhausted_generation.checked_next(), None);
2517 }
2518
2519 #[test]
2520 fn media_request_identity_matches_only_the_current_wire_token() {
2521 let identity =
2522 MediaRequestIdentity::new(2, MediaRequestToken::new(0x1020_3040).unwrap()).unwrap();
2523
2524 assert!(identity.accepts_ack(0x1020_3040, 0, 77));
2525 assert!(identity.accepts_ack(0x1020_3040, 77, 77));
2526 assert!(!identity.accepts_ack(0x1020_3040, 78, 77));
2527 assert!(!identity.accepts_ack(0x1020_303f, 77, 77));
2528 }
2529
2530 #[test]
2531 fn zero_party_fallback_cannot_settle_a_reopened_media_generation() {
2532 let first = MediaRequestIdentity::new(1, MediaRequestToken::new(700).unwrap()).unwrap();
2533 let reopened = first.checked_next().unwrap();
2534
2535 assert!(first.accepts_ack(0, 42, 42));
2537 assert!(!first.accepts_ack(0, 0, 42));
2538
2539 assert!(!reopened.accepts_ack(0, 42, 42));
2541 assert!(!reopened.accepts_ack(first.token().get(), 42, 42));
2542 assert!(reopened.accepts_ack(reopened.token().get(), 42, 42));
2543 }
2544
2545 #[test]
2546 fn decodes_7962_off_hook_capture_shape() {
2547 let frame = Frame::new(22, wire_id::OFF_HOOK, vec![1, 0, 0, 0, 42, 0, 0, 0]);
2548 assert_eq!(
2549 ClientMessage::decode(frame).unwrap(),
2550 ClientMessage::OffHook {
2551 line_instance: 1,
2552 call_reference: 42
2553 }
2554 );
2555 }
2556
2557 #[test]
2558 fn decodes_7961_v22_three_word_keypad_capture_shape() {
2559 let payload: Vec<_> = [8_u32, 1, 1]
2560 .into_iter()
2561 .flat_map(u32::to_le_bytes)
2562 .collect();
2563 let frame = Frame::new(22, wire_id::KEYPAD_BUTTON, payload.clone());
2564 let decoded = ClientMessage::decode(frame).unwrap();
2565 assert_eq!(
2566 decoded,
2567 ClientMessage::KeypadButton {
2568 button: Digit::Number(8),
2569 line_instance: 1,
2570 call_reference: 1,
2571 wire_layout: Some(KeypadButtonWireLayout::WithCallIdentity),
2572 }
2573 );
2574 let encoded = FrameDecoder::new()
2575 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
2576 .unwrap()
2577 .remove(0);
2578 assert_eq!(encoded.payload, payload);
2579 }
2580
2581 #[test]
2582 fn register_ack_is_protocol_zero_and_has_expected_fields() {
2583 let bytes = ServerMessage::RegisterAck {
2584 keepalive_seconds: 30,
2585 secondary_keepalive_seconds: 45,
2586 protocol: ProtocolVersion::V22,
2587 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2588 date_template: DateTemplate::default(),
2589 }
2590 .encode(ProtocolVersion::V22)
2591 .unwrap();
2592 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
2593 assert_eq!(frame.protocol_version, 0);
2594 assert_eq!(frame.message_id, wire_id::REGISTER_ACK);
2595 assert_eq!(
2596 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
2597 ServerMessage::RegisterAck {
2598 keepalive_seconds: 30,
2599 secondary_keepalive_seconds: 45,
2600 protocol: ProtocolVersion::V22,
2601 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2602 date_template: DateTemplate::default(),
2603 }
2604 );
2605 }
2606
2607 #[test]
2608 fn media_layout_sizes_match_supported_wire_specs() {
2609 let endpoint = MediaEndpoint {
2610 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2611 rtp_port: 4000,
2612 rtcp_port: 4001,
2613 codec: Codec::Pcmu,
2614 packet_ms: 20,
2615 max_frames_per_packet: 1,
2616 telephone_event_payload: 101,
2617 };
2618 let start = ServerMessage::StartMediaTransmission {
2619 call_reference: 7,
2620 passthrough_party_id: 9,
2621 endpoint,
2622 silence_suppression: SilenceSuppression::Off,
2623 traffic_class: crate::types::MediaTrafficClass::from_wire(184),
2624 encryption: None,
2625 wire: None,
2626 }
2627 .encode(ProtocolVersion::V17)
2628 .unwrap();
2629 assert_eq!(start.len(), 144); assert_eq!(&start[52..56], &184_u32.to_le_bytes());
2631 assert_eq!(&start[140..144], &1_u32.to_le_bytes());
2632 let open = ServerMessage::OpenReceiveChannel {
2633 call_reference: 7,
2634 passthrough_party_id: 9,
2635 packet_ms: 20,
2636 codec: Codec::Pcmu,
2637 echo_cancellation: EchoCancellation::On,
2638 telephone_event_payload: 101,
2639 source_address: endpoint.address,
2640 source_port: endpoint.rtp_port,
2641 encryption: None,
2642 wire: None,
2643 }
2644 .encode(ProtocolVersion::V17)
2645 .unwrap();
2646 assert_eq!(open.len(), 140); assert_eq!(&open[108..112], &1_u32.to_le_bytes());
2648
2649 let start_v3 = ServerMessage::StartMediaTransmission {
2650 call_reference: 7,
2651 passthrough_party_id: 9,
2652 endpoint,
2653 silence_suppression: SilenceSuppression::Off,
2654 traffic_class: crate::types::MediaTrafficClass::default(),
2655 encryption: None,
2656 wire: None,
2657 }
2658 .encode(ProtocolVersion::V3)
2659 .unwrap();
2660 assert_eq!(start_v3.len(), 120); let open_v3 = ServerMessage::OpenReceiveChannel {
2662 call_reference: 7,
2663 passthrough_party_id: 9,
2664 packet_ms: 20,
2665 codec: Codec::Pcmu,
2666 echo_cancellation: EchoCancellation::On,
2667 telephone_event_payload: 101,
2668 source_address: endpoint.address,
2669 source_port: endpoint.rtp_port,
2670 encryption: None,
2671 wire: None,
2672 }
2673 .encode(ProtocolVersion::V3)
2674 .unwrap();
2675 assert_eq!(open_v3.len(), 104); let start_v22 = ServerMessage::StartMediaTransmission {
2678 call_reference: 7,
2679 passthrough_party_id: 9,
2680 endpoint,
2681 silence_suppression: SilenceSuppression::Off,
2682 traffic_class: crate::types::MediaTrafficClass::default(),
2683 encryption: None,
2684 wire: None,
2685 }
2686 .encode(ProtocolVersion::V22)
2687 .unwrap();
2688 assert_eq!(start_v22.len(), 180); let open_v22 = ServerMessage::OpenReceiveChannel {
2690 call_reference: 7,
2691 passthrough_party_id: 9,
2692 packet_ms: 20,
2693 codec: Codec::Pcmu,
2694 echo_cancellation: EchoCancellation::On,
2695 telephone_event_payload: 101,
2696 source_address: endpoint.address,
2697 source_port: endpoint.rtp_port,
2698 encryption: None,
2699 wire: None,
2700 }
2701 .encode(ProtocolVersion::V22)
2702 .unwrap();
2703 assert_eq!(open_v22.len(), 180); }
2705
2706 #[test]
2707 fn media_close_layouts_consume_the_reference_fields_exactly() {
2708 let close = ServerMessage::CloseReceiveChannel(AudioStreamControl {
2709 conference_id: 6.into(),
2710 passthrough_party_id: 9.into(),
2711 call_reference: 7.into(),
2712 port_handling_flag: 11,
2713 });
2714 let close_v3 = close.encode(ProtocolVersion::V3).unwrap();
2715 assert_eq!(close_v3.len(), 28);
2716 assert_eq!(
2717 ServerMessage::decode(decode_frame(&close_v3), ProtocolVersion::V3).unwrap(),
2718 close
2719 );
2720 let close_v5 = close.encode(ProtocolVersion::V5).unwrap();
2721 assert_eq!(close_v5.len(), 28);
2722 assert_eq!(
2723 ServerMessage::decode(decode_frame(&close_v5), ProtocolVersion::V5).unwrap(),
2724 close
2725 );
2726
2727 let stop = ServerMessage::StopMediaTransmission(AudioStreamControl {
2728 conference_id: 6.into(),
2729 passthrough_party_id: 9.into(),
2730 call_reference: 7.into(),
2731 port_handling_flag: 11,
2732 });
2733 let bytes = stop.encode(ProtocolVersion::V22).unwrap();
2734 assert_eq!(bytes.len(), 28);
2735 assert_eq!(
2736 ServerMessage::decode(decode_frame(&bytes), ProtocolVersion::V22).unwrap(),
2737 stop
2738 );
2739
2740 let mut trailing = decode_frame(&bytes);
2741 trailing.payload.extend_from_slice(&[0; 4]);
2742 assert!(matches!(
2743 ServerMessage::decode(trailing, ProtocolVersion::V22),
2744 Err(CodecError::TrailingBytes { count: 4, .. })
2745 ));
2746 }
2747
2748 #[test]
2749 fn audio_packetization_round_trips_without_default_substitution() {
2750 let endpoint = MediaEndpoint {
2751 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2752 rtp_port: 4000,
2753 rtcp_port: 4001,
2754 codec: Codec::G72264k,
2755 packet_ms: 30,
2756 max_frames_per_packet: 2,
2757 telephone_event_payload: 101,
2758 };
2759 for protocol in [
2760 ProtocolVersion::V3,
2761 ProtocolVersion::V17,
2762 ProtocolVersion::V22,
2763 ] {
2764 let (source_address, source_port) = if protocol.wire() < 12 {
2765 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2766 } else {
2767 (endpoint.address, endpoint.rtp_port)
2768 };
2769 assert_server_round_trip(
2770 ServerMessage::OpenReceiveChannel {
2771 call_reference: 7,
2772 passthrough_party_id: 9,
2773 packet_ms: 30,
2774 codec: Codec::G72264k,
2775 echo_cancellation: EchoCancellation::On,
2776 telephone_event_payload: 101,
2777 source_address,
2778 source_port,
2779 encryption: None,
2780 wire: None,
2781 },
2782 protocol,
2783 );
2784 assert_server_round_trip(
2785 ServerMessage::StartMediaTransmission {
2786 call_reference: 7,
2787 passthrough_party_id: 9,
2788 endpoint,
2789 silence_suppression: SilenceSuppression::On,
2790 traffic_class: crate::types::MediaTrafficClass::default(),
2791 encryption: None,
2792 wire: None,
2793 },
2794 protocol,
2795 );
2796 assert_client_round_trip(
2797 ClientMessage::MediaTransmissionFailure {
2798 conference_id: 7,
2799 passthrough_party_id: 9,
2800 address: endpoint.address,
2801 port: endpoint.rtp_port,
2802 call_reference: 7,
2803 status: MediaStatus::UnspecifiedError,
2804 },
2805 protocol,
2806 );
2807 }
2808 }
2809
2810 #[test]
2811 fn ipv6_audio_endpoints_require_and_round_trip_extended_layouts() {
2812 let address: IpAddr = "2001:db8::42".parse().unwrap();
2813 let endpoint = MediaEndpoint {
2814 address,
2815 rtp_port: 40_000,
2816 rtcp_port: 40_001,
2817 codec: Codec::G72264k,
2818 packet_ms: 20,
2819 max_frames_per_packet: 1,
2820 telephone_event_payload: 101,
2821 };
2822 let start = ServerMessage::StartMediaTransmission {
2823 call_reference: 7,
2824 passthrough_party_id: 9,
2825 endpoint,
2826 silence_suppression: SilenceSuppression::Off,
2827 traffic_class: crate::types::MediaTrafficClass::default(),
2828 encryption: None,
2829 wire: None,
2830 };
2831 let receive_ack = ClientMessage::OpenReceiveChannelAck {
2832 status: MediaStatus::Ok,
2833 address,
2834 port: endpoint.rtp_port,
2835 passthrough_party_id: 9,
2836 call_reference: 7,
2837 };
2838 let transmit_ack = ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
2839 conference_id: 6,
2840 passthrough_party_id: 9,
2841 call_reference: 7,
2842 status: MediaStatus::Ok,
2843 address,
2844 port: endpoint.rtp_port,
2845 wire: None,
2846 });
2847 let failure = ClientMessage::MediaTransmissionFailure {
2848 conference_id: 7,
2849 passthrough_party_id: 9,
2850 address,
2851 port: endpoint.rtp_port,
2852 call_reference: 7,
2853 status: MediaStatus::UnspecifiedError,
2854 };
2855
2856 for protocol in [ProtocolVersion::V17, ProtocolVersion::V22] {
2857 assert_server_round_trip(start.clone(), protocol);
2858 assert_client_round_trip(receive_ack.clone(), protocol);
2859 assert_client_round_trip(transmit_ack.clone(), protocol);
2860 assert_client_round_trip(failure.clone(), protocol);
2861 }
2862 for result in [
2863 start.encode(ProtocolVersion::V16),
2864 receive_ack.encode(ProtocolVersion::V16),
2865 transmit_ack.encode(ProtocolVersion::V16),
2866 failure.encode(ProtocolVersion::V16),
2867 failure.encode(ProtocolVersion::V3),
2868 ] {
2869 assert!(matches!(
2870 result,
2871 Err(CodecError::InvalidValue {
2872 field: "IP address family for pre-v17 protocol"
2873 | "IP address family for this protocol version",
2874 ..
2875 })
2876 ));
2877 }
2878 }
2879
2880 #[test]
2881 fn skinny_dtmf_disables_the_telephone_event_payload_in_both_directions() {
2882 let endpoint = MediaEndpoint {
2883 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2884 rtp_port: 4000,
2885 rtcp_port: 4001,
2886 codec: Codec::Pcmu,
2887 packet_ms: 20,
2888 max_frames_per_packet: 1,
2889 telephone_event_payload: 0,
2890 };
2891 for protocol in [
2892 ProtocolVersion::V3,
2893 ProtocolVersion::V17,
2894 ProtocolVersion::V22,
2895 ] {
2896 let (source_address, source_port) = if protocol.wire() < 12 {
2897 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2898 } else {
2899 (endpoint.address, endpoint.rtp_port)
2900 };
2901 assert_server_round_trip(
2902 ServerMessage::OpenReceiveChannel {
2903 call_reference: 7,
2904 passthrough_party_id: 9,
2905 packet_ms: 20,
2906 codec: Codec::Pcmu,
2907 echo_cancellation: EchoCancellation::On,
2908 telephone_event_payload: 0,
2909 source_address,
2910 source_port,
2911 encryption: None,
2912 wire: None,
2913 },
2914 protocol,
2915 );
2916 assert_server_round_trip(
2917 ServerMessage::StartMediaTransmission {
2918 call_reference: 7,
2919 passthrough_party_id: 9,
2920 endpoint,
2921 silence_suppression: SilenceSuppression::Off,
2922 traffic_class: crate::types::MediaTrafficClass::default(),
2923 encryption: None,
2924 wire: None,
2925 },
2926 protocol,
2927 );
2928 }
2929 }
2930
2931 #[test]
2932 fn open_receive_wildcard_source_round_trips_for_all_supported_layouts() {
2933 for protocol in [
2934 ProtocolVersion::V3,
2935 ProtocolVersion::V17,
2936 ProtocolVersion::V22,
2937 ] {
2938 assert_server_round_trip(
2939 ServerMessage::OpenReceiveChannel {
2940 call_reference: 1,
2941 passthrough_party_id: 1,
2942 packet_ms: 20,
2943 codec: Codec::Pcma,
2944 echo_cancellation: EchoCancellation::Off,
2945 telephone_event_payload: 101,
2946 source_address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
2947 source_port: 0,
2948 encryption: None,
2949 wire: None,
2950 },
2951 protocol,
2952 );
2953 }
2954 }
2955
2956 #[test]
2957 fn media_encryption_round_trips_without_exposing_key_material() {
2958 let key = b"private-key-1234";
2959 let salt = b"private-salt-123";
2960 let encryption =
2961 MediaEncryption::new(EncryptionMethod::Aes128HmacSha1_80, key, salt, 1, 64).unwrap();
2962 assert_eq!(encryption.key(), key);
2963 assert_eq!(encryption.salt(), salt);
2964
2965 let debug = format!("{encryption:?}");
2966 assert!(debug.contains("<redacted>"));
2967 assert!(!debug.contains("112, 114, 105, 118, 97, 116, 101"));
2968 assert!(!debug.contains("private-key"));
2969 let endpoint = MediaEndpoint {
2970 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
2971 rtp_port: 40_000,
2972 rtcp_port: 40_001,
2973 codec: Codec::Pcmu,
2974 packet_ms: 20,
2975 max_frames_per_packet: 1,
2976 telephone_event_payload: 101,
2977 };
2978
2979 for protocol in [
2980 ProtocolVersion::new(12).unwrap(),
2981 ProtocolVersion::V17,
2982 ProtocolVersion::V22,
2983 ] {
2984 let open = ServerMessage::OpenReceiveChannel {
2985 call_reference: 7,
2986 passthrough_party_id: 9,
2987 packet_ms: 20,
2988 codec: Codec::Pcmu,
2989 echo_cancellation: EchoCancellation::On,
2990 telephone_event_payload: 101,
2991 source_address: endpoint.address,
2992 source_port: endpoint.rtp_port,
2993 encryption: Some(encryption.clone()),
2994 wire: None,
2995 };
2996 let open_debug = format!("{open:?}");
2997 assert!(open_debug.contains("<redacted>"));
2998 assert!(!open_debug.contains("112, 114, 105, 118, 97, 116, 101"));
2999 assert_server_round_trip(open, protocol);
3000 assert_server_round_trip(
3001 ServerMessage::StartMediaTransmission {
3002 call_reference: 7,
3003 passthrough_party_id: 9,
3004 endpoint,
3005 silence_suppression: SilenceSuppression::Off,
3006 traffic_class: crate::types::MediaTrafficClass::default(),
3007 encryption: Some(encryption.clone()),
3008 wire: None,
3009 },
3010 protocol,
3011 );
3012 }
3013 }
3014
3015 #[test]
3016 fn media_encryption_rejects_oversized_secrets_with_metadata_only_errors() {
3017 let oversized_key = [0xa5; 17];
3018 let error = MediaEncryption::new(
3019 EncryptionMethod::Aes128HmacSha1_32,
3020 &oversized_key,
3021 &[],
3022 0,
3023 0,
3024 )
3025 .unwrap_err();
3026 assert!(matches!(
3027 error,
3028 CodecError::SecretTooLong {
3029 field: "media encryption key",
3030 actual: 17,
3031 maximum: 16,
3032 }
3033 ));
3034 assert!(!error.to_string().contains("165"));
3035
3036 let oversized_salt = [0x5a; 17];
3037 let error = MediaEncryption::new(
3038 EncryptionMethod::Aes128HmacSha1_32,
3039 &[],
3040 &oversized_salt,
3041 0,
3042 0,
3043 )
3044 .unwrap_err();
3045 assert!(matches!(
3046 error,
3047 CodecError::SecretTooLong {
3048 field: "media encryption salt",
3049 actual: 17,
3050 maximum: 16,
3051 }
3052 ));
3053 assert!(!error.to_string().contains("90"));
3054 }
3055
3056 #[test]
3057 fn common_client_messages_round_trip_semantically() {
3058 assert_client_round_trip(
3059 ClientMessage::FeatureStatusRequest {
3060 index: 7,
3061 capabilities: 1,
3062 },
3063 ProtocolVersion::V22,
3064 );
3065 assert_client_round_trip(
3066 ClientMessage::OffHookWithCallingParty {
3067 calling_party_number: "1001".into(),
3068 voice_mailbox: "5001".into(),
3069 line_instance: 1,
3070 },
3071 ProtocolVersion::V3,
3072 );
3073 assert_client_round_trip(
3074 ClientMessage::RegisterToken(RegisterTokenMessage {
3075 device_id: DeviceId::new("SEP001122334455").unwrap(),
3076 device_instance: 2,
3077 address: "2001:db8::42".parse().unwrap(),
3078 device_type: DeviceType::Cisco7962,
3079 flags: 6,
3080 }),
3081 ProtocolVersion::V22,
3082 );
3083 assert_control_round_trip(
3084 ControlMessage::MediaResourceNotification(MediaResourceNotification {
3085 device_type: DeviceType::Unknown(0xfeed),
3086 in_service_streams: 2,
3087 max_streams_per_conference: 4,
3088 out_of_service_streams: 1,
3089 }),
3090 ProtocolVersion::V17,
3091 );
3092 assert_client_round_trip(
3093 ClientMessage::SubscriptionStatusRequest(SubscriptionRequest {
3094 transaction_id: 0x4b,
3095 feature_id: 1,
3096 timer_seconds: 30,
3097 subscription_id: "4000".into(),
3098 }),
3099 ProtocolVersion::V22,
3100 );
3101 for message in [
3102 ClientMessage::SubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
3103 payload_type: 101,
3104 conference_id: 42,
3105 passthrough_party_id: 7,
3106 }),
3107 ClientMessage::UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
3108 payload_type: 102,
3109 conference_id: 43,
3110 passthrough_party_id: 8,
3111 }),
3112 ] {
3113 let encoded = message.encode(ProtocolVersion::V22).unwrap();
3114 let frame = decode_frame(&encoded);
3115 assert_eq!(frame.payload.len(), 12);
3116 assert_eq!(
3117 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
3118 message
3119 );
3120 }
3121 assert_client_round_trip(
3122 ClientMessage::DeviceToUserDataV1(UserDataV1Message {
3123 application_id: 7,
3124 line_instance: 1,
3125 call_reference: 42,
3126 transaction_id: 9,
3127 sequence_flag: 1,
3128 display_priority: 2,
3129 conference_id: 42,
3130 application_instance_id: 3,
3131 routing: 4,
3132 data: b"<CiscoIPPhoneText/>".to_vec(),
3133 }),
3134 ProtocolVersion::V17,
3135 );
3136 assert_client_round_trip(
3137 ClientMessage::DeviceToUserDataResponse(UserDataMessage {
3138 application_id: 8,
3139 line_instance: 2,
3140 call_reference: 43,
3141 transaction_id: 10,
3142 data: b"<CiscoIPPhoneResponse/>".to_vec(),
3143 }),
3144 ProtocolVersion::V17,
3145 );
3146 assert_client_round_trip(
3147 ClientMessage::DeviceToUserData(UserDataMessage {
3148 application_id: 9,
3149 line_instance: 2,
3150 call_reference: 44,
3151 transaction_id: 11,
3152 data: b"<CiscoIPPhoneInput/>".to_vec(),
3153 }),
3154 ProtocolVersion::V17,
3155 );
3156 assert_client_round_trip(
3157 ClientMessage::DeviceToUserDataResponseV1(UserDataV1Message {
3158 application_id: 9,
3159 line_instance: 2,
3160 call_reference: 44,
3161 transaction_id: 11,
3162 sequence_flag: 2,
3163 display_priority: 1,
3164 conference_id: 44,
3165 application_instance_id: 9,
3166 routing: 1,
3167 data: b"<CiscoIPPhoneResponse/>".to_vec(),
3168 }),
3169 ProtocolVersion::V17,
3170 );
3171 assert_client_round_trip(
3172 ClientMessage::LocationInfo {
3173 xml: "<location><building>west</building></location>".into(),
3174 },
3175 ProtocolVersion::V22,
3176 );
3177 assert_client_round_trip(
3178 ClientMessage::XmlAlarm(
3179 XmlAlarmMessage::from_xml(b"<alarm><severity>warning</severity></alarm>").unwrap(),
3180 ),
3181 ProtocolVersion::V22,
3182 );
3183 assert_client_round_trip(
3184 ClientMessage::CallCountRequest(CallCountRequestPayload::LegacyWord(2)),
3185 ProtocolVersion::V22,
3186 );
3187 assert_control_round_trip(
3188 ControlMessage::PortResponse(PortEndpoint {
3189 conference_id: 42,
3190 call_reference: 42,
3191 passthrough_party_id: 8,
3192 address: "2001:db8::8".parse().unwrap(),
3193 rtp_port: 16_000,
3194 rtcp_port: 16_001,
3195 media_type: Some(MediaType::Audio),
3196 }),
3197 ProtocolVersion::V22,
3198 );
3199 assert_control_round_trip(
3200 ControlMessage::CreateConferenceResponse(CreateConferenceResponse {
3201 conference_id: ConferenceId::new(42),
3202 result: CreateConferenceResult::Ok,
3203 passthrough_data: vec![1, 2, 3],
3204 }),
3205 ProtocolVersion::V22,
3206 );
3207 assert_control_round_trip(
3208 ControlMessage::DeleteConferenceResponse {
3209 conference_id: ConferenceId::new(42),
3210 result: DeleteConferenceResult::ConferenceDoesNotExist,
3211 },
3212 ProtocolVersion::V22,
3213 );
3214 assert_control_round_trip(
3215 ControlMessage::ModifyConferenceResponse(ModifyConferenceResponse {
3216 conference_id: ConferenceId::new(42),
3217 result: ModifyConferenceResult::MoreActiveCallsThanReserved,
3218 passthrough_data: vec![4, 5],
3219 }),
3220 ProtocolVersion::V22,
3221 );
3222 assert_control_round_trip(
3223 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3224 last: 1,
3225 entries: vec![AuditConferenceEntry {
3226 conference_id: ConferenceId::new(42),
3227 resource_type: ConferenceResourceType::Conference,
3228 reserved_participants: 8,
3229 active_participants: 3,
3230 application_id: ApplicationId::new(7),
3231 application_conference_id: "festival-42".into(),
3232 application_data: "main-stage".into(),
3233 }],
3234 }),
3235 ProtocolVersion::V22,
3236 );
3237 assert_control_round_trip(
3238 ControlMessage::AddParticipantResponse(AddParticipantResponse {
3239 conference_id: ConferenceId::new(42),
3240 call_reference: CallReference::new(100),
3241 result: AddParticipantResult::Ok,
3242 bridge_participant_id: BoundedBytes::try_from(vec![3; 257]).unwrap(),
3243 }),
3244 ProtocolVersion::V22,
3245 );
3246 assert_control_round_trip(
3247 ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
3248 result: AuditParticipantResult::Ok,
3249 last: 1,
3250 conference_id: ConferenceId::new(42),
3251 number_of_entries: 2,
3252 participant_entries: vec![1, 2, 3, 4],
3253 }),
3254 ProtocolVersion::V22,
3255 );
3256 }
3257
3258 #[test]
3259 fn common_server_messages_round_trip_semantically() {
3260 assert_server_round_trip(
3261 ServerMessage::SpeedDialStatus {
3262 instance: 7,
3263 number: "2001".into(),
3264 display_name: "Reception".into(),
3265 },
3266 ProtocolVersion::V3,
3267 );
3268 assert_server_round_trip(
3269 ServerMessage::ServiceUrlStatus {
3270 index: 4,
3271 url: "http://services.invalid/directory".into(),
3272 label: "Directory".into(),
3273 extension_text: String::new(),
3274 },
3275 ProtocolVersion::V3,
3276 );
3277 for protocol in [
3278 ProtocolVersion::V3,
3279 ProtocolVersion::V17,
3280 ProtocolVersion::V22,
3281 ] {
3282 assert_server_round_trip(
3283 ServerMessage::ConnectionStatisticsRequest {
3284 directory_number: "1001".into(),
3285 call_reference: 42,
3286 processing: StatisticsProcessing::DoNotClear,
3287 },
3288 protocol,
3289 );
3290 }
3291 assert_server_round_trip(
3292 ServerMessage::DisplayPriorityNotify {
3293 timeout_seconds: 5,
3294 priority: NotificationPriority::Voicemail,
3295 text: "Incoming call".into(),
3296 },
3297 ProtocolVersion::V17,
3298 );
3299 assert_server_round_trip(
3300 ServerMessage::FeatureStatus {
3301 instance: 2,
3302 button_type: ButtonType::BlfSpeedDial,
3303 label: "Support".into(),
3304 state: 0x0002_0101,
3305 },
3306 ProtocolVersion::V22,
3307 );
3308 assert_server_round_trip(
3309 ServerMessage::PortRequest(PortRequest {
3310 conference_id: 42.into(),
3311 call_reference: 42.into(),
3312 passthrough_party_id: 9.into(),
3313 transport: MediaTransport::Rtp,
3314 address_type: Some(IpAddressType::Ipv4AndIpv6),
3315 media_type: Some(MediaType::Audio),
3316 }),
3317 ProtocolVersion::V22,
3318 );
3319 assert_server_round_trip(
3320 ServerMessage::Notification {
3321 transaction_id: 3,
3322 feature_id: 1,
3323 status: BusyLampFieldState::Unknown(77),
3324 text: "4000".into(),
3325 },
3326 ProtocolVersion::V22,
3327 );
3328 assert_server_round_trip(
3329 ServerMessage::SubscriptionStatus {
3330 transaction_id: 3,
3331 feature_id: 1,
3332 timer_seconds: 30,
3333 cause: SubscriptionCause::Ok,
3334 },
3335 ProtocolVersion::V22,
3336 );
3337 assert_server_round_trip(
3338 ServerMessage::UserToDeviceData(UserDataMessage {
3339 application_id: 7,
3340 line_instance: 1,
3341 call_reference: 42,
3342 transaction_id: 9,
3343 data: b"<CiscoIPPhoneText/>".to_vec(),
3344 }),
3345 ProtocolVersion::V17,
3346 );
3347 assert_server_round_trip(
3348 ServerMessage::UserToDeviceDataV1(UserDataV1Message {
3349 application_id: 7,
3350 line_instance: 1,
3351 call_reference: 42,
3352 transaction_id: 9,
3353 sequence_flag: 2,
3354 display_priority: 1,
3355 conference_id: 42,
3356 application_instance_id: 7,
3357 routing: 1,
3358 data: b"<CiscoIPPhoneMenu/>".to_vec(),
3359 }),
3360 ProtocolVersion::V17,
3361 );
3362 assert_server_round_trip(
3363 ServerMessage::CallHistoryDisposition {
3364 disposition: CallHistoryDisposition::Missed,
3365 line_instance: 1,
3366 call_reference: 42,
3367 },
3368 ProtocolVersion::V22,
3369 );
3370 assert_server_round_trip(
3371 ServerMessage::CallCountResponse(CallCountResponse {
3372 total_configured_lines: 2,
3373 starting_line_instance: 1,
3374 line_data: vec![
3375 CallCountLineData {
3376 max_calls: 4,
3377 busy_trigger: 2,
3378 },
3379 CallCountLineData {
3380 max_calls: 2,
3381 busy_trigger: 1,
3382 },
3383 ],
3384 }),
3385 ProtocolVersion::V22,
3386 );
3387 for message in [
3388 ServerMessage::SubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3389 payload_type: 101,
3390 conference_id: 42,
3391 passthrough_party_id: 7,
3392 dtmf_type: 2,
3393 }),
3394 ServerMessage::SubscribeDtmfPayloadError(DtmfPayloadIdentity {
3395 payload_type: 102,
3396 conference_id: 43,
3397 passthrough_party_id: 8,
3398 }),
3399 ServerMessage::UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3400 payload_type: 103,
3401 conference_id: 44,
3402 passthrough_party_id: 9,
3403 dtmf_type: 3,
3404 }),
3405 ServerMessage::UnsubscribeDtmfPayloadError(DtmfPayloadIdentity {
3406 payload_type: 104,
3407 conference_id: 45,
3408 passthrough_party_id: 10,
3409 }),
3410 ] {
3411 let encoded = message.encode(ProtocolVersion::V22).unwrap();
3412 let frame = decode_frame(&encoded);
3413 assert!(matches!(frame.payload.len(), 12 | 16));
3414 assert_eq!(
3415 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
3416 message
3417 );
3418 }
3419 assert_server_round_trip(
3420 ServerMessage::RecordingStatus {
3421 call_reference: 42,
3422 active: true,
3423 },
3424 ProtocolVersion::V22,
3425 );
3426 assert_control_round_trip(
3427 ControlMessage::StartAnnouncement {
3428 announcements: vec![
3429 AnnouncementEntry {
3430 locale: 1,
3431 country: 46,
3432 tone: Tone::Zip,
3433 },
3434 AnnouncementEntry {
3435 locale: 0,
3436 country: 0,
3437 tone: Tone::Silence,
3438 },
3439 AnnouncementEntry {
3440 locale: 2,
3441 country: 1,
3442 tone: Tone::RecorderWarning,
3443 },
3444 ],
3445 end_of_ack: EndOfAnnouncementAck::Required,
3446 conference_id: 42,
3447 matrix_conference_party_ids: vec![7, 0, 9],
3448 hearing_conference_party_mask: 0b101,
3449 play_mode: AnnouncementPlayMode::Continuous,
3450 },
3451 ProtocolVersion::V22,
3452 );
3453 assert_control_round_trip(
3454 ControlMessage::StopAnnouncement { conference_id: 42 },
3455 ProtocolVersion::V22,
3456 );
3457 assert_control_round_trip(
3458 ControlMessage::AnnouncementFinish {
3459 conference_id: 42,
3460 play_status: AnnouncementPlayStatus::Unknown(3),
3461 },
3462 ProtocolVersion::V22,
3463 );
3464 assert_control_round_trip(
3465 ControlMessage::ClearConference {
3466 conference_id: ConferenceId::new(42),
3467 service_number: 3,
3468 },
3469 ProtocolVersion::V22,
3470 );
3471 assert_control_round_trip(
3472 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3473 conference_id: ConferenceId::new(42),
3474 reserved_participants: 8,
3475 resource_type: ConferenceResourceType::Conference,
3476 application_id: ApplicationId::new(7),
3477 application_conference_id: "festival-42".into(),
3478 application_data: "main-stage".into(),
3479 passthrough_data: vec![1, 2, 3],
3480 }),
3481 ProtocolVersion::V22,
3482 );
3483 assert_control_round_trip(
3484 ControlMessage::DeleteConferenceRequest {
3485 conference_id: ConferenceId::new(42),
3486 },
3487 ProtocolVersion::V22,
3488 );
3489 assert_control_round_trip(
3490 ControlMessage::ModifyConferenceRequest(ModifyConferenceRequest {
3491 conference_id: ConferenceId::new(42),
3492 reserved_participants: 12,
3493 application_id: ApplicationId::new(7),
3494 application_conference_id: "festival-42".into(),
3495 application_data: "main-stage".into(),
3496 passthrough_data: vec![4, 5],
3497 }),
3498 ProtocolVersion::V22,
3499 );
3500 assert_control_round_trip(ControlMessage::AuditConferenceRequest, ProtocolVersion::V22);
3501 assert_control_round_trip(
3502 ControlMessage::AddParticipantRequest(AddParticipantRequest {
3503 conference_id: ConferenceId::new(42),
3504 participant: ConferenceParticipant {
3505 call_reference: CallReference::new(100),
3506 presentation_restrictions: PartyInformationRestrictions::CALLING_NUMBER,
3507 name: "Festival Caller".into(),
3508 number: "1001".into(),
3509 conference_name: "Main Stage".into(),
3510 },
3511 }),
3512 ProtocolVersion::V22,
3513 );
3514 assert_control_round_trip(
3515 ControlMessage::DropParticipantRequest {
3516 conference_id: ConferenceId::new(42),
3517 call_reference: CallReference::new(100),
3518 },
3519 ProtocolVersion::V22,
3520 );
3521 assert_control_round_trip(
3522 ControlMessage::AuditParticipantRequest {
3523 conference_id: ConferenceId::new(42),
3524 },
3525 ProtocolVersion::V22,
3526 );
3527 }
3528
3529 #[test]
3530 fn connection_statistics_round_trip_all_layouts_and_redact_opaque_fields() {
3531 let statistics = ConnectionStatistics {
3532 directory_number: "2002".into(),
3533 call_reference: 42,
3534 processing: StatisticsProcessing::Clear,
3535 packets_sent: 100,
3536 octets_sent: 8_000,
3537 packets_received: 98,
3538 octets_received: 7_840,
3539 packets_lost: 2,
3540 jitter_millis: 7,
3541 latency_millis: 18,
3542 quality: ConnectionQualityStatistics::new(b"MLQK=4.5;Secret=opaque".to_vec()).unwrap(),
3543 };
3544 for protocol in [
3545 ProtocolVersion::V3,
3546 ProtocolVersion::V19,
3547 ProtocolVersion::V22,
3548 ] {
3549 assert_client_round_trip(
3550 ClientMessage::ConnectionStatisticsResponse(statistics.clone()),
3551 protocol,
3552 );
3553 }
3554 let debug = format!("{statistics:?}");
3555 assert!(!debug.contains("2002"));
3556 assert!(!debug.contains("Secret"));
3557 assert!(debug.contains("byte_count"));
3558 assert!(matches!(
3559 ConnectionQualityStatistics::new(vec![0; CONNECTION_QUALITY_MAX_BYTES + 1]),
3560 Err(CodecError::CountTooLarge {
3561 field: "quality statistics",
3562 maximum: CONNECTION_QUALITY_MAX_BYTES,
3563 ..
3564 })
3565 ));
3566 }
3567
3568 #[test]
3569 fn dtmf_subscription_messages_require_their_exact_word_layouts() {
3570 for message_id in [
3571 wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES,
3572 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_RES,
3573 ] {
3574 assert!(ClientMessage::decode(Frame::new(22, message_id, Vec::new())).is_err());
3575 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 11])).is_err());
3576 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 12])).is_ok());
3577 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 13])).is_err());
3578 }
3579 for (message_id, size) in [
3580 (wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3581 (wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3582 (wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3583 (wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3584 ] {
3585 assert!(
3586 ServerMessage::decode(
3587 Frame::new(22, message_id, vec![0; size - 1]),
3588 ProtocolVersion::V22,
3589 )
3590 .is_err()
3591 );
3592 assert!(
3593 ServerMessage::decode(
3594 Frame::new(22, message_id, vec![0; size]),
3595 ProtocolVersion::V22,
3596 )
3597 .is_ok()
3598 );
3599 assert!(
3600 ServerMessage::decode(
3601 Frame::new(22, message_id, vec![0; size + 1]),
3602 ProtocolVersion::V22,
3603 )
3604 .is_err()
3605 );
3606 }
3607 }
3608
3609 #[test]
3610 fn announcement_lists_enforce_station_bounds() {
3611 let error = ServerMessage::StartAnnouncement {
3612 announcements: vec![
3613 AnnouncementEntry {
3614 locale: 1,
3615 country: 1,
3616 tone: Tone::Zip,
3617 };
3618 33
3619 ],
3620 end_of_ack: 0,
3621 conference_id: 1,
3622 matrix_conference_party_ids: Vec::new(),
3623 hearing_conference_party_mask: 0,
3624 play_mode: 0,
3625 }
3626 .encode(ProtocolVersion::V22)
3627 .unwrap_err();
3628 assert!(matches!(
3629 error,
3630 CodecError::CountTooLarge {
3631 field: "announcements",
3632 count: 33,
3633 maximum: 32,
3634 ..
3635 }
3636 ));
3637
3638 let error = ServerMessage::StartAnnouncement {
3639 announcements: Vec::new(),
3640 end_of_ack: 0,
3641 conference_id: 1,
3642 matrix_conference_party_ids: (1..=17).collect(),
3643 hearing_conference_party_mask: 0,
3644 play_mode: 0,
3645 }
3646 .encode(ProtocolVersion::V22)
3647 .unwrap_err();
3648 assert!(matches!(
3649 error,
3650 CodecError::CountTooLarge {
3651 field: "matrix conference party identifiers",
3652 count: 17,
3653 maximum: 16,
3654 ..
3655 }
3656 ));
3657 }
3658
3659 #[test]
3660 fn enbloc_uses_the_protocol_19_text_width_boundary() {
3661 for (protocol, payload_len, line_offset) in [
3662 (ProtocolVersion::V18, 28, 24),
3663 (ProtocolVersion::V19, 32, 28),
3664 ] {
3665 let message = ClientMessage::EnblocCall {
3666 called_party: "9801".into(),
3667 line_instance: 3,
3668 };
3669 let frame = FrameDecoder::new()
3670 .push(&message.encode(protocol).unwrap())
3671 .unwrap()
3672 .remove(0);
3673 assert_eq!(frame.payload.len(), payload_len);
3674 assert_eq!(
3675 &frame.payload[line_offset..line_offset + 4],
3676 &3_u32.to_le_bytes()
3677 );
3678 assert_eq!(
3679 ClientMessage::decode_with_version(frame, protocol).unwrap(),
3680 message
3681 );
3682 }
3683 }
3684
3685 #[test]
3686 fn supplemental_client_messages_have_typed_layouts() {
3687 let ports = ClientMessage::MediaPortList(MediaPortList {
3688 rtp_ports: vec![16_000, 16_002],
3689 });
3690 let frame = decode_frame(&ports.encode(ProtocolVersion::V22).unwrap());
3691 assert_eq!(frame.message_id, wire_id::MEDIA_PORT_LIST);
3692 assert_eq!(frame.payload.len(), 68);
3693 assert_eq!(
3694 &frame.payload[..12],
3695 &[2, 0, 0, 0, 0x80, 0x3e, 0, 0, 0x82, 0x3e, 0, 0]
3696 );
3697 assert_eq!(
3698 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
3699 ports
3700 );
3701
3702 let token = ClientMessage::SpcpRegisterToken(SpcpRegisterTokenMessage {
3703 device_id: DeviceId::new("SEP001122334455").unwrap(),
3704 device_instance: 2,
3705 address: Ipv4Addr::new(192, 0, 2, 10),
3706 device_type: DeviceType::Cisco7962,
3707 max_streams: 0x0102_0304,
3708 });
3709 let frame = decode_frame(&token.encode(ProtocolVersion::V22).unwrap());
3710 assert_eq!(frame.message_id, wire_id::SPCP_REGISTER_TOKEN_REQ);
3711 assert_eq!(frame.payload.len(), 36);
3712 assert_eq!(&frame.payload[16..20], &[0; 4]);
3713 assert_eq!(&frame.payload[24..28], &[10, 2, 0, 192]);
3714 assert_eq!(&frame.payload[32..36], &[4, 3, 2, 1]);
3715 assert_eq!(
3716 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
3717 token
3718 );
3719
3720 let oversized = ClientMessage::MediaPortList(MediaPortList {
3721 rtp_ports: vec![16_000; MEDIA_PORT_LIST_MAX_PORTS + 1],
3722 });
3723 assert!(matches!(
3724 oversized.encode(ProtocolVersion::V22),
3725 Err(CodecError::CountTooLarge { .. })
3726 ));
3727
3728 let mut invalid_port = vec![0; 68];
3729 invalid_port[..4].copy_from_slice(&1_u32.to_le_bytes());
3730 invalid_port[4..8].copy_from_slice(&65_536_u32.to_le_bytes());
3731 assert!(matches!(
3732 ClientMessage::decode_with_version(
3733 Frame::new(22, wire_id::MEDIA_PORT_LIST, invalid_port),
3734 ProtocolVersion::V22,
3735 ),
3736 Err(CodecError::InvalidValue {
3737 field: "RTP port",
3738 ..
3739 })
3740 ));
3741 }
3742
3743 #[test]
3744 fn supplemental_server_messages_have_typed_layouts() {
3745 for (message, id, payload) in [
3746 (
3747 ServerMessage::SetHookFlashDetect,
3748 wire_id::SET_HOOK_FLASH_DETECT,
3749 vec![],
3750 ),
3751 (
3752 ServerMessage::StartMediaReception,
3753 wire_id::START_MEDIA_RECEPTION,
3754 vec![],
3755 ),
3756 (
3757 ServerMessage::StopMediaReception {
3758 conference_id: 0x0102_0304.into(),
3759 passthrough_party_id: 0x0506_0708.into(),
3760 },
3761 wire_id::STOP_MEDIA_RECEPTION,
3762 vec![4, 3, 2, 1, 8, 7, 6, 5],
3763 ),
3764 (
3765 ServerMessage::EnunciatorCommand,
3766 wire_id::ENUNCIATOR_COMMAND,
3767 vec![],
3768 ),
3769 (
3770 ServerMessage::SpcpRegisterTokenAck {
3771 features: 0x0102_0304,
3772 },
3773 wire_id::SPCP_REGISTER_TOKEN_ACK,
3774 vec![4, 3, 2, 1],
3775 ),
3776 (
3777 ServerMessage::SpcpRegisterTokenReject {
3778 backoff_seconds: 60,
3779 },
3780 wire_id::SPCP_REGISTER_TOKEN_REJECT,
3781 vec![60, 0, 0, 0],
3782 ),
3783 ] {
3784 let frame = decode_frame(&message.encode(ProtocolVersion::V22).unwrap());
3785 assert_eq!(frame.message_id, id);
3786 assert_eq!(frame.payload, payload);
3787 assert_eq!(
3788 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
3789 message
3790 );
3791 }
3792
3793 assert!(
3794 ServerMessage::decode(
3795 Frame::new(22, wire_id::SET_HOOK_FLASH_DETECT, vec![0; 4]),
3796 ProtocolVersion::V22,
3797 )
3798 .is_err()
3799 );
3800 }
3801
3802 #[test]
3803 fn unknown_messages_are_byte_lossless() {
3804 let unknown_payload = vec![9, 8, 7, 6];
3805 let unknown = ServerMessage::decode(
3806 Frame::new(19, 0xdead_beef, unknown_payload.clone()),
3807 ProtocolVersion::V19,
3808 )
3809 .unwrap();
3810 assert!(matches!(unknown, ServerMessage::Unknown(_)));
3811 let unknown_frame = decode_frame(&unknown.encode(ProtocolVersion::V22).unwrap());
3812 assert_eq!(unknown_frame.message_id, 0xdead_beef);
3813 assert_eq!(unknown_frame.protocol_version, 19);
3814 assert_eq!(unknown_frame.payload, unknown_payload);
3815 }
3816
3817 #[test]
3818 fn opaque_encoding_cannot_bypass_a_typed_contract() {
3819 let message = ClientMessage::KnownOpaque(KnownOpaqueMessage {
3820 id: MessageId::IpPort,
3821 protocol_version: ProtocolVersion::V22.wire(),
3822 payload: BoundedBytes::default(),
3823 });
3824
3825 assert!(matches!(
3826 message.encode(ProtocolVersion::V22),
3827 Err(CodecError::InvalidValue {
3828 message_id: wire_id::IP_PORT,
3829 field: "opaque preservation requires an opaque-only contract",
3830 ..
3831 })
3832 ));
3833 }
3834
3835 #[test]
3836 fn malformed_counts_and_oversized_text_are_rejected() {
3837 let mut capabilities = vec![0; 4 + 18 * 16];
3838 capabilities[..4].copy_from_slice(&19_u32.to_le_bytes());
3839 assert!(matches!(
3840 ClientMessage::decode(Frame::new(22, wire_id::CAPABILITIES_RES, capabilities,)),
3841 Err(CodecError::CountTooLarge { .. })
3842 ));
3843 assert!(matches!(
3844 ServerMessage::DisplayText {
3845 text: "x".repeat(32),
3846 }
3847 .encode(ProtocolVersion::V22),
3848 Err(CodecError::TextTooLong { .. })
3849 ));
3850 assert!(matches!(
3851 ClientMessage::DeviceToUserData(UserDataMessage {
3852 application_id: 1,
3853 line_instance: 1,
3854 call_reference: 1,
3855 transaction_id: 1,
3856 data: vec![0; 2001],
3857 })
3858 .encode(ProtocolVersion::V22),
3859 Err(CodecError::CountTooLarge { .. })
3860 ));
3861 assert!(matches!(
3862 ClientMessage::decode(Frame::new(
3863 22,
3864 wire_id::IP_PORT,
3865 70_000_u32.to_le_bytes().to_vec(),
3866 )),
3867 Err(CodecError::InvalidValue { .. })
3868 ));
3869 assert!(matches!(
3870 ServerMessage::StartMediaTransmission {
3871 call_reference: 1,
3872 passthrough_party_id: 1,
3873 endpoint: MediaEndpoint {
3874 address: "2001:db8::1".parse().unwrap(),
3875 rtp_port: 4000,
3876 rtcp_port: 4001,
3877 codec: Codec::Pcmu,
3878 packet_ms: 20,
3879 max_frames_per_packet: 1,
3880 telephone_event_payload: 101,
3881 },
3882 silence_suppression: SilenceSuppression::Off,
3883 traffic_class: crate::types::MediaTrafficClass::default(),
3884 encryption: None,
3885 wire: None,
3886 }
3887 .encode(ProtocolVersion::V3),
3888 Err(CodecError::InvalidValue { .. })
3889 ));
3890 assert!(matches!(
3891 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3892 conference_id: ConferenceId::new(1),
3893 reserved_participants: 2,
3894 resource_type: ConferenceResourceType::Conference,
3895 application_id: ApplicationId::new(1),
3896 application_conference_id: "conference-1".into(),
3897 application_data: String::new(),
3898 passthrough_data: vec![0; 2001],
3899 })
3900 .encode(ProtocolVersion::V22),
3901 Err(CodecError::CountTooLarge {
3902 field: "conference passthrough data",
3903 count: 2001,
3904 maximum: 2000,
3905 ..
3906 })
3907 ));
3908 assert!(matches!(
3909 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3910 last: 1,
3911 entries: vec![
3912 AuditConferenceEntry {
3913 conference_id: ConferenceId::new(1),
3914 resource_type: ConferenceResourceType::Conference,
3915 reserved_participants: 2,
3916 active_participants: 1,
3917 application_id: ApplicationId::new(1),
3918 application_conference_id: String::new(),
3919 application_data: String::new(),
3920 };
3921 33
3922 ],
3923 })
3924 .encode(ProtocolVersion::V22),
3925 Err(CodecError::CountTooLarge {
3926 field: "conference audit entries",
3927 count: 33,
3928 maximum: 32,
3929 ..
3930 })
3931 ));
3932
3933 let mut oversized_conference_data = vec![0; 12];
3934 oversized_conference_data[8..12].copy_from_slice(&2001_u32.to_le_bytes());
3935 assert!(matches!(
3936 ControlMessage::decode(
3937 Frame::new(
3938 22,
3939 wire_id::CREATE_CONFERENCE_RES,
3940 oversized_conference_data
3941 ),
3942 ProtocolVersion::V22,
3943 ),
3944 Err(CodecError::CountTooLarge {
3945 field: "conference passthrough data",
3946 count: 2001,
3947 maximum: 2000,
3948 ..
3949 })
3950 ));
3951
3952 let mut oversized_audit = vec![0; 8];
3953 oversized_audit[4..8].copy_from_slice(&33_u32.to_le_bytes());
3954 assert!(matches!(
3955 ControlMessage::decode(
3956 Frame::new(22, wire_id::AUDIT_CONFERENCE_RES, oversized_audit),
3957 ProtocolVersion::V22,
3958 ),
3959 Err(CodecError::CountTooLarge {
3960 field: "conference audit entries",
3961 count: 33,
3962 maximum: 32,
3963 ..
3964 })
3965 ));
3966 }
3967
3968 #[test]
3969 fn server_response_uses_the_negotiated_address_layout() {
3970 let message = ServerMessage::ServerResponse {
3971 servers: vec![
3972 SignalingServerEndpoint {
3973 name: "primary".into(),
3974 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
3975 port: NonZeroU16::new(2000).unwrap(),
3976 },
3977 SignalingServerEndpoint {
3978 name: "secondary".into(),
3979 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 20)),
3980 port: NonZeroU16::new(2001).unwrap(),
3981 },
3982 ],
3983 };
3984 let v3 = message.encode(ProtocolVersion::V3).unwrap();
3985 let v17 = message.encode(ProtocolVersion::V17).unwrap();
3986 assert_eq!(v3.len(), 292);
3987 assert_eq!(v17.len(), 372);
3988 assert_server_round_trip(message.clone(), ProtocolVersion::V3);
3989 assert_server_round_trip(message, ProtocolVersion::V17);
3990
3991 let mut zero_port = v3;
3992 zero_port[12 + 5 * 48..12 + 5 * 48 + 4].fill(0);
3993 assert!(matches!(
3994 ServerMessage::decode(decode_frame(&zero_port), ProtocolVersion::V3),
3995 Err(CodecError::InvalidValue {
3996 field: "server endpoint",
3997 value: 0,
3998 ..
3999 })
4000 ));
4001 assert_server_round_trip(
4002 ServerMessage::ServerResponse {
4003 servers: vec![SignalingServerEndpoint {
4004 name: "sccp-v6".into(),
4005 address: "2001:db8::20".parse().unwrap(),
4006 port: NonZeroU16::new(2000).unwrap(),
4007 }],
4008 },
4009 ProtocolVersion::V17,
4010 );
4011
4012 let unspecified = ServerMessage::ServerResponse {
4013 servers: vec![SignalingServerEndpoint {
4014 name: "unroutable".into(),
4015 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
4016 port: NonZeroU16::new(2000).unwrap(),
4017 }],
4018 };
4019 assert!(matches!(
4020 unspecified.encode(ProtocolVersion::V17),
4021 Err(CodecError::InvalidValue {
4022 field: "server address",
4023 value: 0,
4024 ..
4025 })
4026 ));
4027
4028 let endpoints = |count: u8| {
4029 (0..count)
4030 .map(|index| SignalingServerEndpoint {
4031 name: format!("node-{index}"),
4032 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, index + 1)),
4033 port: NonZeroU16::new(2000).unwrap(),
4034 })
4035 .collect()
4036 };
4037 let empty = ServerMessage::ServerResponse {
4038 servers: Vec::new(),
4039 };
4040 assert!(matches!(
4041 empty.encode(ProtocolVersion::V17),
4042 Err(CodecError::InvalidValue {
4043 field: "server endpoints",
4044 value: 0,
4045 ..
4046 })
4047 ));
4048 assert_server_round_trip(
4049 ServerMessage::ServerResponse {
4050 servers: endpoints(5),
4051 },
4052 ProtocolVersion::V17,
4053 );
4054 let too_many = ServerMessage::ServerResponse {
4055 servers: endpoints(6),
4056 };
4057 assert!(matches!(
4058 too_many.encode(ProtocolVersion::V17),
4059 Err(CodecError::CountTooLarge {
4060 field: "server endpoints",
4061 count: 6,
4062 maximum: 5,
4063 ..
4064 })
4065 ));
4066 }
4067}