1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
15use std::num::NonZeroU16;
16
17use binrw::{BinRead, BinWrite};
18
19use super::capabilities::{CapabilityUpdate, CapabilityUpdateVariant};
20use super::catalog::{CodecSupport, MessageRoute};
21use super::values::{
22 AddParticipantResult, AlarmSeverity, AnnouncementPlayMode, AnnouncementPlayStatus,
23 AuditParticipantResult, BusyLampFieldState, ButtonType, CallHistoryDisposition, CallState,
24 Codec, ConferenceResourceType, CreateConferenceResult, DeleteConferenceResult, DeviceType,
25 Digit, DynamicCallInfoLayout, EchoCancellation, EncryptionMethod, EndOfAnnouncementAck,
26 G723BitRate, IpAddressType, KeyMode, LampMode, MediaPathCapability, MediaPathEvent,
27 MediaPathId, MediaStatus, MediaTransport, MediaType, MessageWaitingResult, MicrophoneMode,
28 ModifyConferenceResult, NotificationPriority, PartyInformationRestrictions, PhoneFeatures,
29 ProtocolVersion, QosDirection, QosErrorCode, QosReservationStyle, ResetType, RingDuration,
30 RingerMode, RsvpErrorCode, SilenceSuppression, SoftKey, SpeakerMode, StationSessionContext,
31 StatisticsProcessing, Stimulus, SubscriptionCause, Tone, ToneDirection,
32};
33use super::wire::{CodecError, Frame};
34use super::*;
35use crate::types::{
36 CallInfo, DateTemplate, DeviceId, LegacyCodePage, MAX_STATION_BUTTON_INSTANCE, MediaEndpoint,
37 MediaTrafficClass, SoftKeyProfile,
38};
39
40mod conference;
41mod fixed_text;
42mod io;
43mod media;
44mod qos;
45mod services;
46mod station;
47use conference::*;
48use fixed_text::{WireFixedText, station_text_bytes};
49use io::{
50 decode, decode_prefix, decode_zero_padded, encode, usize_from_wire, validate_exact_payload,
51 validate_payload_bounds, validate_zero_payload, wire_count,
52};
53use media::*;
54use qos::*;
55use services::*;
56use station::*;
57
58fn ensure_station_route(
59 frame: &Frame,
60 expected: MessageRoute,
61 expected_name: &'static str,
62) -> Result<(), CodecError> {
63 let Some(actual) = frame.message_type().route() else {
64 return Ok(());
65 };
66 if actual == expected {
67 Ok(())
68 } else {
69 Err(CodecError::UnexpectedRoute {
70 message_id: frame.message_id,
71 actual,
72 expected: expected_name,
73 })
74 }
75}
76
77fn validate_media_port_count(message_id: u32, count: usize) -> Result<(), CodecError> {
78 match count {
79 0..=MEDIA_PORT_LIST_MAX_PORTS => Ok(()),
80 _ => Err(CodecError::CountTooLarge {
81 message_id,
82 field: "RTP ports",
83 count,
84 maximum: MEDIA_PORT_LIST_MAX_PORTS,
85 }),
86 }
87}
88
89fn preserve_known_message(frame: Frame, id: MessageId) -> Result<KnownOpaqueMessage, CodecError> {
90 ensure_preserve_only(id)?;
91 let payload = BoundedBytes::try_from(frame.payload).map_err(|error| {
92 CodecError::FrameTooLarge(error.actual.saturating_add(super::wire::HEADER_SIZE))
93 })?;
94 Ok(KnownOpaqueMessage {
95 id,
96 protocol_version: frame.protocol_version,
97 payload,
98 })
99}
100
101fn ensure_preserve_only(id: MessageId) -> Result<(), CodecError> {
102 if id
103 .contract()
104 .is_some_and(|contract| contract.codec == CodecSupport::OpaqueOnly)
105 {
106 Ok(())
107 } else {
108 Err(CodecError::InvalidValue {
109 message_id: id.wire_value(),
110 field: "opaque preservation requires an opaque-only contract",
111 value: u64::from(id.wire_value()),
112 })
113 }
114}
115
116fn pad_typed_payload(message_id: u32, payload: &mut Vec<u8>) {
117 use super::catalog::PayloadLayout;
118
119 let Some(contract) = MessageId::from(message_id).contract() else {
120 return;
121 };
122 if !matches!(
123 contract.payload_layout,
124 PayloadLayout::Opaque
125 | PayloadLayout::BoundedOpaque
126 | PayloadLayout::BoundedPreserved
127 | PayloadLayout::VersionAndLengthSelected
128 | PayloadLayout::MinimumLengthPreserved
129 ) {
130 pad_dynamic_payload(payload);
131 }
132}
133
134fn canonical_open_receive_wire(
135 call_reference: u32,
136 source_address: IpAddr,
137 protocol: ProtocolVersion,
138) -> OpenReceiveChannelWire {
139 OpenReceiveChannelWire {
140 conference_id: call_reference,
141 g723_bitrate: 0,
142 stream_passthrough_id: 0,
143 associated_stream_id: 0,
144 dtmf_type: 10,
145 mixing_mode: 0,
146 direction: u32::from(protocol.wire() >= 12),
147 requested_address_type: u32::from(
148 protocol.wire() >= 17 && matches!(source_address, IpAddr::V6(_)),
149 ),
150 audio_level_adjustment: 0,
151 latent_capabilities: [0; 36],
152 }
153}
154
155fn canonical_start_media_wire(
156 call_reference: u32,
157 protocol: ProtocolVersion,
158) -> StartMediaTransmissionWire {
159 StartMediaTransmissionWire {
160 conference_id: call_reference,
161 g723_bitrate: 0,
162 stream_passthrough_id: 0,
163 associated_stream_id: 0,
164 dtmf_type: 10,
165 mixing_mode: 0,
166 direction: u32::from(protocol.wire() >= 12),
167 latent_capabilities: [0; 36],
168 }
169}
170
171#[derive(BinRead, BinWrite, Clone, Copy, Default, Eq, PartialEq)]
172#[brw(little)]
173struct WireEncryptionInfo {
174 algorithm: u32,
175 key_length: u16,
176 salt_length: u16,
177 key: [u8; 16],
178 salt: [u8; 16],
179 mki_present: u32,
180 key_derivation_rate: u32,
181}
182
183impl std::fmt::Debug for WireEncryptionInfo {
184 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 formatter
186 .debug_struct("WireEncryptionInfo")
187 .field("algorithm", &EncryptionMethod::from(self.algorithm))
188 .field("key", &"<redacted>")
189 .field("key_length", &self.key_length)
190 .field("salt", &"<redacted>")
191 .field("salt_length", &self.salt_length)
192 .field("mki_present", &self.mki_present)
193 .field("key_derivation_rate", &self.key_derivation_rate)
194 .finish()
195 }
196}
197
198impl WireEncryptionInfo {
199 fn from_public(encryption: Option<&MediaEncryption>) -> Self {
200 let Some(encryption) = encryption else {
201 return Self::default();
202 };
203 Self {
204 algorithm: encryption.algorithm.wire_value(),
205 key_length: u16::from(encryption.key_length),
206 salt_length: u16::from(encryption.salt_length),
207 key: encryption.key,
208 salt: encryption.salt,
209 mki_present: encryption.mki_present,
210 key_derivation_rate: encryption.key_derivation_rate,
211 }
212 }
213
214 fn to_public(self, _message_id: u32) -> Result<Option<MediaEncryption>, CodecError> {
215 if usize::from(self.key_length) > self.key.len() {
216 return Err(CodecError::SecretTooLong {
217 field: "media encryption key",
218 actual: usize::from(self.key_length),
219 maximum: self.key.len(),
220 });
221 }
222 if usize::from(self.salt_length) > self.salt.len() {
223 return Err(CodecError::SecretTooLong {
224 field: "media encryption salt",
225 actual: usize::from(self.salt_length),
226 maximum: self.salt.len(),
227 });
228 }
229 if self.algorithm == 0
230 && self.key_length == 0
231 && self.salt_length == 0
232 && self.mki_present == 0
233 && self.key_derivation_rate == 0
234 {
235 return Ok(None);
236 }
237 Ok(Some(MediaEncryption::from_wire(
238 EncryptionMethod::from(self.algorithm),
239 self.key,
240 self.key_length as u8,
241 self.salt,
242 self.salt_length as u8,
243 self.mki_present,
244 self.key_derivation_rate,
245 )))
246 }
247}
248
249#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
250#[brw(little)]
251struct WireLatentCapabilities {
252 bytes: [u8; 36],
253}
254
255impl Default for WireLatentCapabilities {
256 fn default() -> Self {
257 Self { bytes: [0; 36] }
258 }
259}
260
261trait WireIpAddress:
262 for<'a> BinRead<Args<'a> = ()>
263 + for<'a> BinWrite<Args<'a> = ()>
264 + Clone
265 + Copy
266 + std::fmt::Debug
267 + Eq
268 + PartialEq
269 + 'static
270{
271 fn from_ip(address: IpAddr, message_id: u32, field: &'static str) -> Result<Self, CodecError>;
272 fn to_ip(self, message_id: u32) -> Result<IpAddr, CodecError>;
273}
274
275#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
276struct WireIpv4Address {
277 bytes: [u8; 4],
278}
279
280impl From<[u8; 4]> for WireIpv4Address {
281 fn from(bytes: [u8; 4]) -> Self {
282 Self { bytes }
283 }
284}
285
286impl From<Ipv4Addr> for WireIpv4Address {
287 fn from(address: Ipv4Addr) -> Self {
288 address.octets().into()
289 }
290}
291
292impl From<WireIpv4Address> for Ipv4Addr {
293 fn from(address: WireIpv4Address) -> Self {
294 Self::from(address.bytes)
295 }
296}
297
298impl WireIpAddress for WireIpv4Address {
299 fn from_ip(address: IpAddr, message_id: u32, field: &'static str) -> Result<Self, CodecError> {
300 let IpAddr::V4(address) = address else {
301 return Err(CodecError::InvalidValue {
302 message_id,
303 field,
304 value: 1,
305 });
306 };
307 Ok(Self {
308 bytes: address.octets(),
309 })
310 }
311
312 fn to_ip(self, _message_id: u32) -> Result<IpAddr, CodecError> {
313 Ok(IpAddr::V4(Ipv4Addr::from(self.bytes)))
314 }
315}
316
317#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
318#[brw(little)]
319struct WireExtendedAddress {
320 family: u32,
321 bytes: [u8; 16],
322}
323
324impl WireExtendedAddress {
325 fn from_ip(address: IpAddr) -> Self {
326 match address {
327 IpAddr::V4(address) => {
328 let mut bytes = [0; 16];
329 bytes[..4].copy_from_slice(&address.octets());
330 Self { family: 0, bytes }
331 }
332 IpAddr::V6(address) => Self {
333 family: 1,
334 bytes: address.octets(),
335 },
336 }
337 }
338
339 fn to_ip(self, message_id: u32) -> Result<IpAddr, CodecError> {
340 match self.family {
341 0 => Ok(IpAddr::V4(Ipv4Addr::new(
342 self.bytes[0],
343 self.bytes[1],
344 self.bytes[2],
345 self.bytes[3],
346 ))),
347 1 => Ok(IpAddr::V6(Ipv6Addr::from(self.bytes))),
348 value => Err(CodecError::InvalidValue {
349 message_id,
350 field: "IP address family",
351 value: u64::from(value),
352 }),
353 }
354 }
355}
356
357impl WireIpAddress for WireExtendedAddress {
358 fn from_ip(
359 address: IpAddr,
360 _message_id: u32,
361 _field: &'static str,
362 ) -> Result<Self, CodecError> {
363 Ok(Self::from_ip(address))
364 }
365
366 fn to_ip(self, message_id: u32) -> Result<IpAddr, CodecError> {
367 self.to_ip(message_id)
368 }
369}
370
371#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
372#[brw(little)]
373struct WireStartMulticastReception<Address: WireIpAddress> {
374 conference_id: u32,
375 passthrough_party_id: u32,
376 address: Address,
377 port: u32,
378 packet_millis: u32,
379 codec: u32,
380 echo_cancellation: u32,
381 g723_bitrate: u32,
382 call_reference: u32,
383}
384
385#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
386#[brw(little)]
387struct WireStartMulticastTransmission<Address: WireIpAddress> {
388 conference_id: u32,
389 passthrough_party_id: u32,
390 address: Address,
391 port: u32,
392 packet_millis: u32,
393 codec: u32,
394 precedence: u32,
395 silence_suppression: u32,
396 max_frames_per_packet: u32,
397 g723_bitrate: u32,
398 call_reference: u32,
399}
400
401#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
402#[brw(little)]
403struct WireOpenReceiveV11 {
404 conference_id: u32,
405 passthrough_party_id: u32,
406 packet_millis: u32,
407 codec: u32,
408 vad: u32,
409 g723_bitrate: u32,
410 call_reference: u32,
411 encryption: WireEncryptionInfo,
412 stream_passthrough_id: u32,
413 associated_stream_id: u32,
414 rfc2833_payload: u32,
415 dtmf_type: u32,
416}
417
418#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
419#[brw(little)]
420struct WireOpenReceiveAddressed<Address: WireIpAddress> {
421 base: WireOpenReceiveV11,
422 mixing_mode: u32,
423 direction: u32,
424 remote: Address,
425 remote_port: u32,
426}
427
428type WireOpenReceiveV12 = WireOpenReceiveAddressed<WireIpv4Address>;
429
430#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
431#[brw(little)]
432struct WireOpenReceiveV17 {
433 base: WireOpenReceiveAddressed<WireExtendedAddress>,
434 requested_address_type: u32,
435}
436
437#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
438#[brw(little)]
439struct WireOpenReceiveV18 {
440 base: WireOpenReceiveV17,
441 audio_level_adjustment: u32,
442}
443
444#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
445#[brw(little)]
446struct WireOpenReceiveV21 {
447 base: WireOpenReceiveV18,
448 latent_capabilities: WireLatentCapabilities,
449}
450
451#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
452#[brw(little)]
453struct WireStartMediaBase<Address: WireIpAddress> {
454 conference_id: u32,
455 passthrough_party_id: u32,
456 remote: Address,
457 remote_port: u32,
458 packet_millis: u32,
459 codec: u32,
460 precedence: u32,
461 silence_suppression: u32,
462 max_frames_per_packet: u32,
463 g723_bitrate: u32,
464 call_reference: u32,
465 encryption: WireEncryptionInfo,
466 stream_passthrough_id: u32,
467 associated_stream_id: u32,
468 rfc2833_payload: u32,
469 dtmf_type: u32,
470}
471
472type WireStartMediaV11 = WireStartMediaBase<WireIpv4Address>;
473
474#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
475#[brw(little)]
476struct WireStartMediaDirected<Address: WireIpAddress> {
477 base: WireStartMediaBase<Address>,
478 mixing_mode: u32,
479 direction: u32,
480}
481
482type WireStartMediaV12 = WireStartMediaDirected<WireIpv4Address>;
483type WireStartMediaV17 = WireStartMediaDirected<WireExtendedAddress>;
484
485#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
486#[brw(little)]
487struct WireStartMediaV21 {
488 base: WireStartMediaV17,
489 latent_capabilities: WireLatentCapabilities,
490}
491
492#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
493#[brw(little)]
494struct WireStartMediaAck<Address: WireIpAddress> {
495 conference_id: u32,
496 passthrough_party_id: u32,
497 call_reference: u32,
498 address: Address,
499 port: u32,
500 status: u32,
501}
502
503type WireStartMediaAckV3 = WireStartMediaAck<WireIpv4Address>;
504type WireStartMediaAckV17 = WireStartMediaAck<WireExtendedAddress>;
505
506#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
507#[brw(little)]
508struct WireStartMediaAckV20 {
509 base: WireStartMediaAckV17,
510 extension: [u8; 8],
511}
512
513macro_rules! words {
514 ($name:ident { $($field:ident),+ $(,)? }) => {
515 #[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
516 #[brw(little)]
517 struct $name {
518 $($field: u32),+
519 }
520 };
521}
522
523words!(WireOneWord { value });
524words!(WireMulticastReceptionAck {
525 status,
526 passthrough_party_id,
527 call_reference
528});
529words!(WireLineCall {
530 line_instance,
531 call_reference
532});
533words!(WireCallParty {
534 call_reference,
535 passthrough_party_id
536});
537words!(WireAudioStreamControl {
538 conference_id,
539 passthrough_party_id,
540 call_reference,
541 port_handling_flag
542});
543words!(WireSelectSoftKeys {
544 line_instance,
545 call_reference,
546 set,
547 valid_mask
548});
549words!(WireCallState {
550 state,
551 line_instance,
552 call_reference,
553 visibility,
554 precedence,
555 domain
556});
557words!(WireCallInfoDynamicHeader {
558 line_instance,
559 call_reference,
560 call_type,
561 original_redirect_reason,
562 last_redirect_reason,
563 call_instance,
564 security_status,
565 party_restrictions
566});
567words!(WireDynamicPromptHeader {
568 timeout_seconds,
569 line_instance,
570 call_reference
571});
572words!(WireModeLineCall {
573 mode,
574 duration,
575 line_instance,
576 call_reference
577});
578words!(WireToneLineCall {
579 tone,
580 direction,
581 line_instance,
582 call_reference
583});
584words!(WireLampState {
585 stimulus,
586 instance,
587 mode
588});
589words!(WirePortRequest {
590 conference_id,
591 call_reference,
592 passthrough_party_id,
593 transport
594});
595#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
596#[brw(little)]
597struct WirePortRequestV20 {
598 base: WirePortRequest,
599 address_type: u32,
600 media_type: u32,
601}
602words!(WirePortClose {
603 conference_id,
604 call_reference,
605 passthrough_party_id
606});
607#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
608#[brw(little)]
609struct WirePortCloseV20 {
610 base: WirePortClose,
611 media_type: u32,
612}
613words!(WireSubscriptionStatus {
614 transaction_id,
615 feature_id,
616 timer_seconds,
617 cause
618});
619words!(WireCallSelectStatus {
620 status,
621 call_reference,
622 line_instance
623});
624words!(WireRecordingStatus {
625 call_reference,
626 active
627});
628words!(WireFeatureStatusRequest {
629 index,
630 capabilities
631});
632words!(WireLineStatusDynamicHeader {
633 line_instance,
634 line_type
635});
636words!(WireStopToneV12 {
637 line_instance,
638 call_reference,
639 tone
640});
641words!(WireCallHistoryDisposition {
642 disposition,
643 line_instance,
644 call_reference
645});
646words!(WireAnnouncementFinish {
647 conference_id,
648 play_status
649});
650words!(WireStopMulticast {
651 conference_id,
652 passthrough_party_id,
653 call_reference
654});
655words!(WireAddParticipantResponseHeader {
656 conference_id,
657 call_reference,
658 result
659});
660words!(WireAuditParticipantResponseHeader {
661 result,
662 last,
663 conference_id,
664 number_of_entries
665});
666
667#[derive(BinRead, BinWrite, Clone, Copy, Debug, Default, Eq, PartialEq)]
668#[brw(little)]
669struct WireAnnouncementEntry {
670 locale: u32,
671 country: u32,
672 tone: u32,
673}
674
675#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
676#[brw(little)]
677struct WireStartAnnouncement {
678 announcements: [WireAnnouncementEntry; 32],
679 end_of_ack: u32,
680 conference_id: u32,
681 matrix_conference_party_ids: [u32; 16],
682 hearing_conference_party_mask: u32,
683 play_mode: u32,
684}
685
686#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
687#[brw(little)]
688struct WireCreateConferenceRequest {
689 conference_id: u32,
690 reserved_participants: u32,
691 resource_type: u32,
692 application_id: u32,
693 application_conference_id: WireFixedText<32>,
694 application_data: WireFixedText<24>,
695 data_length: u32,
696 #[br(count = data_length)]
697 passthrough_data: Vec<u8>,
698}
699
700#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
701#[brw(little)]
702struct WireModifyConferenceRequest {
703 conference_id: u32,
704 reserved_participants: u32,
705 application_id: u32,
706 application_conference_id: WireFixedText<32>,
707 application_data: WireFixedText<24>,
708 data_length: u32,
709 #[br(count = data_length)]
710 passthrough_data: Vec<u8>,
711}
712
713#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
714#[brw(little)]
715struct WireConferenceResponse {
716 conference_id: u32,
717 result: u32,
718 data_length: u32,
719 #[br(count = data_length)]
720 passthrough_data: Vec<u8>,
721}
722
723#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
724#[brw(little)]
725struct WireAuditConferenceEntry {
726 conference_id: u32,
727 resource_type: u32,
728 reserved_participants: u32,
729 active_participants: u32,
730 application_id: u32,
731 application_conference_id: WireFixedText<32>,
732 application_data: WireFixedText<24>,
733}
734
735#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
736#[brw(little)]
737struct WireAuditConferenceResponse {
738 last: u32,
739 number_of_entries: u32,
740 #[br(count = number_of_entries)]
741 entries: Vec<WireAuditConferenceEntry>,
742}
743
744#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
745#[brw(little)]
746struct WireParticipantRequest {
747 conference_id: u32,
748 call_reference: u32,
749 presentation_restrictions: u32,
750 participant_name: WireFixedText<40>,
751 participant_number: WireFixedText<24>,
752 conference_name: WireFixedText<32>,
753}
754
755#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
756#[brw(little)]
757struct WireQosFlow {
758 conference_id: u32,
759 call_reference: u32,
760 passthrough_party_id: u32,
761 address: [u8; 4],
762 port: u32,
763}
764
765#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
766#[brw(little)]
767struct WireQosApplicationIdentifier {
768 vendor_id: WireFixedText<32>,
769 version: WireFixedText<16>,
770 application_name: WireFixedText<32>,
771 sub_application_id: WireFixedText<32>,
772}
773
774#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
775#[brw(little)]
776struct WireQosReservationNotify {
777 flow: WireQosFlow,
778 direction: u32,
779}
780
781#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
782#[brw(little)]
783struct WireUpdateDscp {
784 flow: WireQosFlow,
785 dscp: u32,
786}
787
788#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
789#[brw(little)]
790struct WireQosErrorNotify {
791 flow: WireQosFlow,
792 direction: u32,
793 error_code: u32,
794 failure_node: u32,
795 rsvp_error_code: u32,
796 rsvp_error_subcode: u32,
797 rsvp_error_flags: u32,
798}
799
800#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
801#[brw(little)]
802struct WireQosListen {
803 flow: WireQosFlow,
804 reservation_style: u32,
805 maximum_retries: u32,
806 retry_timer: u32,
807 confirmation_required: u32,
808 preemption_priority: u32,
809 defending_priority: u32,
810 compression_type: u32,
811 average_bit_rate: u32,
812 burst_size: u32,
813 peak_rate: u32,
814 application: WireQosApplicationIdentifier,
815}
816
817#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
818#[brw(little)]
819struct WireQosPath {
820 flow: WireQosFlow,
821 reservation_style: u32,
822 maximum_retries: u32,
823 retry_timer: u32,
824 preemption_priority: u32,
825 defending_priority: u32,
826 compression_type: u32,
827 average_bit_rate: u32,
828 burst_size: u32,
829 peak_rate: u32,
830 application: WireQosApplicationIdentifier,
831}
832
833#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
834#[brw(little)]
835struct WireQosModify {
836 flow: WireQosFlow,
837 direction: u32,
838 compression_type: u32,
839 average_bit_rate: u32,
840 burst_size: u32,
841 peak_rate: u32,
842 application: WireQosApplicationIdentifier,
843}
844
845#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
846#[brw(little)]
847struct WireMessageWaitingNotification {
848 target_number: WireFixedText<25>,
849 control_number: WireFixedText<25>,
850 alignment: [u8; 2],
851 messages_waiting: u32,
852 total_voicemail_new: u32,
853 total_voicemail_old: u32,
854 priority_voicemail_new: u32,
855 priority_voicemail_old: u32,
856 total_fax_new: u32,
857 total_fax_old: u32,
858 priority_fax_new: u32,
859 priority_fax_old: u32,
860}
861
862#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
863#[brw(little)]
864struct WireMessageWaitingResponse {
865 target_number: WireFixedText<25>,
866 alignment: [u8; 3],
867 result: u32,
868}
869
870#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
871#[brw(little)]
872struct WireRegisterAck {
873 keepalive_seconds: u32,
874 date_template: [u8; 6],
875 alignment: [u8; 2],
876 secondary_keepalive_seconds: u32,
877 protocol_features: [u8; 4],
878}
879
880#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
881#[brw(little)]
882struct WireConfigStatus {
883 device_id: WireFixedText<16>,
884 station_user_id: u32,
885 station_instance: u32,
886 user_name: WireFixedText<40>,
887 server_name: WireFixedText<40>,
888 line_count: u32,
889 speed_dial_count: u32,
890}
891
892#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
893#[brw(little)]
894struct WireLineStatus {
895 line_instance: u32,
896 directory_number: WireFixedText<24>,
897 display_name: WireFixedText<40>,
898 display_label: WireFixedText<40>,
899 reserved: u32,
900}
901
902#[derive(BinRead, BinWrite, Clone, Copy, Debug, Default, Eq, PartialEq)]
903struct WireButtonDefinition {
904 instance: u8,
905 button_type: u8,
906}
907
908#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
909#[brw(little)]
910struct WireButtonTemplate {
911 offset: u32,
912 count: u32,
913 total: u32,
914 definitions: [WireButtonDefinition; BUTTON_TEMPLATE_ENTRIES_PER_CHUNK],
915}
916
917#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
918#[brw(little)]
919struct WireServerResponse<Address: WireIpAddress> {
920 names: [WireFixedText<48>; 5],
921 ports: [u32; 5],
922 addresses: [Address; 5],
923}
924
925words!(WireTimeDate {
926 year,
927 month,
928 weekday,
929 day,
930 hour,
931 minute,
932 second,
933 milliseconds,
934 unix_seconds
935});
936
937#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
938#[brw(little)]
939struct WireSoftKeyDefinition {
940 label: [u8; 16],
941 event: u32,
942}
943
944#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
945#[brw(little)]
946struct WireSoftKeyTemplate {
947 offset: u32,
948 count: u32,
949 total: u32,
950 definitions: [WireSoftKeyDefinition; 32],
951}
952
953#[derive(BinRead, BinWrite, Clone, Copy, Debug, Default, Eq, PartialEq)]
954#[brw(little)]
955struct WireSoftKeySetDefinition {
956 template_indexes: [u8; 16],
957 info: [u16; 16],
958}
959
960#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
961#[brw(little)]
962struct WireSoftKeySet {
963 offset: u32,
964 count: u32,
965 total: u32,
966 #[br(count = 16)]
967 sets: Vec<WireSoftKeySetDefinition>,
968}
969
970#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
971#[brw(little)]
972struct WireCallInfo {
973 calling_name: WireFixedText<40>,
974 calling_number: WireFixedText<24>,
975 called_name: WireFixedText<40>,
976 called_number: WireFixedText<24>,
977 line_instance: u32,
978 call_reference: u32,
979 call_type: u32,
980 original_called_name: WireFixedText<40>,
981 original_called_number: WireFixedText<24>,
982 last_redirecting_name: WireFixedText<40>,
983 last_redirecting_number: WireFixedText<24>,
984 original_redirect_reason: u32,
985 last_redirect_reason: u32,
986 voice_mailboxes: [WireFixedText<24>; 4],
987 call_instance: u32,
988 security_status: u32,
989 party_restrictions: u32,
990}
991
992#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
993#[brw(little)]
994struct WirePromptStatus {
995 timeout_seconds: u32,
996 text: WireFixedText<32>,
997 line_instance: u32,
998 call_reference: u32,
999}
1000
1001#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1002#[brw(little)]
1003struct WireNotify {
1004 timeout_seconds: u32,
1005 text: WireFixedText<32>,
1006}
1007
1008#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1009#[brw(little)]
1010struct WireDynamicNotifyHeader {
1011 timeout_seconds: u32,
1012}
1013
1014#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1015#[brw(little)]
1016struct WirePriorityNotify {
1017 timeout_seconds: u32,
1018 priority: u32,
1019 text: WireFixedText<32>,
1020}
1021
1022#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1023#[brw(little)]
1024struct WireDynamicPriorityNotifyHeader {
1025 timeout_seconds: u32,
1026 priority: u32,
1027}
1028
1029#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1030struct WireAlignedText<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1031 value: WireFixedText<TEXT_BYTES>,
1032 alignment: [u8; ALIGNMENT_BYTES],
1033}
1034
1035impl<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>
1036 WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>
1037{
1038 fn new(message_id: u32, field: &'static str, value: &str) -> Result<Self, CodecError> {
1039 Ok(Self {
1040 value: WireFixedText::new(message_id, field, value)?,
1041 alignment: [0; ALIGNMENT_BYTES],
1042 })
1043 }
1044
1045 fn text(&self) -> Result<String, CodecError> {
1046 self.value.text()
1047 }
1048
1049 fn validate(&self, message_id: u32) -> Result<(), CodecError> {
1050 validate_zero_payload(&self.alignment, message_id, ALIGNMENT_BYTES)
1051 }
1052}
1053
1054#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1055#[brw(little)]
1056struct WireConnectionStatisticsRequest<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1057 directory_number: WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>,
1058 call_reference: u32,
1059 processing: u32,
1060}
1061
1062#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1063#[brw(little)]
1064struct WireForwardTarget<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1065 active: u32,
1066 number: WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>,
1067}
1068
1069#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1070#[brw(little)]
1071struct WireForwardStatus<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1072 active: u32,
1073 line_instance: u32,
1074 all: WireForwardTarget<TEXT_BYTES, ALIGNMENT_BYTES>,
1075 busy: WireForwardTarget<TEXT_BYTES, ALIGNMENT_BYTES>,
1076 no_answer: WireForwardTarget<TEXT_BYTES, ALIGNMENT_BYTES>,
1077}
1078
1079#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1080#[brw(little)]
1081struct WireSpeedDialStatus {
1082 instance: u32,
1083 number: WireFixedText<24>,
1084 display_name: WireFixedText<40>,
1085}
1086
1087#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1088#[brw(little)]
1089struct WireDialedNumber<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1090 number: WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>,
1091 line_instance: u32,
1092 call_reference: u32,
1093}
1094
1095#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1096#[brw(little)]
1097struct WireFeatureStatus {
1098 instance: u32,
1099 button_type: u32,
1100 label: WireFixedText<40>,
1101 state: u32,
1102}
1103
1104#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1105#[brw(little)]
1106struct WireFeatureStatusDynamic {
1107 instance: u32,
1108 button_type: u32,
1109 state: u32,
1110 label: WireFixedText<121>,
1111 padding: [u8; 3],
1112}
1113
1114#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1115#[brw(little)]
1116struct WireServiceUrlStatus {
1117 index: u32,
1118 url: WireFixedText<256>,
1119 label: WireFixedText<40>,
1120}
1121
1122#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1123#[brw(little)]
1124struct WireNotification {
1125 transaction_id: u32,
1126 feature_id: u32,
1127 status: u32,
1128 text: WireFixedText<100>,
1129}
1130
1131#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1132#[brw(little)]
1133struct WireRegister {
1134 device_id: WireFixedText<16>,
1135 station_user_id: u32,
1136 station_instance: u32,
1137 reported_address: [u8; 4],
1138 device_type: u32,
1139 max_streams: u32,
1140 active_streams: u32,
1141 protocol_features: [u8; 4],
1142 max_conferences: u32,
1143 active_conferences: u32,
1144 mac_address: [u8; 12],
1145 ipv4_address_scope: u32,
1146 max_lines: u32,
1147 ipv6_address: [u8; 16],
1148 ipv6_address_scope: u32,
1149 firmware: WireFixedText<32>,
1150}
1151
1152const REGISTER_ALTERNATE_BYTES: usize = 32;
1153const REGISTER_CANONICAL_BYTES: usize = 124;
1154const REGISTER_MAXIMUM_BYTES: usize = 172;
1155
1156const fn valid_registration_canonical_prefix(bytes: usize) -> bool {
1157 matches!(bytes, 36 | 40 | 44 | 48 | 52 | 64 | 68 | 72 | 88 | 92 | 124)
1158}
1159
1160fn registration_layout(
1161 message_id: u32,
1162 payload_bytes: usize,
1163) -> Result<RegistrationWireLayout, CodecError> {
1164 if payload_bytes < REGISTER_ALTERNATE_BYTES {
1165 return Err(CodecError::Truncated {
1166 message_id,
1167 needed: REGISTER_ALTERNATE_BYTES,
1168 actual: payload_bytes,
1169 });
1170 }
1171 if payload_bytes > REGISTER_MAXIMUM_BYTES {
1172 return Err(CodecError::TrailingBytes {
1173 message_id,
1174 count: payload_bytes - REGISTER_MAXIMUM_BYTES,
1175 });
1176 }
1177 if payload_bytes == REGISTER_ALTERNATE_BYTES {
1178 return Ok(RegistrationWireLayout::Alternate32);
1179 }
1180
1181 let prefix_bytes = payload_bytes.min(REGISTER_CANONICAL_BYTES);
1182 if !valid_registration_canonical_prefix(prefix_bytes) {
1183 return Err(CodecError::InvalidValue {
1184 message_id,
1185 field: "registration payload length",
1186 value: payload_bytes as u64,
1187 });
1188 }
1189 Ok(RegistrationWireLayout::Canonical {
1190 prefix_bytes: u8::try_from(prefix_bytes)
1191 .expect("registration prefix is bounded to 124 bytes"),
1192 })
1193}
1194
1195fn registration_prefix_bytes(layout: RegistrationWireLayout) -> Result<usize, CodecError> {
1196 match layout {
1197 RegistrationWireLayout::Alternate32 => Ok(REGISTER_ALTERNATE_BYTES),
1198 RegistrationWireLayout::Canonical { prefix_bytes } => {
1199 let prefix_bytes = usize::from(prefix_bytes);
1200 if valid_registration_canonical_prefix(prefix_bytes) {
1201 Ok(prefix_bytes)
1202 } else {
1203 Err(CodecError::InvalidValue {
1204 message_id: wire_id::REGISTER,
1205 field: "canonical registration prefix length",
1206 value: prefix_bytes as u64,
1207 })
1208 }
1209 }
1210 }
1211}
1212
1213fn validate_registration_fields(
1214 registration: &RegistrationMessage,
1215 wire: RegistrationWireDetails,
1216 prefix_bytes: usize,
1217) -> Result<(), CodecError> {
1218 let alternate = wire.layout == RegistrationWireLayout::Alternate32;
1219 let canonical_prefix = (!alternate).then_some(prefix_bytes);
1220 let omits = |field_end| canonical_prefix.is_none_or(|bytes| bytes < field_end);
1221 let incompatible_field = [
1222 (
1223 alternate && registration.reported_address.is_some(),
1224 "reported IPv4 address",
1225 ),
1226 (alternate && !registration.features.is_empty(), "features"),
1227 (omits(36) && wire.max_streams != 0, "maximum streams"),
1228 (omits(40) && wire.active_streams != 0, "active streams"),
1229 (
1230 omits(48) && wire.max_conferences != 0,
1231 "maximum conferences",
1232 ),
1233 (
1234 omits(52) && wire.active_conferences != 0,
1235 "active conferences",
1236 ),
1237 (
1238 omits(64) && wire.mac_address_and_padding.iter().any(|byte| *byte != 0),
1239 "MAC address and padding",
1240 ),
1241 (
1242 omits(68) && wire.ipv4_address_scope != 0,
1243 "IPv4 address scope",
1244 ),
1245 (omits(72) && wire.max_lines != 0, "maximum lines"),
1246 (
1247 omits(88) && registration.reported_ipv6_address.is_some(),
1248 "reported IPv6 address",
1249 ),
1250 (
1251 omits(92) && wire.ipv6_address_scope != 0,
1252 "IPv6 address scope",
1253 ),
1254 (omits(124) && !registration.firmware.is_empty(), "firmware"),
1255 (
1256 omits(124) && !registration.configuration_version_stamp.is_empty(),
1257 "configuration version stamp",
1258 ),
1259 ]
1260 .into_iter()
1261 .find_map(|(incompatible, field)| incompatible.then_some(field));
1262 if let Some(field) = incompatible_field {
1263 return Err(CodecError::InvalidValue {
1264 message_id: wire_id::REGISTER,
1265 field,
1266 value: prefix_bytes as u64,
1267 });
1268 }
1269 if !alternate
1270 && prefix_bytes < 44
1271 && (registration.advertised_protocol.is_some() || !registration.features.is_empty())
1272 {
1273 return Err(CodecError::InvalidValue {
1274 message_id: wire_id::REGISTER,
1275 field: "protocol fields require a 44-byte registration prefix",
1276 value: prefix_bytes as u64,
1277 });
1278 }
1279 if !alternate && prefix_bytes >= 52 && registration.advertised_protocol.is_none() {
1280 return Err(CodecError::InvalidValue {
1281 message_id: wire_id::REGISTER,
1282 field: "registration protocol is absent from a layout that carries it",
1283 value: prefix_bytes as u64,
1284 });
1285 }
1286 Ok(())
1287}
1288
1289words!(WireKeypadButtonLegacy { button });
1290
1291#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1292#[brw(little)]
1293struct WireKeypadButtonWithCall {
1294 base: WireKeypadButtonLegacy,
1295 line_instance: u32,
1296 call_reference: u32,
1297}
1298
1299#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1300#[brw(little)]
1301struct WireKeypadButton {
1302 base: WireKeypadButtonWithCall,
1303 keypad_union: u32,
1304 reserved: u32,
1305}
1306
1307#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1308#[brw(little)]
1309struct WireEnblocWithoutLine {
1310 called_party: WireFixedText<24>,
1311}
1312
1313#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1314#[brw(little)]
1315struct WireEnblocWithLine<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1316 called_party: WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>,
1317 line_instance: u32,
1318}
1319
1320#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1321enum EnblocWireLayout {
1322 WithoutLine,
1323 Line24,
1324 Line25Packed,
1325 Line25Aligned,
1326 Line25Candidates,
1327}
1328
1329impl EnblocWireLayout {
1330 fn select(protocol: u32, payload_bytes: usize, message_id: u32) -> Result<Self, CodecError> {
1331 match (protocol, payload_bytes) {
1332 (..=16, 24) => Ok(Self::WithoutLine),
1333 (..=18, 28) => Ok(Self::Line24),
1334 (19.., 29..=31) => Ok(Self::Line25Packed),
1335 (19.., 32) => Ok(Self::Line25Candidates),
1336 _ => Err(CodecError::InvalidLength(message_id)),
1337 }
1338 }
1339
1340 const fn canonical(protocol: u32) -> Self {
1341 match protocol {
1342 ..=16 => Self::WithoutLine,
1343 17..=18 => Self::Line24,
1344 19.. => Self::Line25Aligned,
1345 }
1346 }
1347}
1348
1349#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1350#[brw(little)]
1351struct WireOffHookWithCallingParty<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize> {
1352 calling_party_number: WireFixedText<TEXT_BYTES>,
1353 voice_mailbox: WireFixedText<TEXT_BYTES>,
1354 alignment: [u8; ALIGNMENT_BYTES],
1355 line_instance: u32,
1356}
1357
1358words!(WireStimulus {
1359 stimulus,
1360 instance,
1361 call_reference,
1362 status
1363});
1364
1365const CAPABILITIES_RESPONSE_STANDARD_ENTRIES: usize = 18;
1366const CAPABILITIES_RESPONSE_EXTENDED_ENTRIES: usize = 24;
1367const CAPABILITIES_RESPONSE_MAX_ENTRIES: usize = CAPABILITIES_RESPONSE_EXTENDED_ENTRIES;
1368
1369#[derive(BinRead, BinWrite, Clone, Copy, Debug, Default, Eq, PartialEq)]
1370#[brw(little)]
1371struct WireMediaCapability {
1372 codec: u32,
1373 max_frames_per_packet: u32,
1374 codec_parameters: [u8; 8],
1375}
1376
1377#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1378enum CapabilitiesResponseWireLayout {
1379 Standard,
1380 Extended,
1381}
1382
1383impl CapabilitiesResponseWireLayout {
1384 const fn entries(self) -> usize {
1385 match self {
1386 Self::Standard => CAPABILITIES_RESPONSE_STANDARD_ENTRIES,
1387 Self::Extended => CAPABILITIES_RESPONSE_EXTENDED_ENTRIES,
1388 }
1389 }
1390
1391 const fn payload_bytes(self) -> usize {
1392 4 + self.entries() * 16
1393 }
1394}
1395
1396#[derive(BinRead, BinWrite, Clone, Copy, Debug, Default, Eq, PartialEq)]
1397#[brw(little)]
1398struct WireCallCountLineData {
1399 max_calls: u16,
1400 busy_trigger: u16,
1401}
1402
1403#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
1404#[brw(little)]
1405struct WireCallCountResponse {
1406 total_configured_lines: u32,
1407 starting_line_instance: u32,
1408 line_data_entries: u32,
1409 line_data: [WireCallCountLineData; CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES],
1410}
1411
1412#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1413#[brw(little)]
1414struct WireMediaPortList {
1415 count: u32,
1416 ports: [u32; MEDIA_PORT_LIST_MAX_PORTS],
1417}
1418
1419#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1420#[brw(little)]
1421struct WireAlarmBase {
1422 severity: u32,
1423 text: WireFixedText<80>,
1424}
1425
1426#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1427#[brw(little)]
1428struct WireAlarm {
1429 base: WireAlarmBase,
1430 parameter_1: u32,
1431 parameter_2: u32,
1432}
1433
1434#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1435#[brw(little)]
1436struct WireLocationInfo {
1437 xml: WireFixedText<2401>,
1438 alignment: [u8; 3],
1439}
1440
1441#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1442#[brw(little)]
1443struct WireReceiveChannelAck<Address: WireIpAddress> {
1444 status: u32,
1445 address: Address,
1446 port: u32,
1447 passthrough_party_id: u32,
1448 call_reference: u32,
1449}
1450
1451type WireOpenReceiveAckV3 = WireReceiveChannelAck<WireIpv4Address>;
1452type WireOpenReceiveAckV17 = WireReceiveChannelAck<WireExtendedAddress>;
1453
1454words!(WireSoftKeyEvent {
1455 event,
1456 line_instance,
1457 call_reference
1458});
1459
1460#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1461#[brw(little)]
1462struct WireRegisterToken {
1463 device_id: WireFixedText<16>,
1464 device_instance: u32,
1465 ipv4_address: [u8; 4],
1466 device_type: u32,
1467 ipv6_address: [u8; 16],
1468 flags: u32,
1469}
1470
1471#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1472#[brw(little)]
1473struct WireSpcpRegisterToken {
1474 device_id: WireFixedText<16>,
1475 reserved: u32,
1476 device_instance: u32,
1477 ipv4_address: u32,
1478 device_type: u32,
1479 max_streams: u32,
1480}
1481
1482words!(WireStopMediaReception {
1483 conference_id,
1484 passthrough_party_id
1485});
1486
1487words!(WireMediaResourceNotification {
1488 device_type,
1489 in_service_streams,
1490 max_streams_per_conference,
1491 out_of_service_streams
1492});
1493words!(WireAccessoryStatus { accessory, state });
1494words!(WireDtmfToneControl {
1495 tone,
1496 conference_id,
1497 passthrough_party_id
1498});
1499words!(WireDtmfPayloadIdentity {
1500 payload_type,
1501 conference_id,
1502 passthrough_party_id
1503});
1504words!(WireDtmfPayloadRequest {
1505 payload_type,
1506 conference_id,
1507 passthrough_party_id,
1508 dtmf_type
1509});
1510#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1511#[brw(little)]
1512struct WireMediaFailureDetection {
1513 conference_id: u32,
1514 passthrough_party_id: u32,
1515 packet_millis: u32,
1516 codec: u32,
1517 echo_cancellation: u32,
1518 codec_qualifier: [u8; 4],
1519 call_reference: u32,
1520}
1521words!(WireMultimediaStreamControl {
1522 conference_id,
1523 passthrough_party_id,
1524 call_reference,
1525 port_handling_flag
1526});
1527words!(WireVideoFlowControl {
1528 conference_id,
1529 passthrough_party_id,
1530 call_reference,
1531 maximum_bit_rate
1532});
1533words!(WireVideoDisplayCommand {
1534 conference_id,
1535 call_reference,
1536 layout_id
1537});
1538
1539type WireOpenMultimediaAckPre17 = WireReceiveChannelAck<WireIpv4Address>;
1540type WireOpenMultimediaAckFrom17 = WireReceiveChannelAck<WireExtendedAddress>;
1541type WireStartMultimediaAckPre17 = WireStartMediaAck<WireIpv4Address>;
1542type WireStartMultimediaAckFrom17 = WireStartMediaAck<WireExtendedAddress>;
1543
1544#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1545#[brw(little)]
1546struct WireSessionTransmission<Address: WireIpAddress> {
1547 remote_address: Address,
1548 session_type: u32,
1549}
1550
1551type WireSessionTransmissionPre17 = WireSessionTransmission<WireIpv4Address>;
1552type WireSessionTransmissionFrom17 = WireSessionTransmission<WireExtendedAddress>;
1553
1554#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1555#[brw(little)]
1556struct WireMultimediaPayloadDescriptor {
1557 payload_rfc_number: u32,
1558 payload_type: u32,
1559}
1560
1561impl From<MultimediaPayloadDescriptor> for WireMultimediaPayloadDescriptor {
1562 fn from(value: MultimediaPayloadDescriptor) -> Self {
1563 Self {
1564 payload_rfc_number: value.rfc_number(),
1565 payload_type: value.payload_number().into(),
1566 }
1567 }
1568}
1569
1570#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1571#[brw(little)]
1572struct WireOpenMultimediaV11 {
1573 conference_id: u32,
1574 passthrough_party_id: u32,
1575 compression_type: u32,
1576 line_instance: u32,
1577 call_reference: u32,
1578 payload_type: WireMultimediaPayloadDescriptor,
1579 conference_creator: u32,
1580 capability: [u8; MULTIMEDIA_CAPABILITY_BYTES],
1581 encryption: WireEncryptionInfo,
1582 stream_passthrough_id: u32,
1583 associated_stream_id: u32,
1584}
1585
1586#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1587#[brw(little)]
1588struct WireOpenMultimediaAddressed<Address: WireIpAddress> {
1589 base: WireOpenMultimediaV11,
1590 source_address: Address,
1591 source_port: u32,
1592}
1593
1594type WireOpenMultimediaV12 = WireOpenMultimediaAddressed<WireIpv4Address>;
1595
1596#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1597#[brw(little)]
1598struct WireOpenMultimediaV17 {
1599 base: WireOpenMultimediaAddressed<WireExtendedAddress>,
1600 requested_address_type: u32,
1601}
1602
1603#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1604#[brw(little)]
1605struct WireStartMultimedia<Address: WireIpAddress> {
1606 conference_id: u32,
1607 passthrough_party_id: u32,
1608 compression_type: u32,
1609 remote_address: Address,
1610 remote_port: u32,
1611 call_reference: u32,
1612 payload_type: WireMultimediaPayloadDescriptor,
1613 dscp: u32,
1614 capability: [u8; MULTIMEDIA_CAPABILITY_BYTES],
1615 encryption: WireEncryptionInfo,
1616 stream_passthrough_id: u32,
1617 associated_stream_id: u32,
1618}
1619
1620type WireStartMultimediaPre17 = WireStartMultimedia<WireIpv4Address>;
1621type WireStartMultimediaFrom17 = WireStartMultimedia<WireExtendedAddress>;
1622
1623#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1624#[brw(little)]
1625struct WireMiscellaneousCommand {
1626 conference_id: u32,
1627 passthrough_party_id: u32,
1628 call_reference: u32,
1629 command: u32,
1630 data: [u8; 36],
1631}
1632
1633#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1634#[brw(little)]
1635struct WireExtensionDeviceCapabilities {
1636 unknown_1: u32,
1637 unknown_2: u32,
1638 unknown_3: u32,
1639 description: WireFixedText<152>,
1640}
1641
1642#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1643#[brw(little)]
1644struct WireMediaFailure<Address: WireIpAddress> {
1645 conference_id: u32,
1646 passthrough_party_id: u32,
1647 address: Address,
1648 port: u32,
1649 call_reference: u32,
1650}
1651
1652type WireMediaFailureV3 = WireMediaFailure<WireIpv4Address>;
1653type WireMediaFailureV17 = WireMediaFailure<WireExtendedAddress>;
1654
1655#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
1656#[brw(little)]
1657struct WireUserDataHeader {
1658 application_id: u32,
1659 line_instance: u32,
1660 call_reference: u32,
1661 transaction_id: u32,
1662 data_length: u32,
1663}
1664
1665#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
1666#[brw(little)]
1667struct WireUserData {
1668 header: WireUserDataHeader,
1669 #[br(count = header.data_length)]
1670 data: Vec<u8>,
1671}
1672
1673#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
1674#[brw(little)]
1675struct WireUserDataV1 {
1676 header: WireUserDataHeader,
1677 sequence_flag: u32,
1678 display_priority: u32,
1679 conference_id: u32,
1680 application_instance_id: u32,
1681 routing: u32,
1682 #[br(count = header.data_length)]
1683 data: Vec<u8>,
1684}
1685
1686#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1687#[brw(little)]
1688struct WirePortResponse<Address: WireIpAddress> {
1689 conference_id: u32,
1690 call_reference: u32,
1691 passthrough_party_id: u32,
1692 address: Address,
1693 rtp_port: u32,
1694 rtcp_port: u32,
1695}
1696
1697type WirePortResponseV3 = WirePortResponse<WireIpv4Address>;
1698
1699#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1700#[brw(little)]
1701struct WirePortResponseV20 {
1702 base: WirePortResponse<WireExtendedAddress>,
1703 media_type: u32,
1704}
1705
1706#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1707#[brw(little)]
1708struct WireSubscriptionRequest {
1709 transaction_id: u32,
1710 feature_id: u32,
1711 timer_seconds: u32,
1712 subscription_id: WireFixedText<256>,
1713}
1714
1715#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1716#[brw(little)]
1717struct WireConnectionStatisticsCounters {
1718 packets_sent: u32,
1719 octets_sent: u32,
1720 packets_received: u32,
1721 octets_received: u32,
1722 packets_lost: u32,
1723 jitter_millis: u32,
1724 latency_millis: u32,
1725}
1726
1727#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1728#[brw(little)]
1729struct WireConnectionStatisticsTail {
1730 counters: WireConnectionStatisticsCounters,
1731 quality_size: u32,
1732}
1733
1734trait WireStatisticsProcessing:
1735 for<'a> BinRead<Args<'a> = ()>
1736 + for<'a> BinWrite<Args<'a> = ()>
1737 + Clone
1738 + Copy
1739 + std::fmt::Debug
1740 + Eq
1741 + PartialEq
1742 + 'static
1743{
1744 fn to_wire(self) -> u32;
1745}
1746
1747impl WireStatisticsProcessing for u32 {
1748 fn to_wire(self) -> u32 {
1749 self
1750 }
1751}
1752
1753impl WireStatisticsProcessing for u8 {
1754 fn to_wire(self) -> u32 {
1755 u32::from(self)
1756 }
1757}
1758
1759#[derive(BinRead, BinWrite, Clone, Debug, Eq, PartialEq)]
1760#[brw(little)]
1761struct WireConnectionStatistics<
1762 const TEXT_BYTES: usize,
1763 const ALIGNMENT_BYTES: usize,
1764 Processing: WireStatisticsProcessing,
1765> {
1766 directory_number: WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>,
1767 call_reference: u32,
1768 processing: Processing,
1769 statistics: WireConnectionStatisticsTail,
1770 #[br(count = statistics.quality_size)]
1771 quality: Vec<u8>,
1772}
1773
1774type WireConnectionStatisticsV3 = WireConnectionStatistics<24, 0, u32>;
1775type WireConnectionStatisticsV19 = WireConnectionStatistics<25, 3, u32>;
1776
1777#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1778#[brw(little)]
1779struct WireConnectionStatisticsPrefix<
1780 const TEXT_BYTES: usize,
1781 const ALIGNMENT_BYTES: usize,
1782 Processing: WireStatisticsProcessing,
1783> {
1784 directory_number: WireAlignedText<TEXT_BYTES, ALIGNMENT_BYTES>,
1785 call_reference: u32,
1786 processing: Processing,
1787 statistics: WireConnectionStatisticsTail,
1788}
1789
1790type WireConnectionStatisticsV3Prefix = WireConnectionStatisticsPrefix<24, 0, u32>;
1791type WireConnectionStatisticsV19Prefix = WireConnectionStatisticsPrefix<25, 3, u32>;
1792
1793#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1794#[brw(little)]
1795struct WireConnectionStatisticsPackedBase {
1796 directory_number: WireAlignedText<28, 0>,
1797 call_reference: u32,
1798 processing: u8,
1799 counters: WireConnectionStatisticsCounters,
1800}
1801
1802#[derive(BinRead, BinWrite, Clone, Copy, Debug, Eq, PartialEq)]
1803#[brw(little)]
1804struct WireConnectionStatisticsPackedPrefix {
1805 base: WireConnectionStatisticsPackedBase,
1806 quality_size: u32,
1807}
1808
1809fn validate_enbloc_line_instance(line_instance: u32, message_id: u32) -> Result<(), CodecError> {
1810 if line_instance <= MAX_STATION_BUTTON_INSTANCE {
1811 Ok(())
1812 } else {
1813 Err(CodecError::InvalidValue {
1814 message_id,
1815 field: "line instance",
1816 value: u64::from(line_instance),
1817 })
1818 }
1819}
1820
1821fn enbloc_from_wire<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
1822 payload: &[u8],
1823 message_id: u32,
1824) -> Result<ClientMessage, CodecError> {
1825 let value: WireEnblocWithLine<TEXT_BYTES, ALIGNMENT_BYTES> = decode(message_id, payload)?;
1826 value.called_party.validate(message_id)?;
1827 validate_enbloc_line_instance(value.line_instance, message_id)?;
1828 Ok(ClientMessage::EnblocCall {
1829 called_party: value.called_party.text()?,
1830 line_instance: value.line_instance,
1831 })
1832}
1833
1834fn enbloc_from_packed_wire(payload: &[u8], message_id: u32) -> Result<ClientMessage, CodecError> {
1835 let value: WireEnblocWithLine<25, 0> = decode_prefix(message_id, payload)?;
1836 value.called_party.validate(message_id)?;
1837 validate_zero_payload(&payload[29..], message_id, payload.len() - 29)?;
1838 validate_enbloc_line_instance(value.line_instance, message_id)?;
1839 Ok(ClientMessage::EnblocCall {
1840 called_party: value.called_party.text()?,
1841 line_instance: value.line_instance,
1842 })
1843}
1844
1845fn select_enbloc_candidate(
1846 packed: Result<ClientMessage, CodecError>,
1847 aligned: Result<ClientMessage, CodecError>,
1848 payload_bytes: usize,
1849 message_id: u32,
1850) -> Result<ClientMessage, CodecError> {
1851 match (packed, aligned) {
1852 (Ok(packed), Ok(aligned)) if packed == aligned => Ok(packed),
1853 (Ok(_), Ok(_)) => Err(CodecError::InvalidValue {
1854 message_id,
1855 field: "conflicting Enbloc layout",
1856 value: payload_bytes as u64,
1857 }),
1858 (Ok(message), Err(_)) | (Err(_), Ok(message)) => Ok(message),
1859 (Err(error), Err(_)) => Err(error),
1860 }
1861}
1862
1863fn decode_enbloc(
1864 payload: &[u8],
1865 protocol: u32,
1866 message_id: u32,
1867) -> Result<ClientMessage, CodecError> {
1868 match EnblocWireLayout::select(protocol, payload.len(), message_id)? {
1869 EnblocWireLayout::WithoutLine => {
1870 let value: WireEnblocWithoutLine = decode(message_id, payload)?;
1871 Ok(ClientMessage::EnblocCall {
1872 called_party: value.called_party.text()?,
1873 line_instance: 0,
1874 })
1875 }
1876 EnblocWireLayout::Line24 => enbloc_from_wire::<24, 0>(payload, message_id),
1877 EnblocWireLayout::Line25Packed => enbloc_from_packed_wire(payload, message_id),
1878 EnblocWireLayout::Line25Aligned => enbloc_from_wire::<25, 3>(payload, message_id),
1879 EnblocWireLayout::Line25Candidates => select_enbloc_candidate(
1880 enbloc_from_packed_wire(payload, message_id),
1881 enbloc_from_wire::<25, 3>(payload, message_id),
1882 payload.len(),
1883 message_id,
1884 ),
1885 }
1886}
1887
1888fn enbloc_to_wire<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
1889 called_party: &str,
1890 line_instance: u32,
1891) -> Result<Vec<u8>, CodecError> {
1892 validate_enbloc_line_instance(line_instance, wire_id::ENBLOC_CALL)?;
1893 encode(
1894 wire_id::ENBLOC_CALL,
1895 &WireEnblocWithLine::<TEXT_BYTES, ALIGNMENT_BYTES> {
1896 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", called_party)?,
1897 line_instance,
1898 },
1899 )
1900}
1901
1902fn encode_enbloc(
1903 called_party: &str,
1904 line_instance: u32,
1905 protocol: u32,
1906) -> Result<Vec<u8>, CodecError> {
1907 match EnblocWireLayout::canonical(protocol) {
1908 EnblocWireLayout::WithoutLine if line_instance == 0 => encode(
1909 wire_id::ENBLOC_CALL,
1910 &WireEnblocWithoutLine {
1911 called_party: WireFixedText::new(
1912 wire_id::ENBLOC_CALL,
1913 "called party",
1914 called_party,
1915 )?,
1916 },
1917 ),
1918 EnblocWireLayout::WithoutLine => Err(CodecError::InvalidValue {
1919 message_id: wire_id::ENBLOC_CALL,
1920 field: "line instance is unavailable before protocol 17",
1921 value: u64::from(line_instance),
1922 }),
1923 EnblocWireLayout::Line24 => enbloc_to_wire::<24, 0>(called_party, line_instance),
1924 EnblocWireLayout::Line25Packed => enbloc_to_wire::<25, 0>(called_party, line_instance),
1925 EnblocWireLayout::Line25Aligned => enbloc_to_wire::<25, 3>(called_party, line_instance),
1926 EnblocWireLayout::Line25Candidates => unreachable!("candidate selection is decode-only"),
1927 }
1928}
1929
1930fn decode_on_hook(payload: &[u8], message_id: u32) -> Result<ClientMessage, CodecError> {
1931 match payload.len() {
1932 0 => Ok(ClientMessage::OnHook {
1933 line_instance: 0,
1934 call_reference: 0,
1935 }),
1936 8 => {
1937 let value: WireLineCall = decode(message_id, payload)?;
1938 Ok(ClientMessage::OnHook {
1939 line_instance: value.line_instance,
1940 call_reference: value.call_reference,
1941 })
1942 }
1943 _ => Err(CodecError::InvalidLength(message_id)),
1944 }
1945}
1946
1947fn decode_capabilities_response(
1948 payload: &[u8],
1949 message_id: u32,
1950) -> Result<ClientMessage, CodecError> {
1951 if payload.len() < 4 {
1952 return Err(CodecError::Truncated {
1953 message_id,
1954 needed: 4,
1955 actual: payload.len(),
1956 });
1957 }
1958 let count = usize_from_wire(
1959 message_id,
1960 "audio capabilities",
1961 decode_prefix::<WireOneWord>(message_id, payload)?.value,
1962 )?;
1963 if count > CAPABILITIES_RESPONSE_MAX_ENTRIES {
1964 return Err(CodecError::CountTooLarge {
1965 message_id,
1966 field: "audio capabilities",
1967 count,
1968 maximum: CAPABILITIES_RESPONSE_MAX_ENTRIES,
1969 });
1970 }
1971 let counted_bytes = 4 + count * 16;
1972 let reservoir_entries = match payload.len() {
1973 bytes if bytes == counted_bytes => None,
1974 bytes if bytes == CapabilitiesResponseWireLayout::Standard.payload_bytes() => {
1975 Some(CapabilitiesResponseWireLayout::Standard.entries())
1976 }
1977 bytes if bytes == CapabilitiesResponseWireLayout::Extended.payload_bytes() => {
1978 Some(CapabilitiesResponseWireLayout::Extended.entries())
1979 }
1980 actual if actual < counted_bytes => {
1981 return Err(CodecError::Truncated {
1982 message_id,
1983 needed: counted_bytes,
1984 actual,
1985 });
1986 }
1987 actual => {
1988 return Err(CodecError::TrailingBytes {
1989 message_id,
1990 count: actual - counted_bytes,
1991 });
1992 }
1993 };
1994 if let Some(entries) = reservoir_entries
1995 && count > entries
1996 {
1997 return Err(CodecError::CountTooLarge {
1998 message_id,
1999 field: "audio capabilities",
2000 count,
2001 maximum: entries,
2002 });
2003 }
2004
2005 let (entries, remainder) = payload[4..].as_chunks::<16>();
2006 if !remainder.is_empty() {
2007 return Err(CodecError::InvalidLength(message_id));
2008 }
2009 let mut capabilities = Vec::with_capacity(count);
2010 for entry in entries.iter().take(count) {
2011 let capability: WireMediaCapability = decode(message_id, entry)?;
2012 capabilities.push(MediaCapability {
2013 codec: Codec::from(capability.codec),
2014 max_packet_ms: capability.max_frames_per_packet,
2015 codec_parameters: capability.codec_parameters,
2016 });
2017 }
2018 Ok(ClientMessage::CapabilitiesResponse(capabilities))
2019}
2020
2021fn encode_capabilities_response(capabilities: &[MediaCapability]) -> Result<Vec<u8>, CodecError> {
2022 let layout = match capabilities.len() {
2023 0..=CAPABILITIES_RESPONSE_STANDARD_ENTRIES => CapabilitiesResponseWireLayout::Standard,
2024 19..=CAPABILITIES_RESPONSE_EXTENDED_ENTRIES => CapabilitiesResponseWireLayout::Extended,
2025 count => {
2026 return Err(CodecError::CountTooLarge {
2027 message_id: wire_id::CAPABILITIES_RES,
2028 field: "audio capabilities",
2029 count,
2030 maximum: CAPABILITIES_RESPONSE_MAX_ENTRIES,
2031 });
2032 }
2033 };
2034 let mut payload = encode(
2035 wire_id::CAPABILITIES_RES,
2036 &WireOneWord {
2037 value: wire_count(
2038 wire_id::CAPABILITIES_RES,
2039 "audio capabilities",
2040 capabilities.len(),
2041 )?,
2042 },
2043 )?;
2044 for capability in capabilities {
2045 payload.extend_from_slice(&encode(
2046 wire_id::CAPABILITIES_RES,
2047 &WireMediaCapability {
2048 codec: capability.codec.wire_value(),
2049 max_frames_per_packet: capability.max_packet_ms,
2050 codec_parameters: capability.codec_parameters,
2051 },
2052 )?);
2053 }
2054 payload.resize(layout.payload_bytes(), 0);
2055 Ok(payload)
2056}
2057
2058fn decode_off_hook_with_calling_party<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2059 payload: &[u8],
2060 message_id: u32,
2061) -> Result<ClientMessage, CodecError> {
2062 let value: WireOffHookWithCallingParty<TEXT_BYTES, ALIGNMENT_BYTES> =
2063 decode(message_id, payload)?;
2064 validate_zero_payload(&value.alignment, message_id, ALIGNMENT_BYTES)?;
2065 Ok(ClientMessage::OffHookWithCallingParty {
2066 calling_party_number: value.calling_party_number.text()?,
2067 voice_mailbox: value.voice_mailbox.text()?,
2068 line_instance: value.line_instance,
2069 })
2070}
2071
2072fn encode_off_hook_with_calling_party<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2073 calling_party_number: &str,
2074 voice_mailbox: &str,
2075 line_instance: u32,
2076) -> Result<Vec<u8>, CodecError> {
2077 encode(
2078 wire_id::OFF_HOOK_WITH_CALLING_PARTY,
2079 &WireOffHookWithCallingParty::<TEXT_BYTES, ALIGNMENT_BYTES> {
2080 calling_party_number: WireFixedText::new(
2081 wire_id::OFF_HOOK_WITH_CALLING_PARTY,
2082 "calling party number",
2083 calling_party_number,
2084 )?,
2085 voice_mailbox: WireFixedText::new(
2086 wire_id::OFF_HOOK_WITH_CALLING_PARTY,
2087 "voice mailbox",
2088 voice_mailbox,
2089 )?,
2090 alignment: [0; ALIGNMENT_BYTES],
2091 line_instance,
2092 },
2093 )
2094}
2095
2096fn decode_connection_statistics_request<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2097 payload: &[u8],
2098 message_id: u32,
2099) -> Result<ServerMessage, CodecError> {
2100 let value: WireConnectionStatisticsRequest<TEXT_BYTES, ALIGNMENT_BYTES> =
2101 decode(message_id, payload)?;
2102 value.directory_number.validate(message_id)?;
2103 Ok(ServerMessage::ConnectionStatisticsRequest {
2104 directory_number: value.directory_number.text()?,
2105 call_reference: value.call_reference,
2106 processing: StatisticsProcessing::from(value.processing),
2107 })
2108}
2109
2110fn encode_connection_statistics_request<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2111 directory_number: &str,
2112 call_reference: u32,
2113 processing: StatisticsProcessing,
2114) -> Result<Vec<u8>, CodecError> {
2115 encode(
2116 wire_id::CONNECTION_STATISTICS_REQ,
2117 &WireConnectionStatisticsRequest::<TEXT_BYTES, ALIGNMENT_BYTES> {
2118 directory_number: WireAlignedText::new(
2119 wire_id::CONNECTION_STATISTICS_REQ,
2120 "directory number",
2121 directory_number,
2122 )?,
2123 call_reference,
2124 processing: processing.wire_value(),
2125 },
2126 )
2127}
2128
2129fn decode_forward_status<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2130 payload: &[u8],
2131 message_id: u32,
2132) -> Result<ServerMessage, CodecError> {
2133 let value: WireForwardStatus<TEXT_BYTES, ALIGNMENT_BYTES> =
2134 decode_zero_padded(message_id, payload)?;
2135 value.all.number.validate(message_id)?;
2136 value.busy.number.validate(message_id)?;
2137 value.no_answer.number.validate(message_id)?;
2138 Ok(ServerMessage::ForwardStatus {
2139 forward_all: (value.all.active != 0)
2140 .then(|| value.all.number.text())
2141 .transpose()?,
2142 forward_busy: (value.busy.active != 0)
2143 .then(|| value.busy.number.text())
2144 .transpose()?,
2145 forward_no_answer: (value.no_answer.active != 0)
2146 .then(|| value.no_answer.number.text())
2147 .transpose()?,
2148 line_instance: value.line_instance,
2149 })
2150}
2151
2152fn encode_forward_status<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2153 line_instance: u32,
2154 forward_all: Option<&str>,
2155 forward_busy: Option<&str>,
2156 forward_no_answer: Option<&str>,
2157) -> Result<Vec<u8>, CodecError> {
2158 let target = |value: Option<&str>| -> Result<_, CodecError> {
2159 Ok(WireForwardTarget {
2160 active: u32::from(value.is_some()),
2161 number: WireAlignedText::new(
2162 wire_id::FORWARD_STAT,
2163 "forward number",
2164 value.unwrap_or(""),
2165 )?,
2166 })
2167 };
2168 encode(
2169 wire_id::FORWARD_STAT,
2170 &WireForwardStatus::<TEXT_BYTES, ALIGNMENT_BYTES> {
2171 active: u32::from(
2172 forward_all.is_some() || forward_busy.is_some() || forward_no_answer.is_some(),
2173 ),
2174 line_instance,
2175 all: target(forward_all)?,
2176 busy: target(forward_busy)?,
2177 no_answer: target(forward_no_answer)?,
2178 },
2179 )
2180}
2181
2182fn decode_dialed_number<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2183 payload: &[u8],
2184 message_id: u32,
2185) -> Result<ServerMessage, CodecError> {
2186 let value: WireDialedNumber<TEXT_BYTES, ALIGNMENT_BYTES> =
2187 decode_zero_padded(message_id, payload)?;
2188 value.number.validate(message_id)?;
2189 Ok(ServerMessage::DialedNumber {
2190 number: value.number.text()?,
2191 line_instance: value.line_instance,
2192 call_reference: value.call_reference,
2193 })
2194}
2195
2196fn encode_dialed_number<const TEXT_BYTES: usize, const ALIGNMENT_BYTES: usize>(
2197 number: &str,
2198 line_instance: u32,
2199 call_reference: u32,
2200) -> Result<Vec<u8>, CodecError> {
2201 encode(
2202 wire_id::DIALED_NUMBER,
2203 &WireDialedNumber::<TEXT_BYTES, ALIGNMENT_BYTES> {
2204 number: WireAlignedText::new(wire_id::DIALED_NUMBER, "dialed number", number)?,
2205 line_instance,
2206 call_reference,
2207 },
2208 )
2209}
2210
2211impl ClientMessage {
2212 pub fn decode_with_version(
2217 frame: Frame,
2218 protocol: ProtocolVersion,
2219 ) -> Result<Self, CodecError> {
2220 ensure_station_route(&frame, MessageRoute::StationToControl, "station-to-control")?;
2221 Self::decode_using_protocol(frame, protocol.wire())
2222 }
2223
2224 pub fn decode(frame: Frame) -> Result<Self, CodecError> {
2229 ensure_station_route(&frame, MessageRoute::StationToControl, "station-to-control")?;
2230 let protocol_version = frame.protocol_version;
2231 Self::decode_using_protocol(frame, protocol_version)
2232 }
2233
2234 fn decode_using_protocol(frame: Frame, protocol_version: u32) -> Result<Self, CodecError> {
2235 let p = &frame.payload;
2236 match frame.message_id {
2237 wire_id::KEEP_ALIVE => Ok(Self::KeepAlive),
2238 wire_id::REGISTER => {
2239 let layout = registration_layout(frame.message_id, p.len())?;
2240 let prefix_bytes = registration_prefix_bytes(layout)?;
2241 let mut canonical = [0; REGISTER_CANONICAL_BYTES];
2242 canonical[..prefix_bytes].copy_from_slice(&p[..prefix_bytes]);
2243 let value: WireRegister = decode(frame.message_id, &canonical)?;
2244 let alternate = layout == RegistrationWireLayout::Alternate32;
2245 if alternate && value.reported_address[1..].iter().any(|byte| *byte != 0) {
2246 return Err(CodecError::NonZeroPadding {
2247 message_id: frame.message_id,
2248 field: "alternate registration protocol",
2249 });
2250 }
2251 let reported_address = (!alternate)
2252 .then(|| Ipv4Addr::from(value.reported_address))
2253 .filter(|address| !address.is_unspecified());
2254 let reported_ipv6_address = if !alternate
2255 && prefix_bytes >= 88
2256 && value.ipv6_address.iter().any(|byte| *byte != 0)
2257 {
2258 Some(Ipv6Addr::from(value.ipv6_address))
2259 } else {
2260 None
2261 };
2262 let advertised_protocol = if alternate {
2263 Some(u32::from(value.reported_address[0])).filter(|version| *version != 0)
2264 } else if prefix_bytes >= 44 {
2265 Some(u32::from(value.protocol_features[0]))
2266 .filter(|version| *version != 0 || !matches!(prefix_bytes, 44 | 48))
2267 } else {
2268 None
2269 };
2270 Ok(Self::Register(RegistrationMessage {
2271 device_id: DeviceId::new(value.device_id.text()?)?,
2272 reported_address,
2273 reported_ipv6_address,
2274 device_type: DeviceType::from(value.device_type),
2275 advertised_protocol,
2276 features: if !alternate && prefix_bytes >= 44 {
2277 PhoneFeatures::from_bits_retain(
2278 u32::from_le_bytes(value.protocol_features) & !0xff,
2279 )
2280 } else {
2281 PhoneFeatures::empty()
2282 },
2283 firmware: if !alternate && prefix_bytes >= REGISTER_CANONICAL_BYTES {
2284 value.firmware.text()?
2285 } else {
2286 String::new()
2287 },
2288 configuration_version_stamp: BoundedBytes::try_from(
2289 p[REGISTER_CANONICAL_BYTES.min(p.len())..].to_vec(),
2290 )
2291 .expect("registration suffix length was bounded before allocation"),
2292 wire: Some(RegistrationWireDetails {
2293 layout,
2294 station_user_id: value.station_user_id,
2295 station_instance: value.station_instance,
2296 max_streams: value.max_streams,
2297 active_streams: value.active_streams,
2298 mac_address_and_padding: value.mac_address,
2299 max_conferences: value.max_conferences,
2300 active_conferences: value.active_conferences,
2301 ipv4_address_scope: value.ipv4_address_scope,
2302 max_lines: value.max_lines,
2303 ipv6_address_scope: value.ipv6_address_scope,
2304 }),
2305 }))
2306 }
2307 wire_id::IP_PORT => {
2308 let value: WireOneWord = decode(frame.message_id, p)?;
2309 Ok(Self::IpPort {
2310 rtp_port: decode_port(value.value, frame.message_id, "RTP port")?,
2311 })
2312 }
2313 wire_id::KEYPAD_BUTTON => {
2314 let (button, line_instance, call_reference, wire_layout) = match p.len() {
2315 4 => {
2316 let value: WireKeypadButtonLegacy = decode(frame.message_id, p)?;
2317 (
2318 value.button,
2319 0,
2320 0,
2321 Some(KeypadButtonWireLayout::LegacyButtonOnly),
2322 )
2323 }
2324 12 => {
2325 let value: WireKeypadButtonWithCall = decode(frame.message_id, p)?;
2326 (
2327 value.base.button,
2328 value.line_instance,
2329 value.call_reference,
2330 Some(KeypadButtonWireLayout::WithCallIdentity),
2331 )
2332 }
2333 20 => {
2334 let value: WireKeypadButton = decode(frame.message_id, p)?;
2335 if value.keypad_union != 0 || value.reserved != 0 {
2336 return Err(CodecError::InvalidValue {
2337 message_id: frame.message_id,
2338 field: "keypad reserved fields",
2339 value: 1,
2340 });
2341 }
2342 (
2343 value.base.base.button,
2344 value.base.line_instance,
2345 value.base.call_reference,
2346 None,
2347 )
2348 }
2349 _ => return Err(CodecError::InvalidLength(frame.message_id)),
2350 };
2351 Ok(Self::KeypadButton {
2352 button: Digit::from_keypad(button),
2353 line_instance,
2354 call_reference,
2355 wire_layout,
2356 })
2357 }
2358 wire_id::ENBLOC_CALL => decode_enbloc(p, protocol_version, frame.message_id),
2359 wire_id::STIMULUS => {
2360 let value: WireStimulus = decode(frame.message_id, p)?;
2361 Ok(Self::Stimulus {
2362 stimulus: Stimulus::from(value.stimulus),
2363 instance: value.instance,
2364 call_reference: value.call_reference,
2365 status: value.status,
2366 })
2367 }
2368 wire_id::OFF_HOOK => {
2369 let value: WireLineCall = decode(frame.message_id, p)?;
2370 Ok(Self::OffHook {
2371 line_instance: value.line_instance,
2372 call_reference: value.call_reference,
2373 })
2374 }
2375 wire_id::ON_HOOK => decode_on_hook(p, frame.message_id),
2376 wire_id::OFF_HOOK_WITH_CALLING_PARTY => match protocol_version {
2377 19.. => decode_off_hook_with_calling_party::<25, 2>(p, frame.message_id),
2378 _ => decode_off_hook_with_calling_party::<24, 0>(p, frame.message_id),
2379 },
2380 wire_id::LINE_STAT_REQ => {
2381 let value: WireOneWord = decode(frame.message_id, p)?;
2382 Ok(Self::LineStatRequest {
2383 line_instance: value.value,
2384 })
2385 }
2386 wire_id::CONFIG_STAT_REQ => Ok(Self::ConfigStatRequest),
2387 wire_id::TIME_DATE_REQ => Ok(Self::TimeDateRequest),
2388 wire_id::BUTTON_TEMPLATE_REQ => Ok(Self::ButtonTemplateRequest),
2389 wire_id::VERSION_REQ => Ok(Self::VersionRequest),
2390 wire_id::CAPABILITIES_RES => decode_capabilities_response(p, frame.message_id),
2391 wire_id::MEDIA_PORT_LIST => {
2392 let value: WireMediaPortList = decode(frame.message_id, p)?;
2393 let count = usize_from_wire(frame.message_id, "RTP ports", value.count)?;
2394 validate_media_port_count(frame.message_id, count)?;
2395 let rtp_ports = value.ports[..count]
2396 .iter()
2397 .copied()
2398 .map(|port| {
2399 u16::try_from(port).map_err(|_| CodecError::InvalidValue {
2400 message_id: frame.message_id,
2401 field: "RTP port",
2402 value: u64::from(port),
2403 })
2404 })
2405 .collect::<Result<Vec<_>, _>>()?;
2406 Ok(Self::MediaPortList(MediaPortList { rtp_ports }))
2407 }
2408 wire_id::UPDATE_CAPABILITIES => {
2409 let expanded_layout = CapabilityUpdateVariant::Version1ExpandedVideo;
2410 let variant = match (
2411 protocol_version,
2412 p.len() >= expanded_layout.minimum_payload_bytes(protocol_version),
2413 ) {
2414 (16.., true) => expanded_layout,
2415 _ => CapabilityUpdateVariant::Version1,
2416 };
2417 CapabilityUpdate::decode(variant, protocol_version, p).map(Self::CapabilitiesUpdate)
2418 }
2419 wire_id::UPDATE_CAPABILITIES_V2 => {
2420 CapabilityUpdate::decode(CapabilityUpdateVariant::Version2, protocol_version, p)
2421 .map(Self::CapabilitiesUpdate)
2422 }
2423 wire_id::UPDATE_CAPABILITIES_V3 => {
2424 CapabilityUpdate::decode(CapabilityUpdateVariant::Version3, protocol_version, p)
2425 .map(Self::CapabilitiesUpdate)
2426 }
2427 wire_id::OPEN_MULTIMEDIA_RECEIVE_CHANNEL_ACK => {
2428 decode_open_multimedia_ack(p, protocol_version, frame.message_id)
2429 .map(Self::OpenMultimediaReceiveChannelAck)
2430 }
2431 wire_id::SERVER_REQ => Ok(Self::ServerRequest),
2432 wire_id::ALARM => match p.len() {
2433 84 => {
2434 let value: WireAlarmBase = decode(frame.message_id, p)?;
2435 Ok(Self::Alarm {
2436 severity: AlarmSeverity::from(value.severity),
2437 text: value.text.text()?,
2438 parameters: None,
2439 })
2440 }
2441 92 => {
2442 let value: WireAlarm = decode(frame.message_id, p)?;
2443 Ok(Self::Alarm {
2444 severity: AlarmSeverity::from(value.base.severity),
2445 text: value.base.text.text()?,
2446 parameters: Some([value.parameter_1, value.parameter_2]),
2447 })
2448 }
2449 _ => Err(CodecError::InvalidLength(frame.message_id)),
2450 },
2451 wire_id::MULTICAST_MEDIA_RECEPTION_ACK => {
2452 validate_exact_payload(p, frame.message_id, 12)?;
2453 let value: WireMulticastReceptionAck = decode(frame.message_id, p)?;
2454 Ok(Self::MulticastMediaReceptionAck {
2455 status: MediaStatus::from(value.status),
2456 passthrough_party_id: value.passthrough_party_id.into(),
2457 call_reference: value.call_reference.into(),
2458 })
2459 }
2460 wire_id::OPEN_RECEIVE_CHANNEL_ACK => match protocol_version {
2461 17.. => {
2462 let value: WireOpenReceiveAckV17 = decode(frame.message_id, p)?;
2463 Ok(Self::OpenReceiveChannelAck {
2464 status: MediaStatus::from(value.status),
2465 address: value.address.to_ip(frame.message_id)?,
2466 port: decode_port(value.port, frame.message_id, "RTP port")?,
2467 passthrough_party_id: value.passthrough_party_id,
2468 call_reference: value.call_reference,
2469 })
2470 }
2471 _ => {
2472 let value: WireOpenReceiveAckV3 = decode(frame.message_id, p)?;
2473 Ok(Self::OpenReceiveChannelAck {
2474 status: MediaStatus::from(value.status),
2475 address: value.address.to_ip(frame.message_id)?,
2476 port: decode_port(value.port, frame.message_id, "RTP port")?,
2477 passthrough_party_id: value.passthrough_party_id,
2478 call_reference: value.call_reference,
2479 })
2480 }
2481 },
2482 wire_id::SOFT_KEY_SET_REQ => Ok(Self::SoftKeySetRequest),
2483 wire_id::SOFT_KEY_TEMPLATE_REQ => Ok(Self::SoftKeyTemplateRequest),
2484 wire_id::SOFT_KEY_EVENT => {
2485 let value: WireSoftKeyEvent = decode(frame.message_id, p)?;
2486 Ok(Self::SoftKeyEvent {
2487 event: value.event,
2488 line_instance: value.line_instance,
2489 call_reference: value.call_reference,
2490 })
2491 }
2492 wire_id::UNREGISTER => {
2493 let reason = if p.is_empty() {
2494 0
2495 } else {
2496 decode::<WireOneWord>(frame.message_id, p)?.value
2497 };
2498 Ok(Self::Unregister { reason })
2499 }
2500 wire_id::REGISTER_TOKEN_REQ => {
2501 let value: WireRegisterToken = decode(frame.message_id, p)?;
2502 let address = if value.ipv6_address.iter().any(|byte| *byte != 0) {
2503 IpAddr::V6(Ipv6Addr::from(value.ipv6_address))
2504 } else {
2505 IpAddr::V4(Ipv4Addr::from(value.ipv4_address))
2506 };
2507 Ok(Self::RegisterToken(RegisterTokenMessage {
2508 device_id: DeviceId::new(value.device_id.text()?)?,
2509 device_instance: value.device_instance,
2510 address,
2511 device_type: DeviceType::from(value.device_type),
2512 flags: value.flags,
2513 }))
2514 }
2515 wire_id::SPCP_REGISTER_TOKEN_REQ => {
2516 let value: WireSpcpRegisterToken = decode(frame.message_id, p)?;
2517 Ok(Self::SpcpRegisterToken(SpcpRegisterTokenMessage {
2518 device_id: DeviceId::new(value.device_id.text()?)?,
2519 device_instance: value.device_instance,
2520 address: Ipv4Addr::from(value.ipv4_address),
2521 device_type: DeviceType::from(value.device_type),
2522 max_streams: value.max_streams,
2523 }))
2524 }
2525 wire_id::HOOK_FLASH => {
2526 let value: WireLineCall = decode(frame.message_id, p)?;
2527 Ok(Self::HookFlash {
2528 line_instance: value.line_instance,
2529 call_reference: value.call_reference,
2530 })
2531 }
2532 wire_id::FORWARD_STAT_REQ => {
2533 let value: WireOneWord = decode(frame.message_id, p)?;
2534 Ok(Self::ForwardStatusRequest {
2535 line_instance: value.value,
2536 })
2537 }
2538 wire_id::SPEED_DIAL_STAT_REQ => {
2539 let value: WireOneWord = decode(frame.message_id, p)?;
2540 Ok(Self::SpeedDialStatusRequest {
2541 speed_dial_instance: value.value,
2542 })
2543 }
2544 wire_id::HEADSET_STATUS => {
2545 let value: WireOneWord = decode(frame.message_id, p)?;
2546 Ok(Self::HeadsetStatus {
2547 enabled: value.value == 1,
2548 })
2549 }
2550 wire_id::MEDIA_RESOURCE_NOTIFICATION => {
2551 let value: WireMediaResourceNotification = decode(frame.message_id, p)?;
2552 Ok(Self::MediaResourceNotification(MediaResourceNotification {
2553 device_type: DeviceType::from(value.device_type),
2554 in_service_streams: value.in_service_streams,
2555 max_streams_per_conference: value.max_streams_per_conference,
2556 out_of_service_streams: value.out_of_service_streams,
2557 }))
2558 }
2559 wire_id::ACCESSORY_STATUS => {
2560 let value: WireAccessoryStatus = decode(frame.message_id, p)?;
2561 Ok(Self::MediaPathEvent {
2562 path: MediaPathId::from(value.accessory),
2563 event: MediaPathEvent::from(value.state),
2564 })
2565 }
2566 wire_id::MEDIA_PATH_CAPABILITY => {
2567 let value: WireAccessoryStatus = decode(frame.message_id, p)?;
2568 Ok(Self::MediaPathCapability {
2569 path: MediaPathId::from(value.accessory),
2570 capability: MediaPathCapability::from(value.state),
2571 })
2572 }
2573 wire_id::REGISTER_AVAILABLE_LINES => {
2574 let lines = if p.len() >= std::mem::size_of::<u32>() {
2575 decode::<WireOneWord>(frame.message_id, p)?.value
2576 } else {
2577 0
2578 };
2579 Ok(Self::RegisterAvailableLines { lines })
2580 }
2581 wire_id::DEVICE_TO_USER_DATA => {
2582 decode_user_data(p, frame.message_id).map(Self::DeviceToUserData)
2583 }
2584 wire_id::DEVICE_TO_USER_DATA_RESPONSE => {
2585 decode_user_data(p, frame.message_id).map(Self::DeviceToUserDataResponse)
2586 }
2587 wire_id::DEVICE_TO_USER_DATA_V1 => {
2588 decode_user_data_v1(p, frame.message_id).map(Self::DeviceToUserDataV1)
2589 }
2590 wire_id::DEVICE_TO_USER_DATA_RESPONSE_V1 => {
2591 decode_user_data_v1(p, frame.message_id).map(Self::DeviceToUserDataResponseV1)
2592 }
2593 wire_id::PORT_RESPONSE => {
2594 decode_port_response(p, protocol_version, frame.message_id).map(Self::PortResponse)
2595 }
2596 wire_id::SUBSCRIPTION_STAT_REQ => {
2597 let value: WireSubscriptionRequest = decode(frame.message_id, p)?;
2598 Ok(Self::SubscriptionStatusRequest(SubscriptionRequest {
2599 transaction_id: value.transaction_id,
2600 feature_id: value.feature_id,
2601 timer_seconds: value.timer_seconds,
2602 subscription_id: value.subscription_id.text()?,
2603 }))
2604 }
2605 wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES => {
2606 let value: WireDtmfPayloadIdentity = decode(frame.message_id, p)?;
2607 Ok(Self::SubscribeDtmfPayloadResponse(
2608 dtmf_payload_identity_from_wire(value),
2609 ))
2610 }
2611 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_RES => {
2612 let value: WireDtmfPayloadIdentity = decode(frame.message_id, p)?;
2613 Ok(Self::UnsubscribeDtmfPayloadResponse(
2614 dtmf_payload_identity_from_wire(value),
2615 ))
2616 }
2617 wire_id::SERVICE_URL_STAT_REQ => {
2618 let value: WireOneWord = decode(frame.message_id, p)?;
2619 Ok(Self::ServiceUrlStatusRequest { index: value.value })
2620 }
2621 wire_id::FEATURE_STAT_REQ => {
2622 let value: WireFeatureStatusRequest = decode(frame.message_id, p)?;
2623 Ok(Self::FeatureStatusRequest {
2624 index: value.index,
2625 capabilities: value.capabilities,
2626 })
2627 }
2628 wire_id::MEDIA_TRANSMISSION_FAILURE => match protocol_version {
2629 17.. => {
2630 let value: WireMediaFailureV17 = decode(frame.message_id, p)?;
2631 Ok(Self::MediaTransmissionFailure {
2632 conference_id: value.conference_id,
2633 passthrough_party_id: value.passthrough_party_id,
2634 address: value.address.to_ip(frame.message_id)?,
2635 port: decode_port(value.port, frame.message_id, "RTP port")?,
2636 call_reference: value.call_reference,
2637 status: MediaStatus::UnspecifiedError,
2638 })
2639 }
2640 _ => {
2641 let value: WireMediaFailureV3 = decode(frame.message_id, p)?;
2642 Ok(Self::MediaTransmissionFailure {
2643 conference_id: value.conference_id,
2644 passthrough_party_id: value.passthrough_party_id,
2645 address: value.address.to_ip(frame.message_id)?,
2646 port: decode_port(value.port, frame.message_id, "RTP port")?,
2647 call_reference: value.call_reference,
2648 status: MediaStatus::UnspecifiedError,
2649 })
2650 }
2651 },
2652 wire_id::CONNECTION_STATISTICS_RES => {
2653 decode_connection_statistics(p, protocol_version, frame.message_id)
2654 .map(Self::ConnectionStatisticsResponse)
2655 }
2656 wire_id::START_MEDIA_TRANSMISSION_ACK => {
2657 decode_start_media_ack(p, protocol_version, frame.message_id)
2658 .map(Self::StartMediaTransmissionAck)
2659 }
2660 wire_id::START_MULTIMEDIA_TRANSMISSION_ACK => {
2661 decode_start_multimedia_ack(p, protocol_version, frame.message_id)
2662 .map(Self::StartMultimediaTransmissionAck)
2663 }
2664 wire_id::EXTENSION_DEVICE_CAPABILITIES => {
2665 let value: WireExtensionDeviceCapabilities = decode(frame.message_id, p)?;
2666 Ok(Self::ExtensionDeviceCapabilities(
2667 ExtensionDeviceCapabilities {
2668 unknown_1: value.unknown_1,
2669 unknown_2: value.unknown_2,
2670 unknown_3: value.unknown_3,
2671 description: value.description.text()?,
2672 },
2673 ))
2674 }
2675 wire_id::LOCATION_INFO => {
2676 let value: WireLocationInfo = decode(frame.message_id, p)?;
2677 validate_zero_payload(&value.alignment, frame.message_id, 3)?;
2678 Ok(Self::LocationInfo {
2679 xml: value.xml.text()?,
2680 })
2681 }
2682 wire_id::XML_ALARM => {
2683 XmlAlarmMessage::from_wire_payload(p.to_vec()).map(Self::XmlAlarm)
2684 }
2685 wire_id::CALL_COUNT_REQ => {
2686 let payload = match p.len() {
2687 0 => CallCountRequestPayload::Empty,
2688 4 => CallCountRequestPayload::LegacyWord(
2689 decode::<WireOneWord>(frame.message_id, p)?.value,
2690 ),
2691 CALL_COUNT_REQUEST_EXTENDED_BYTES => {
2692 let extended =
2693 p.as_slice()
2694 .try_into()
2695 .map_err(|_| CodecError::InvalidValue {
2696 message_id: frame.message_id,
2697 field: "call-count request payload length",
2698 value: p.len() as u64,
2699 })?;
2700 CallCountRequestPayload::Extended(extended)
2701 }
2702 _ => {
2703 return Err(CodecError::InvalidValue {
2704 message_id: frame.message_id,
2705 field: "call-count request payload length",
2706 value: p.len() as u64,
2707 });
2708 }
2709 };
2710 Ok(Self::CallCountRequest(payload))
2711 }
2712 wire_id::CREATE_CONFERENCE_RES => {
2713 validate_conference_data_length(p, frame.message_id, 12, 8)?;
2714 let value: WireConferenceResponse = decode_zero_padded(frame.message_id, p)?;
2715 Ok(Self::CreateConferenceResponse(CreateConferenceResponse {
2716 conference_id: value.conference_id.into(),
2717 result: CreateConferenceResult::from(value.result),
2718 passthrough_data: value.passthrough_data,
2719 }))
2720 }
2721 wire_id::DELETE_CONFERENCE_RES => {
2722 validate_exact_payload(p, frame.message_id, 8)?;
2723 let value: WireCallParty = decode(frame.message_id, p)?;
2724 Ok(Self::DeleteConferenceResponse {
2725 conference_id: value.call_reference.into(),
2726 result: DeleteConferenceResult::from(value.passthrough_party_id),
2727 })
2728 }
2729 wire_id::MODIFY_CONFERENCE_RES => {
2730 validate_conference_data_length(p, frame.message_id, 12, 8)?;
2731 let value: WireConferenceResponse = decode_zero_padded(frame.message_id, p)?;
2732 Ok(Self::ModifyConferenceResponse(ModifyConferenceResponse {
2733 conference_id: value.conference_id.into(),
2734 result: ModifyConferenceResult::from(value.result),
2735 passthrough_data: value.passthrough_data,
2736 }))
2737 }
2738 wire_id::AUDIT_CONFERENCE_RES => {
2739 if p.len() < 8 {
2740 return Err(CodecError::Truncated {
2741 message_id: frame.message_id,
2742 needed: 8,
2743 actual: p.len(),
2744 });
2745 }
2746 let number_of_entries = usize_from_wire(
2747 frame.message_id,
2748 "conference audit entries",
2749 u32::from_le_bytes(p[4..8].try_into().expect("validated audit header")),
2750 )?;
2751 if number_of_entries > MAX_AUDIT_CONFERENCE_ENTRIES {
2752 return Err(CodecError::CountTooLarge {
2753 message_id: frame.message_id,
2754 field: "conference audit entries",
2755 count: number_of_entries,
2756 maximum: MAX_AUDIT_CONFERENCE_ENTRIES,
2757 });
2758 }
2759 validate_exact_payload(p, frame.message_id, 8 + number_of_entries * 76)?;
2760 let value: WireAuditConferenceResponse = decode(frame.message_id, p)?;
2761 Ok(Self::AuditConferenceResponse(AuditConferenceResponse {
2762 last: value.last,
2763 entries: value
2764 .entries
2765 .into_iter()
2766 .map(|entry| {
2767 Ok(AuditConferenceEntry {
2768 conference_id: entry.conference_id.into(),
2769 resource_type: ConferenceResourceType::from(entry.resource_type),
2770 reserved_participants: entry.reserved_participants,
2771 active_participants: entry.active_participants,
2772 application_id: entry.application_id.into(),
2773 application_conference_id: entry
2774 .application_conference_id
2775 .text()?,
2776 application_data: entry.application_data.text()?,
2777 })
2778 })
2779 .collect::<Result<Vec<_>, CodecError>>()?,
2780 }))
2781 }
2782 wire_id::ADD_PARTICIPANT_RES => {
2783 validate_payload_bounds(p, frame.message_id, 12, 272)?;
2784 let value: WireAddParticipantResponseHeader = decode_prefix(frame.message_id, p)?;
2785 let identifier_end = if p.len() == 272 {
2786 if p[269..].iter().any(|byte| *byte != 0) {
2787 return Err(CodecError::InvalidValue {
2788 message_id: frame.message_id,
2789 field: "AddParticipantResponse alignment",
2790 value: 1,
2791 });
2792 }
2793 269
2794 } else {
2795 p.len()
2796 };
2797 let identifier = &p[12..identifier_end];
2798 let bridge_participant_id =
2799 BoundedBytes::try_from(identifier).map_err(|error| {
2800 CodecError::CountTooLarge {
2801 message_id: frame.message_id,
2802 field: "bridge participant identifier",
2803 count: error.actual,
2804 maximum: error.maximum,
2805 }
2806 })?;
2807 Ok(Self::AddParticipantResponse(AddParticipantResponse {
2808 conference_id: value.conference_id.into(),
2809 call_reference: value.call_reference.into(),
2810 result: AddParticipantResult::from(value.result),
2811 bridge_participant_id,
2812 }))
2813 }
2814 wire_id::AUDIT_PARTICIPANT_RES => {
2815 if p.len() < 16 {
2816 return Err(CodecError::Truncated {
2817 message_id: frame.message_id,
2818 needed: 16,
2819 actual: p.len(),
2820 });
2821 }
2822 let participant_entries = &p[16..];
2823 if participant_entries.len() > MAX_AUDIT_PARTICIPANT_DATA {
2824 return Err(CodecError::CountTooLarge {
2825 message_id: frame.message_id,
2826 field: "participant audit data",
2827 count: participant_entries.len(),
2828 maximum: MAX_AUDIT_PARTICIPANT_DATA,
2829 });
2830 }
2831 let value: WireAuditParticipantResponseHeader = decode(frame.message_id, &p[..16])?;
2832 Ok(Self::AuditParticipantResponse(AuditParticipantResponse {
2833 result: AuditParticipantResult::from(value.result),
2834 last: value.last,
2835 conference_id: value.conference_id.into(),
2836 number_of_entries: value.number_of_entries,
2837 participant_entries: participant_entries.to_vec(),
2838 }))
2839 }
2840 _ => {
2841 let id = frame.message_type();
2842 if id.is_known() {
2843 preserve_known_message(frame, id).map(Self::KnownOpaque)
2844 } else {
2845 Ok(Self::Unknown(RawMessage {
2846 message_id: frame.message_id,
2847 protocol_version: frame.protocol_version,
2848 payload: frame.payload,
2849 }))
2850 }
2851 }
2852 }
2853 }
2854
2855 pub fn encode(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
2857 let (message_id, payload, header_protocol) = self.payload(protocol)?;
2858 reject_non_station_route(
2859 message_id,
2860 MessageRoute::StationToControl,
2861 "station-to-control",
2862 )?;
2863 Frame::new(header_protocol, message_id, payload).encode()
2864 }
2865
2866 fn encode_unchecked(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
2867 let (message_id, payload, header_protocol) = self.payload(protocol)?;
2868 Frame::new(header_protocol, message_id, payload).encode()
2869 }
2870
2871 fn payload(&self, protocol: ProtocolVersion) -> Result<(u32, Vec<u8>, u32), CodecError> {
2872 let mut payload = Vec::new();
2873 let mut header_protocol = protocol.wire();
2874 let message_id = match self {
2875 Self::KeepAlive => {
2876 header_protocol = 0;
2877 wire_id::KEEP_ALIVE
2878 }
2879 Self::Register(registration) => {
2880 header_protocol = 0;
2881 let feature_bytes = registration.features.bits().to_le_bytes();
2882 let wire = registration.wire.unwrap_or(RegistrationWireDetails {
2883 layout: RegistrationWireLayout::default(),
2884 station_user_id: 0,
2885 station_instance: 1,
2886 max_streams: 0,
2887 active_streams: 0,
2888 mac_address_and_padding: [0; 12],
2889 max_conferences: 0,
2890 active_conferences: 0,
2891 ipv4_address_scope: 0,
2892 max_lines: 0,
2893 ipv6_address_scope: 0,
2894 });
2895 let prefix_bytes = registration_prefix_bytes(wire.layout)?;
2896 validate_registration_fields(registration, wire, prefix_bytes)?;
2897 let alternate = wire.layout == RegistrationWireLayout::Alternate32;
2898 let device_id = WireFixedText::new(
2899 wire_id::REGISTER,
2900 "device ID",
2901 registration.device_id.as_str(),
2902 )?;
2903 let advertised_protocol = registration.advertised_protocol.unwrap_or(0);
2904 let protocol_features = if alternate {
2905 [0; 4]
2906 } else {
2907 [
2908 advertised_protocol.min(u32::from(u8::MAX)) as u8,
2909 feature_bytes[1],
2910 feature_bytes[2],
2911 feature_bytes[3],
2912 ]
2913 };
2914 let reported_address = if alternate {
2915 [advertised_protocol.min(u32::from(u8::MAX)) as u8, 0, 0, 0]
2916 } else {
2917 registration
2918 .reported_address
2919 .unwrap_or(Ipv4Addr::UNSPECIFIED)
2920 .octets()
2921 };
2922 payload = encode(
2923 wire_id::REGISTER,
2924 &WireRegister {
2925 device_id,
2926 station_user_id: wire.station_user_id,
2927 station_instance: wire.station_instance,
2928 reported_address,
2929 device_type: registration.device_type.wire_value(),
2930 max_streams: wire.max_streams,
2931 active_streams: wire.active_streams,
2932 protocol_features,
2933 max_conferences: wire.max_conferences,
2934 active_conferences: wire.active_conferences,
2935 mac_address: wire.mac_address_and_padding,
2936 ipv4_address_scope: wire.ipv4_address_scope,
2937 max_lines: wire.max_lines,
2938 ipv6_address: registration
2939 .reported_ipv6_address
2940 .unwrap_or(Ipv6Addr::UNSPECIFIED)
2941 .octets(),
2942 ipv6_address_scope: wire.ipv6_address_scope,
2943 firmware: WireFixedText::new(
2944 wire_id::REGISTER,
2945 "firmware",
2946 ®istration.firmware,
2947 )?,
2948 },
2949 )?;
2950 payload.truncate(prefix_bytes);
2951 if !alternate {
2952 payload.extend_from_slice(registration.configuration_version_stamp.as_bytes());
2953 }
2954 wire_id::REGISTER
2955 }
2956 Self::IpPort { rtp_port } => {
2957 payload = encode(
2958 wire_id::IP_PORT,
2959 &WireOneWord {
2960 value: u32::from(*rtp_port),
2961 },
2962 )?;
2963 wire_id::IP_PORT
2964 }
2965 Self::KeypadButton {
2966 button,
2967 line_instance,
2968 call_reference,
2969 wire_layout,
2970 } => {
2971 payload = match wire_layout {
2972 Some(KeypadButtonWireLayout::LegacyButtonOnly) => encode(
2973 wire_id::KEYPAD_BUTTON,
2974 &WireKeypadButtonLegacy {
2975 button: button.keypad_value(),
2976 },
2977 )?,
2978 Some(KeypadButtonWireLayout::WithCallIdentity) => encode(
2979 wire_id::KEYPAD_BUTTON,
2980 &WireKeypadButtonWithCall {
2981 base: WireKeypadButtonLegacy {
2982 button: button.keypad_value(),
2983 },
2984 line_instance: *line_instance,
2985 call_reference: *call_reference,
2986 },
2987 )?,
2988 None => encode(
2989 wire_id::KEYPAD_BUTTON,
2990 &WireKeypadButton {
2991 base: WireKeypadButtonWithCall {
2992 base: WireKeypadButtonLegacy {
2993 button: button.keypad_value(),
2994 },
2995 line_instance: *line_instance,
2996 call_reference: *call_reference,
2997 },
2998 keypad_union: 0,
2999 reserved: 0,
3000 },
3001 )?,
3002 };
3003 wire_id::KEYPAD_BUTTON
3004 }
3005 Self::EnblocCall {
3006 called_party,
3007 line_instance,
3008 } => {
3009 payload = encode_enbloc(called_party, *line_instance, protocol.wire())?;
3010 wire_id::ENBLOC_CALL
3011 }
3012 Self::Stimulus {
3013 stimulus,
3014 instance,
3015 call_reference,
3016 status,
3017 } => {
3018 payload = encode(
3019 wire_id::STIMULUS,
3020 &WireStimulus {
3021 stimulus: stimulus.wire_value(),
3022 instance: *instance,
3023 call_reference: *call_reference,
3024 status: *status,
3025 },
3026 )?;
3027 wire_id::STIMULUS
3028 }
3029 Self::OffHook {
3030 line_instance,
3031 call_reference,
3032 } => {
3033 payload = encode(
3034 wire_id::OFF_HOOK,
3035 &WireLineCall {
3036 line_instance: *line_instance,
3037 call_reference: *call_reference,
3038 },
3039 )?;
3040 wire_id::OFF_HOOK
3041 }
3042 Self::OnHook {
3043 line_instance,
3044 call_reference,
3045 } => {
3046 payload = encode(
3047 wire_id::ON_HOOK,
3048 &WireLineCall {
3049 line_instance: *line_instance,
3050 call_reference: *call_reference,
3051 },
3052 )?;
3053 wire_id::ON_HOOK
3054 }
3055 Self::OffHookWithCallingParty {
3056 calling_party_number,
3057 voice_mailbox,
3058 line_instance,
3059 } => {
3060 payload = match protocol.wire() {
3061 19.. => encode_off_hook_with_calling_party::<25, 2>(
3062 calling_party_number,
3063 voice_mailbox,
3064 *line_instance,
3065 ),
3066 _ => encode_off_hook_with_calling_party::<24, 0>(
3067 calling_party_number,
3068 voice_mailbox,
3069 *line_instance,
3070 ),
3071 }?;
3072 wire_id::OFF_HOOK_WITH_CALLING_PARTY
3073 }
3074 Self::HookFlash {
3075 line_instance,
3076 call_reference,
3077 } => {
3078 payload = encode(
3079 wire_id::HOOK_FLASH,
3080 &WireLineCall {
3081 line_instance: *line_instance,
3082 call_reference: *call_reference,
3083 },
3084 )?;
3085 wire_id::HOOK_FLASH
3086 }
3087 Self::ForwardStatusRequest { line_instance } => {
3088 payload = encode(
3089 wire_id::FORWARD_STAT_REQ,
3090 &WireOneWord {
3091 value: *line_instance,
3092 },
3093 )?;
3094 wire_id::FORWARD_STAT_REQ
3095 }
3096 Self::SpeedDialStatusRequest {
3097 speed_dial_instance,
3098 } => {
3099 payload = encode(
3100 wire_id::SPEED_DIAL_STAT_REQ,
3101 &WireOneWord {
3102 value: *speed_dial_instance,
3103 },
3104 )?;
3105 wire_id::SPEED_DIAL_STAT_REQ
3106 }
3107 Self::LineStatRequest { line_instance } => {
3108 payload = encode(
3109 wire_id::LINE_STAT_REQ,
3110 &WireOneWord {
3111 value: *line_instance,
3112 },
3113 )?;
3114 wire_id::LINE_STAT_REQ
3115 }
3116 Self::ConfigStatRequest => wire_id::CONFIG_STAT_REQ,
3117 Self::TimeDateRequest => wire_id::TIME_DATE_REQ,
3118 Self::ButtonTemplateRequest => wire_id::BUTTON_TEMPLATE_REQ,
3119 Self::VersionRequest => wire_id::VERSION_REQ,
3120 Self::CapabilitiesResponse(capabilities) => {
3121 payload = encode_capabilities_response(capabilities)?;
3122 wire_id::CAPABILITIES_RES
3123 }
3124 Self::MediaPortList(message) => {
3125 validate_media_port_count(wire_id::MEDIA_PORT_LIST, message.rtp_ports.len())?;
3126 let mut ports = [0; MEDIA_PORT_LIST_MAX_PORTS];
3127 for (target, port) in ports.iter_mut().zip(&message.rtp_ports) {
3128 *target = u32::from(*port);
3129 }
3130 payload = encode(
3131 wire_id::MEDIA_PORT_LIST,
3132 &WireMediaPortList {
3133 count: wire_count(
3134 wire_id::MEDIA_PORT_LIST,
3135 "RTP ports",
3136 message.rtp_ports.len(),
3137 )?,
3138 ports,
3139 },
3140 )?;
3141 wire_id::MEDIA_PORT_LIST
3142 }
3143 Self::CapabilitiesUpdate(update) => {
3144 payload.extend_from_slice(update.raw_payload());
3145 update.variant().message_id()
3146 }
3147 Self::OpenMultimediaReceiveChannelAck(ack) => {
3148 payload = encode_open_multimedia_ack(*ack, protocol)?;
3149 wire_id::OPEN_MULTIMEDIA_RECEIVE_CHANNEL_ACK
3150 }
3151 Self::ServerRequest => wire_id::SERVER_REQ,
3152 Self::Alarm {
3153 severity,
3154 text,
3155 parameters,
3156 } => {
3157 let text = WireFixedText::new(wire_id::ALARM, "alarm text", text)?;
3158 let base = WireAlarmBase {
3159 severity: severity.wire_value(),
3160 text,
3161 };
3162 payload = match parameters {
3163 Some([parameter_1, parameter_2]) => encode(
3164 wire_id::ALARM,
3165 &WireAlarm {
3166 base,
3167 parameter_1: *parameter_1,
3168 parameter_2: *parameter_2,
3169 },
3170 ),
3171 None => encode(wire_id::ALARM, &base),
3172 }?;
3173 wire_id::ALARM
3174 }
3175 Self::MulticastMediaReceptionAck {
3176 status,
3177 passthrough_party_id,
3178 call_reference,
3179 } => {
3180 payload = encode(
3181 wire_id::MULTICAST_MEDIA_RECEPTION_ACK,
3182 &WireMulticastReceptionAck {
3183 status: status.wire_value(),
3184 passthrough_party_id: passthrough_party_id.get(),
3185 call_reference: call_reference.get(),
3186 },
3187 )?;
3188 wire_id::MULTICAST_MEDIA_RECEPTION_ACK
3189 }
3190 Self::OpenReceiveChannelAck {
3191 status,
3192 address,
3193 port,
3194 passthrough_party_id,
3195 call_reference,
3196 } => {
3197 payload = match protocol.wire() {
3198 17.. => encode(
3199 wire_id::OPEN_RECEIVE_CHANNEL_ACK,
3200 &WireOpenReceiveAckV17 {
3201 status: status.wire_value(),
3202 address: WireExtendedAddress::from_ip(*address),
3203 port: u32::from(*port),
3204 passthrough_party_id: *passthrough_party_id,
3205 call_reference: *call_reference,
3206 },
3207 ),
3208 _ => encode(
3209 wire_id::OPEN_RECEIVE_CHANNEL_ACK,
3210 &WireOpenReceiveAckV3 {
3211 status: status.wire_value(),
3212 address: WireIpv4Address::from_ip(
3213 *address,
3214 wire_id::OPEN_RECEIVE_CHANNEL_ACK,
3215 "IP address family for this protocol version",
3216 )?,
3217 port: u32::from(*port),
3218 passthrough_party_id: *passthrough_party_id,
3219 call_reference: *call_reference,
3220 },
3221 ),
3222 }?;
3223 wire_id::OPEN_RECEIVE_CHANNEL_ACK
3224 }
3225 Self::SoftKeySetRequest => wire_id::SOFT_KEY_SET_REQ,
3226 Self::SoftKeyTemplateRequest => wire_id::SOFT_KEY_TEMPLATE_REQ,
3227 Self::SoftKeyEvent {
3228 event,
3229 line_instance,
3230 call_reference,
3231 } => {
3232 payload = encode(
3233 wire_id::SOFT_KEY_EVENT,
3234 &WireSoftKeyEvent {
3235 event: *event,
3236 line_instance: *line_instance,
3237 call_reference: *call_reference,
3238 },
3239 )?;
3240 wire_id::SOFT_KEY_EVENT
3241 }
3242 Self::Unregister { reason } => {
3243 payload = encode(wire_id::UNREGISTER, &WireOneWord { value: *reason })?;
3244 wire_id::UNREGISTER
3245 }
3246 Self::RegisterToken(token) => {
3247 let (ipv4_address, ipv6_address) = match token.address {
3248 IpAddr::V4(address) => (address.octets(), [0; 16]),
3249 IpAddr::V6(address) => ([0; 4], address.octets()),
3250 };
3251 payload = encode(
3252 wire_id::REGISTER_TOKEN_REQ,
3253 &WireRegisterToken {
3254 device_id: WireFixedText::new(
3255 wire_id::REGISTER_TOKEN_REQ,
3256 "device ID",
3257 token.device_id.as_str(),
3258 )?,
3259 device_instance: token.device_instance,
3260 ipv4_address,
3261 device_type: token.device_type.wire_value(),
3262 ipv6_address,
3263 flags: token.flags,
3264 },
3265 )?;
3266 wire_id::REGISTER_TOKEN_REQ
3267 }
3268 Self::SpcpRegisterToken(token) => {
3269 payload = encode(
3270 wire_id::SPCP_REGISTER_TOKEN_REQ,
3271 &WireSpcpRegisterToken {
3272 device_id: WireFixedText::new(
3273 wire_id::SPCP_REGISTER_TOKEN_REQ,
3274 "device ID",
3275 token.device_id.as_str(),
3276 )?,
3277 reserved: 0,
3278 device_instance: token.device_instance,
3279 ipv4_address: u32::from(token.address),
3280 device_type: token.device_type.wire_value(),
3281 max_streams: token.max_streams,
3282 },
3283 )?;
3284 wire_id::SPCP_REGISTER_TOKEN_REQ
3285 }
3286 Self::ConnectionStatisticsResponse(statistics) => {
3287 payload = encode_connection_statistics(statistics, protocol)?;
3288 wire_id::CONNECTION_STATISTICS_RES
3289 }
3290 Self::HeadsetStatus { enabled } => {
3291 payload = encode(
3292 wire_id::HEADSET_STATUS,
3293 &WireOneWord {
3294 value: u32::from(*enabled),
3295 },
3296 )?;
3297 wire_id::HEADSET_STATUS
3298 }
3299 Self::MediaResourceNotification(notification) => {
3300 payload = encode(
3301 wire_id::MEDIA_RESOURCE_NOTIFICATION,
3302 &WireMediaResourceNotification {
3303 device_type: notification.device_type.wire_value(),
3304 in_service_streams: notification.in_service_streams,
3305 max_streams_per_conference: notification.max_streams_per_conference,
3306 out_of_service_streams: notification.out_of_service_streams,
3307 },
3308 )?;
3309 wire_id::MEDIA_RESOURCE_NOTIFICATION
3310 }
3311 Self::MediaPathEvent { path, event } => {
3312 payload = encode(
3313 wire_id::ACCESSORY_STATUS,
3314 &WireAccessoryStatus {
3315 accessory: path.wire_value(),
3316 state: event.wire_value(),
3317 },
3318 )?;
3319 wire_id::ACCESSORY_STATUS
3320 }
3321 Self::MediaPathCapability { path, capability } => {
3322 payload = encode(
3323 wire_id::MEDIA_PATH_CAPABILITY,
3324 &WireAccessoryStatus {
3325 accessory: path.wire_value(),
3326 state: capability.wire_value(),
3327 },
3328 )?;
3329 wire_id::MEDIA_PATH_CAPABILITY
3330 }
3331 Self::MediaTransmissionFailure {
3332 conference_id,
3333 passthrough_party_id,
3334 address,
3335 port,
3336 call_reference,
3337 ..
3338 } => {
3339 payload = match protocol.wire() {
3340 17.. => encode(
3341 wire_id::MEDIA_TRANSMISSION_FAILURE,
3342 &WireMediaFailureV17 {
3343 conference_id: *conference_id,
3344 passthrough_party_id: *passthrough_party_id,
3345 address: WireExtendedAddress::from_ip(*address),
3346 port: u32::from(*port),
3347 call_reference: *call_reference,
3348 },
3349 ),
3350 _ => encode(
3351 wire_id::MEDIA_TRANSMISSION_FAILURE,
3352 &WireMediaFailureV3 {
3353 conference_id: *conference_id,
3354 passthrough_party_id: *passthrough_party_id,
3355 address: WireIpv4Address::from_ip(
3356 *address,
3357 wire_id::MEDIA_TRANSMISSION_FAILURE,
3358 "IP address family for this protocol version",
3359 )?,
3360 port: u32::from(*port),
3361 call_reference: *call_reference,
3362 },
3363 ),
3364 }?;
3365 wire_id::MEDIA_TRANSMISSION_FAILURE
3366 }
3367 Self::RegisterAvailableLines { lines } => {
3368 payload = encode(
3369 wire_id::REGISTER_AVAILABLE_LINES,
3370 &WireOneWord { value: *lines },
3371 )?;
3372 wire_id::REGISTER_AVAILABLE_LINES
3373 }
3374 Self::ServiceUrlStatusRequest { index } => {
3375 payload = encode(
3376 wire_id::SERVICE_URL_STAT_REQ,
3377 &WireOneWord { value: *index },
3378 )?;
3379 wire_id::SERVICE_URL_STAT_REQ
3380 }
3381 Self::FeatureStatusRequest {
3382 index,
3383 capabilities,
3384 } => {
3385 payload = encode(
3386 wire_id::FEATURE_STAT_REQ,
3387 &WireFeatureStatusRequest {
3388 index: *index,
3389 capabilities: *capabilities,
3390 },
3391 )?;
3392 wire_id::FEATURE_STAT_REQ
3393 }
3394 Self::StartMediaTransmissionAck(ack) => {
3395 payload = encode_start_media_ack(ack, protocol)?;
3396 wire_id::START_MEDIA_TRANSMISSION_ACK
3397 }
3398 Self::StartMultimediaTransmissionAck(ack) => {
3399 payload = encode_start_multimedia_ack(*ack, protocol)?;
3400 wire_id::START_MULTIMEDIA_TRANSMISSION_ACK
3401 }
3402 Self::ExtensionDeviceCapabilities(capabilities) => {
3403 payload = encode(
3404 wire_id::EXTENSION_DEVICE_CAPABILITIES,
3405 &WireExtensionDeviceCapabilities {
3406 unknown_1: capabilities.unknown_1,
3407 unknown_2: capabilities.unknown_2,
3408 unknown_3: capabilities.unknown_3,
3409 description: WireFixedText::new(
3410 wire_id::EXTENSION_DEVICE_CAPABILITIES,
3411 "extension-device capability description",
3412 &capabilities.description,
3413 )?,
3414 },
3415 )?;
3416 wire_id::EXTENSION_DEVICE_CAPABILITIES
3417 }
3418 Self::DeviceToUserData(data) => {
3419 payload = encode_user_data(data, wire_id::DEVICE_TO_USER_DATA)?;
3420 wire_id::DEVICE_TO_USER_DATA
3421 }
3422 Self::DeviceToUserDataResponse(data) => {
3423 payload = encode_user_data(data, wire_id::DEVICE_TO_USER_DATA_RESPONSE)?;
3424 wire_id::DEVICE_TO_USER_DATA_RESPONSE
3425 }
3426 Self::DeviceToUserDataV1(data) => {
3427 payload = encode_user_data_v1(data, wire_id::DEVICE_TO_USER_DATA_V1)?;
3428 wire_id::DEVICE_TO_USER_DATA_V1
3429 }
3430 Self::DeviceToUserDataResponseV1(data) => {
3431 payload = encode_user_data_v1(data, wire_id::DEVICE_TO_USER_DATA_RESPONSE_V1)?;
3432 wire_id::DEVICE_TO_USER_DATA_RESPONSE_V1
3433 }
3434 Self::PortResponse(endpoint) => {
3435 payload = encode_port_response(endpoint, protocol)?;
3436 wire_id::PORT_RESPONSE
3437 }
3438 Self::SubscriptionStatusRequest(subscription) => {
3439 payload = encode(
3440 wire_id::SUBSCRIPTION_STAT_REQ,
3441 &WireSubscriptionRequest {
3442 transaction_id: subscription.transaction_id,
3443 feature_id: subscription.feature_id,
3444 timer_seconds: subscription.timer_seconds,
3445 subscription_id: WireFixedText::new(
3446 wire_id::SUBSCRIPTION_STAT_REQ,
3447 "subscription ID",
3448 &subscription.subscription_id,
3449 )?,
3450 },
3451 )?;
3452 wire_id::SUBSCRIPTION_STAT_REQ
3453 }
3454 Self::SubscribeDtmfPayloadResponse(identity) => {
3455 payload = encode(
3456 wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES,
3457 &dtmf_payload_identity_to_wire(*identity),
3458 )?;
3459 wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES
3460 }
3461 Self::UnsubscribeDtmfPayloadResponse(identity) => {
3462 payload = encode(
3463 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_RES,
3464 &dtmf_payload_identity_to_wire(*identity),
3465 )?;
3466 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_RES
3467 }
3468 Self::LocationInfo { xml } => {
3469 payload = encode(
3470 wire_id::LOCATION_INFO,
3471 &WireLocationInfo {
3472 xml: WireFixedText::new(wire_id::LOCATION_INFO, "location XML", xml)?,
3473 alignment: [0; 3],
3474 },
3475 )?;
3476 wire_id::LOCATION_INFO
3477 }
3478 Self::XmlAlarm(message) => {
3479 payload = message.wire_payload().to_vec();
3480 wire_id::XML_ALARM
3481 }
3482 Self::CallCountRequest(request) => {
3483 match request {
3484 CallCountRequestPayload::Empty => {}
3485 CallCountRequestPayload::LegacyWord(value) => {
3486 payload = encode(wire_id::CALL_COUNT_REQ, &WireOneWord { value: *value })?;
3487 }
3488 CallCountRequestPayload::Extended(extended) => {
3489 payload.extend_from_slice(extended);
3490 }
3491 }
3492 wire_id::CALL_COUNT_REQ
3493 }
3494 Self::CreateConferenceResponse(response) => {
3495 payload = encode(
3496 wire_id::CREATE_CONFERENCE_RES,
3497 &WireConferenceResponse {
3498 conference_id: response.conference_id.get(),
3499 result: response.result.wire_value(),
3500 data_length: validate_conference_data_for_encode(
3501 wire_id::CREATE_CONFERENCE_RES,
3502 &response.passthrough_data,
3503 )?,
3504 passthrough_data: response.passthrough_data.clone(),
3505 },
3506 )?;
3507 wire_id::CREATE_CONFERENCE_RES
3508 }
3509 Self::DeleteConferenceResponse {
3510 conference_id,
3511 result,
3512 } => {
3513 payload = encode(
3514 wire_id::DELETE_CONFERENCE_RES,
3515 &WireCallParty {
3516 call_reference: conference_id.get(),
3517 passthrough_party_id: result.wire_value(),
3518 },
3519 )?;
3520 wire_id::DELETE_CONFERENCE_RES
3521 }
3522 Self::ModifyConferenceResponse(response) => {
3523 payload = encode(
3524 wire_id::MODIFY_CONFERENCE_RES,
3525 &WireConferenceResponse {
3526 conference_id: response.conference_id.get(),
3527 result: response.result.wire_value(),
3528 data_length: validate_conference_data_for_encode(
3529 wire_id::MODIFY_CONFERENCE_RES,
3530 &response.passthrough_data,
3531 )?,
3532 passthrough_data: response.passthrough_data.clone(),
3533 },
3534 )?;
3535 wire_id::MODIFY_CONFERENCE_RES
3536 }
3537 Self::AuditConferenceResponse(response) => {
3538 if response.entries.len() > MAX_AUDIT_CONFERENCE_ENTRIES {
3539 return Err(CodecError::CountTooLarge {
3540 message_id: wire_id::AUDIT_CONFERENCE_RES,
3541 field: "conference audit entries",
3542 count: response.entries.len(),
3543 maximum: MAX_AUDIT_CONFERENCE_ENTRIES,
3544 });
3545 }
3546 payload = encode(
3547 wire_id::AUDIT_CONFERENCE_RES,
3548 &WireAuditConferenceResponse {
3549 last: response.last,
3550 number_of_entries: wire_count(
3551 wire_id::AUDIT_CONFERENCE_RES,
3552 "conference audit entries",
3553 response.entries.len(),
3554 )?,
3555 entries: response
3556 .entries
3557 .iter()
3558 .map(|entry| {
3559 Ok(WireAuditConferenceEntry {
3560 conference_id: entry.conference_id.get(),
3561 resource_type: entry.resource_type.wire_value(),
3562 reserved_participants: entry.reserved_participants,
3563 active_participants: entry.active_participants,
3564 application_id: entry.application_id.get(),
3565 application_conference_id: WireFixedText::new(
3566 wire_id::AUDIT_CONFERENCE_RES,
3567 "application conference ID",
3568 &entry.application_conference_id,
3569 )?,
3570 application_data: WireFixedText::new(
3571 wire_id::AUDIT_CONFERENCE_RES,
3572 "application data",
3573 &entry.application_data,
3574 )?,
3575 })
3576 })
3577 .collect::<Result<Vec<_>, CodecError>>()?,
3578 },
3579 )?;
3580 wire_id::AUDIT_CONFERENCE_RES
3581 }
3582 Self::AddParticipantResponse(response) => {
3583 payload = encode(
3584 wire_id::ADD_PARTICIPANT_RES,
3585 &WireAddParticipantResponseHeader {
3586 conference_id: response.conference_id.get(),
3587 call_reference: response.call_reference.get(),
3588 result: response.result.wire_value(),
3589 },
3590 )?;
3591 payload.extend_from_slice(response.bridge_participant_id.as_bytes());
3592 payload.resize(269, 0);
3593 payload.extend_from_slice(&[0; 3]);
3594 wire_id::ADD_PARTICIPANT_RES
3595 }
3596 Self::AuditParticipantResponse(response) => {
3597 if response.participant_entries.len() > MAX_AUDIT_PARTICIPANT_DATA {
3598 return Err(CodecError::CountTooLarge {
3599 message_id: wire_id::AUDIT_PARTICIPANT_RES,
3600 field: "participant audit data",
3601 count: response.participant_entries.len(),
3602 maximum: MAX_AUDIT_PARTICIPANT_DATA,
3603 });
3604 }
3605 payload = encode(
3606 wire_id::AUDIT_PARTICIPANT_RES,
3607 &WireAuditParticipantResponseHeader {
3608 result: response.result.wire_value(),
3609 last: response.last,
3610 conference_id: response.conference_id.get(),
3611 number_of_entries: response.number_of_entries,
3612 },
3613 )?;
3614 payload.extend_from_slice(&response.participant_entries);
3615 wire_id::AUDIT_PARTICIPANT_RES
3616 }
3617 Self::KnownOpaque(message) => {
3618 ensure_preserve_only(message.id)?;
3619 return Ok((
3620 message.id.wire_value(),
3621 message.payload.as_bytes().to_vec(),
3622 message.protocol_version,
3623 ));
3624 }
3625 Self::Unknown(message) => {
3626 return Ok((
3627 message.message_id,
3628 message.payload.clone(),
3629 message.protocol_version,
3630 ));
3631 }
3632 };
3633 pad_typed_payload(message_id, &mut payload);
3634 Ok((message_id, payload, header_protocol))
3635 }
3636}
3637
3638fn encode_connection_statistics(
3639 statistics: &ConnectionStatistics,
3640 protocol: ProtocolVersion,
3641) -> Result<Vec<u8>, CodecError> {
3642 let tail = WireConnectionStatisticsTail {
3643 counters: WireConnectionStatisticsCounters {
3644 packets_sent: statistics.packets_sent,
3645 octets_sent: statistics.octets_sent,
3646 packets_received: statistics.packets_received,
3647 octets_received: statistics.octets_received,
3648 packets_lost: statistics.packets_lost,
3649 jitter_millis: statistics.jitter_millis,
3650 latency_millis: statistics.latency_millis,
3651 },
3652 quality_size: u32::try_from(statistics.quality.as_bytes().len()).map_err(|_| {
3653 CodecError::CountTooLarge {
3654 message_id: wire_id::CONNECTION_STATISTICS_RES,
3655 field: "quality statistics",
3656 count: statistics.quality.as_bytes().len(),
3657 maximum: CONNECTION_QUALITY_MAX_BYTES,
3658 }
3659 })?,
3660 };
3661 match protocol.wire() {
3662 19.. => encode(
3663 wire_id::CONNECTION_STATISTICS_RES,
3664 &WireConnectionStatisticsV19 {
3665 directory_number: WireAlignedText::new(
3666 wire_id::CONNECTION_STATISTICS_RES,
3667 "directory number",
3668 &statistics.directory_number,
3669 )?,
3670 call_reference: statistics.call_reference,
3671 processing: statistics.processing.wire_value(),
3672 statistics: tail,
3673 quality: statistics.quality.as_bytes().to_vec(),
3674 },
3675 ),
3676 _ => encode(
3677 wire_id::CONNECTION_STATISTICS_RES,
3678 &WireConnectionStatisticsV3 {
3679 directory_number: WireAlignedText::new(
3680 wire_id::CONNECTION_STATISTICS_RES,
3681 "directory number",
3682 &statistics.directory_number,
3683 )?,
3684 call_reference: statistics.call_reference,
3685 processing: statistics.processing.wire_value(),
3686 statistics: tail,
3687 quality: statistics.quality.as_bytes().to_vec(),
3688 },
3689 ),
3690 }
3691}
3692
3693fn encode_start_media_ack(
3694 ack: &MediaTransmissionAck,
3695 protocol: ProtocolVersion,
3696) -> Result<Vec<u8>, CodecError> {
3697 match protocol.wire() {
3698 17.. => {
3699 let base = WireStartMediaAckV17 {
3700 conference_id: ack.conference_id,
3701 passthrough_party_id: ack.passthrough_party_id,
3702 call_reference: ack.call_reference,
3703 address: WireExtendedAddress::from_ip(ack.address),
3704 port: u32::from(ack.port),
3705 status: ack.status.wire_value(),
3706 };
3707 match ack.wire.as_ref().and_then(|wire| wire.extension) {
3708 Some(extension) => encode(
3709 wire_id::START_MEDIA_TRANSMISSION_ACK,
3710 &WireStartMediaAckV20 { base, extension },
3711 ),
3712 None => encode(wire_id::START_MEDIA_TRANSMISSION_ACK, &base),
3713 }
3714 }
3715 _ => encode(
3716 wire_id::START_MEDIA_TRANSMISSION_ACK,
3717 &WireStartMediaAckV3 {
3718 conference_id: ack.conference_id,
3719 passthrough_party_id: ack.passthrough_party_id,
3720 call_reference: ack.call_reference,
3721 address: WireIpv4Address::from_ip(
3722 ack.address,
3723 wire_id::START_MEDIA_TRANSMISSION_ACK,
3724 "IP address family for this protocol version",
3725 )?,
3726 port: u32::from(ack.port),
3727 status: ack.status.wire_value(),
3728 },
3729 ),
3730 }
3731}
3732
3733impl ServerMessage {
3734 pub fn decode(frame: Frame, protocol: ProtocolVersion) -> Result<Self, CodecError> {
3737 ensure_station_route(&frame, MessageRoute::ControlToStation, "control-to-station")?;
3738 Self::decode_unchecked(frame, protocol)
3739 }
3740
3741 fn decode_unchecked(frame: Frame, protocol: ProtocolVersion) -> Result<Self, CodecError> {
3742 let p = &frame.payload;
3743 match frame.message_id {
3744 wire_id::REGISTER_ACK => {
3745 let value: WireRegisterAck = decode(frame.message_id, p)?;
3746 validate_zero_payload(&value.alignment, frame.message_id, 2)?;
3747 let protocol_features = u32::from_le_bytes(value.protocol_features);
3748 Ok(Self::RegisterAck {
3749 keepalive_seconds: value.keepalive_seconds,
3750 secondary_keepalive_seconds: value.secondary_keepalive_seconds,
3751 protocol: ProtocolVersion::negotiate(u32::from(value.protocol_features[0]))?,
3752 features: PhoneFeatures::from_bits_retain(protocol_features & !0xff),
3753 date_template: DateTemplate::new(
3754 std::str::from_utf8(
3755 &value.date_template[..value
3756 .date_template
3757 .iter()
3758 .position(|byte| *byte == 0)
3759 .unwrap_or(6)],
3760 )
3761 .map_err(|_| CodecError::InvalidText)?,
3762 )?,
3763 })
3764 }
3765 wire_id::REGISTER_REJECT => {
3766 let value: WireFixedText<33> = decode_zero_padded(frame.message_id, p)?;
3767 Ok(Self::RegisterReject {
3768 reason: value.text()?,
3769 })
3770 }
3771 wire_id::KEEP_ALIVE_ACK => Ok(Self::KeepAliveAck),
3772 wire_id::UNREGISTER_ACK => {
3773 let _: WireOneWord = decode(frame.message_id, p)?;
3774 Ok(Self::UnregisterAck)
3775 }
3776 wire_id::CAPABILITIES_REQ => Ok(Self::CapabilitiesRequest),
3777 wire_id::ENUNCIATOR_COMMAND => {
3778 validate_exact_payload(p, frame.message_id, 0)?;
3779 Ok(Self::EnunciatorCommand)
3780 }
3781 wire_id::CONFIG_STAT => {
3782 let value: WireConfigStatus = decode(frame.message_id, p)?;
3783 Ok(Self::ConfigStatus(ConfigurationStatus {
3784 device_name: value.device_id.text()?,
3785 station_user_id: value.station_user_id,
3786 station_instance: value.station_instance,
3787 user_name: value.user_name.text()?,
3788 server_name: value.server_name.text()?,
3789 line_count: value.line_count,
3790 speed_dial_count: value.speed_dial_count,
3791 }))
3792 }
3793 wire_id::CONFIG_STAT_DYNAMIC => decode_dynamic_config_status(p),
3794 wire_id::LINE_STAT => {
3795 let value: WireLineStatus = decode(frame.message_id, p)?;
3796 Ok(Self::LineStatus {
3797 instance: value.line_instance,
3798 directory_number: value.directory_number.text()?,
3799 fully_qualified_display_name: value.display_name.text()?,
3800 display_label: value.display_label.text()?,
3801 })
3802 }
3803 wire_id::LINE_STAT_DYNAMIC => decode_dynamic_line_status(p),
3804 wire_id::BUTTON_TEMPLATE => {
3805 let value: WireButtonTemplate = decode(frame.message_id, p)?;
3806 if value.count > BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 {
3807 return Err(CodecError::CountTooLarge {
3808 message_id: frame.message_id,
3809 field: "button definitions in message",
3810 count: usize_from_wire(
3811 frame.message_id,
3812 "button definitions in message",
3813 value.count,
3814 )?,
3815 maximum: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK,
3816 });
3817 }
3818 let total = usize_from_wire(frame.message_id, "button definitions", value.total)?;
3819 let offset = usize_from_wire(frame.message_id, "button offset", value.offset)?;
3820 let count = usize_from_wire(frame.message_id, "button definitions", value.count)?;
3821 if offset.checked_add(count).is_none_or(|end| end > total) {
3822 return Err(CodecError::InvalidValue {
3823 message_id: frame.message_id,
3824 field: "button template range",
3825 value: u64::from(value.offset) + u64::from(value.count),
3826 });
3827 }
3828 let buttons = value.definitions[..count]
3829 .iter()
3830 .map(|definition| ButtonTemplateEntry {
3831 instance: u32::from(definition.instance),
3832 button_type: ButtonType::from(u32::from(definition.button_type)),
3833 })
3834 .collect::<Vec<_>>();
3835 Ok(Self::ButtonTemplate {
3836 offset: value.offset,
3837 total: value.total,
3838 buttons,
3839 })
3840 }
3841 wire_id::VERSION => {
3842 let value: WireFixedText<16> = decode(frame.message_id, p)?;
3843 Ok(Self::Version {
3844 firmware: value.text()?,
3845 })
3846 }
3847 wire_id::SERVER_RES => {
3848 let servers = match protocol.wire() {
3849 17.. => {
3850 let value: WireServerResponse<WireExtendedAddress> =
3851 decode(frame.message_id, p)?;
3852 decode_server_endpoints(
3853 frame.message_id,
3854 value.names,
3855 value.ports,
3856 value
3857 .addresses
3858 .map(|address| address.to_ip(frame.message_id))
3859 .into_iter()
3860 .collect::<Result<Vec<_>, _>>()?,
3861 )?
3862 }
3863 _ => {
3864 let value: WireServerResponse<WireIpv4Address> =
3865 decode(frame.message_id, p)?;
3866 decode_server_endpoints(
3867 frame.message_id,
3868 value.names,
3869 value.ports,
3870 value
3871 .addresses
3872 .map(|address| address.to_ip(frame.message_id))
3873 .into_iter()
3874 .collect::<Result<Vec<_>, _>>()?,
3875 )?
3876 }
3877 };
3878 Ok(Self::ServerResponse { servers })
3879 }
3880 wire_id::DEFINE_TIME_DATE => {
3881 let value: WireTimeDate = decode(frame.message_id, p)?;
3882 Ok(Self::TimeDate {
3883 year: value.year,
3884 month: value.month,
3885 weekday: value.weekday,
3886 day: value.day,
3887 hour: value.hour,
3888 minute: value.minute,
3889 second: value.second,
3890 milliseconds: value.milliseconds,
3891 unix_seconds: value.unix_seconds,
3892 })
3893 }
3894 wire_id::SOFT_KEY_TEMPLATE_RES => {
3895 let value: WireSoftKeyTemplate = decode(frame.message_id, p)?;
3896 let actions = value
3897 .definitions
3898 .iter()
3899 .filter(|definition| definition.event != 0)
3900 .map(|definition| SoftKey::from(definition.event))
3901 .collect();
3902 Ok(Self::SoftKeyTemplate { actions })
3903 }
3904 wire_id::SOFT_KEY_SET_RES => {
3905 let value: WireSoftKeySet = decode(frame.message_id, p)?;
3906 let profile =
3907 SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
3908 let actions = value
3909 .sets
3910 .get(mode.wire_value() as usize)
3911 .map(|set| {
3912 set.template_indexes
3913 .iter()
3914 .copied()
3915 .take_while(|index| *index != 0)
3916 .map(|index| SoftKey::from(u32::from(index)))
3917 .collect()
3918 })
3919 .unwrap_or_default();
3920 (mode, actions)
3921 }))?;
3922 Ok(Self::SoftKeySet { profile })
3923 }
3924 wire_id::SELECT_SOFT_KEYS => {
3925 let value: WireSelectSoftKeys = decode(frame.message_id, p)?;
3926 Ok(Self::SelectSoftKeys {
3927 line_instance: value.line_instance,
3928 call_reference: value.call_reference,
3929 set: KeyMode::from(value.set),
3930 valid_mask: value.valid_mask,
3931 })
3932 }
3933 wire_id::CALL_STATE => {
3934 let value: WireCallState = decode(frame.message_id, p)?;
3935 Ok(Self::CallState {
3936 state: CallState::from(value.state),
3937 line_instance: value.line_instance,
3938 call_reference: value.call_reference,
3939 })
3940 }
3941 wire_id::CALL_INFO => {
3942 let value: WireCallInfo = decode(frame.message_id, p)?;
3943 let call_type = super::values::CallType::from(value.call_type);
3944 Ok(Self::CallInfo {
3945 info: CallInfo {
3946 direction: match call_type {
3947 super::values::CallType::Inbound => {
3948 crate::types::CallDirection::Inbound
3949 }
3950 _ => crate::types::CallDirection::Outbound,
3951 },
3952 calling_name: value.calling_name.text()?,
3953 calling_number: value.calling_number.text()?,
3954 called_name: value.called_name.text()?,
3955 called_number: value.called_number.text()?,
3956 original_called_name: value.original_called_name.text()?,
3957 original_called_number: value.original_called_number.text()?,
3958 last_redirecting_name: value.last_redirecting_name.text()?,
3959 last_redirecting_number: value.last_redirecting_number.text()?,
3960 original_redirect_reason: value.original_redirect_reason,
3961 last_redirect_reason: value.last_redirect_reason,
3962 party_restrictions: value.party_restrictions,
3963 },
3964 line_instance: value.line_instance,
3965 call_reference: value.call_reference,
3966 })
3967 }
3968 wire_id::CALL_INFO_DYNAMIC => decode_dynamic_call_info(p, protocol),
3969 wire_id::DISPLAY_PROMPT_STATUS => {
3970 let value: WirePromptStatus = decode(frame.message_id, p)?;
3971 Ok(Self::DisplayPrompt {
3972 timeout_seconds: value.timeout_seconds,
3973 text: value.text.text()?,
3974 line_instance: value.line_instance,
3975 call_reference: value.call_reference,
3976 })
3977 }
3978 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS => {
3979 const HEADER_SIZE: usize = 12;
3980 if p.len() < HEADER_SIZE {
3981 return Err(CodecError::Truncated {
3982 message_id: frame.message_id,
3983 needed: HEADER_SIZE,
3984 actual: p.len(),
3985 });
3986 }
3987 let value: WireDynamicPromptHeader = decode(frame.message_id, &p[..HEADER_SIZE])?;
3988 Ok(Self::DisplayPrompt {
3989 timeout_seconds: value.timeout_seconds,
3990 text: decode_dynamic_text(frame.message_id, p, HEADER_SIZE)?,
3991 line_instance: value.line_instance,
3992 call_reference: value.call_reference,
3993 })
3994 }
3995 wire_id::CLEAR_PROMPT_STATUS => {
3996 let value: WireLineCall = decode(frame.message_id, p)?;
3997 Ok(Self::ClearPrompt {
3998 line_instance: value.line_instance,
3999 call_reference: value.call_reference,
4000 })
4001 }
4002 wire_id::DISPLAY_NOTIFY => {
4003 let value: WireNotify = decode(frame.message_id, p)?;
4004 Ok(Self::DisplayNotify {
4005 timeout_seconds: value.timeout_seconds,
4006 text: value.text.text()?,
4007 })
4008 }
4009 wire_id::DISPLAY_DYNAMIC_NOTIFY => {
4010 const HEADER_SIZE: usize = 4;
4011 if p.len() < HEADER_SIZE {
4012 return Err(CodecError::Truncated {
4013 message_id: frame.message_id,
4014 needed: HEADER_SIZE,
4015 actual: p.len(),
4016 });
4017 }
4018 let value: WireDynamicNotifyHeader = decode(frame.message_id, &p[..HEADER_SIZE])?;
4019 Ok(Self::DisplayNotify {
4020 timeout_seconds: value.timeout_seconds,
4021 text: decode_dynamic_text(frame.message_id, p, HEADER_SIZE)?,
4022 })
4023 }
4024 wire_id::CLEAR_NOTIFY => Ok(Self::ClearNotify),
4025 wire_id::DISPLAY_PRIORITY_NOTIFY => {
4026 let value: WirePriorityNotify = decode(frame.message_id, p)?;
4027 Ok(Self::DisplayPriorityNotify {
4028 timeout_seconds: value.timeout_seconds,
4029 priority: NotificationPriority::from(value.priority),
4030 text: value.text.text()?,
4031 })
4032 }
4033 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY => {
4034 const HEADER_SIZE: usize = 8;
4035 if p.len() < HEADER_SIZE {
4036 return Err(CodecError::Truncated {
4037 message_id: frame.message_id,
4038 needed: HEADER_SIZE,
4039 actual: p.len(),
4040 });
4041 }
4042 let value: WireDynamicPriorityNotifyHeader =
4043 decode(frame.message_id, &p[..HEADER_SIZE])?;
4044 Ok(Self::DisplayPriorityNotify {
4045 timeout_seconds: value.timeout_seconds,
4046 priority: NotificationPriority::from(value.priority),
4047 text: decode_dynamic_text(frame.message_id, p, HEADER_SIZE)?,
4048 })
4049 }
4050 wire_id::CLEAR_PRIORITY_NOTIFY => {
4051 let value: WireOneWord = decode(frame.message_id, p)?;
4052 Ok(Self::ClearPriorityNotify {
4053 priority: NotificationPriority::from(value.value),
4054 })
4055 }
4056 wire_id::NOTIFY_DTMF_TONE | wire_id::SEND_DTMF_TONE => {
4057 let value: WireDtmfToneControl = decode(frame.message_id, p)?;
4058 let message = DtmfToneControl {
4059 tone: Tone::from(value.tone),
4060 conference_id: value.conference_id.into(),
4061 passthrough_party_id: value.passthrough_party_id,
4062 };
4063 if frame.message_id == wire_id::NOTIFY_DTMF_TONE {
4064 Ok(Self::NotifyDtmfTone(message))
4065 } else {
4066 Ok(Self::SendDtmfTone(message))
4067 }
4068 }
4069 wire_id::START_ANNOUNCEMENT => {
4070 const PAYLOAD_SIZE: usize = 464;
4071 validate_exact_payload(p, frame.message_id, PAYLOAD_SIZE)?;
4072 let value: WireStartAnnouncement = decode(frame.message_id, p)?;
4073 let mut announcements = value
4074 .announcements
4075 .into_iter()
4076 .map(|entry| AnnouncementEntry {
4077 locale: entry.locale,
4078 country: entry.country,
4079 tone: Tone::from(entry.tone),
4080 })
4081 .collect::<Vec<_>>();
4082 while announcements.last().is_some_and(|entry| {
4083 entry.locale == 0 && entry.country == 0 && entry.tone.wire_value() == 0
4084 }) {
4085 announcements.pop();
4086 }
4087 let mut matrix_conference_party_ids = value.matrix_conference_party_ids.to_vec();
4088 while matrix_conference_party_ids.last() == Some(&0) {
4089 matrix_conference_party_ids.pop();
4090 }
4091 Ok(Self::StartAnnouncement {
4092 announcements,
4093 end_of_ack: value.end_of_ack,
4094 conference_id: value.conference_id,
4095 matrix_conference_party_ids,
4096 hearing_conference_party_mask: value.hearing_conference_party_mask,
4097 play_mode: value.play_mode,
4098 })
4099 }
4100 wire_id::STOP_ANNOUNCEMENT => {
4101 validate_exact_payload(p, frame.message_id, 4)?;
4102 let value: WireOneWord = decode(frame.message_id, p)?;
4103 Ok(Self::StopAnnouncement {
4104 conference_id: value.value,
4105 })
4106 }
4107 wire_id::ANNOUNCEMENT_FINISH => {
4108 validate_exact_payload(p, frame.message_id, 8)?;
4109 let value: WireAnnouncementFinish = decode(frame.message_id, p)?;
4110 Ok(Self::AnnouncementFinish {
4111 conference_id: value.conference_id,
4112 play_status: value.play_status,
4113 })
4114 }
4115 wire_id::CLEAR_CONFERENCE => {
4116 validate_exact_payload(p, frame.message_id, 8)?;
4117 let value: WireCallParty = decode(frame.message_id, p)?;
4118 Ok(Self::ClearConference {
4119 conference_id: value.call_reference.into(),
4120 service_number: value.passthrough_party_id,
4121 })
4122 }
4123 wire_id::CREATE_CONFERENCE_REQ => {
4124 validate_conference_data_length(p, frame.message_id, 76, 72)?;
4125 let value: WireCreateConferenceRequest = decode_zero_padded(frame.message_id, p)?;
4126 Ok(Self::CreateConferenceRequest(CreateConferenceRequest {
4127 conference_id: value.conference_id.into(),
4128 reserved_participants: value.reserved_participants,
4129 resource_type: ConferenceResourceType::from(value.resource_type),
4130 application_id: value.application_id.into(),
4131 application_conference_id: value.application_conference_id.text()?,
4132 application_data: value.application_data.text()?,
4133 passthrough_data: value.passthrough_data,
4134 }))
4135 }
4136 wire_id::DELETE_CONFERENCE_REQ => {
4137 validate_exact_payload(p, frame.message_id, 4)?;
4138 let value: WireOneWord = decode(frame.message_id, p)?;
4139 Ok(Self::DeleteConferenceRequest {
4140 conference_id: value.value.into(),
4141 })
4142 }
4143 wire_id::MODIFY_CONFERENCE_REQ => {
4144 validate_conference_data_length(p, frame.message_id, 72, 68)?;
4145 let value: WireModifyConferenceRequest = decode_zero_padded(frame.message_id, p)?;
4146 Ok(Self::ModifyConferenceRequest(ModifyConferenceRequest {
4147 conference_id: value.conference_id.into(),
4148 reserved_participants: value.reserved_participants,
4149 application_id: value.application_id.into(),
4150 application_conference_id: value.application_conference_id.text()?,
4151 application_data: value.application_data.text()?,
4152 passthrough_data: value.passthrough_data,
4153 }))
4154 }
4155 wire_id::AUDIT_CONFERENCE_REQ => {
4156 validate_exact_payload(p, frame.message_id, 0)?;
4157 Ok(Self::AuditConferenceRequest)
4158 }
4159 wire_id::ADD_PARTICIPANT_REQ => {
4160 let (conference_id, participant) = decode_participant_request(p, frame.message_id)?;
4161 Ok(Self::AddParticipantRequest(AddParticipantRequest {
4162 conference_id,
4163 participant,
4164 }))
4165 }
4166 wire_id::DROP_PARTICIPANT_REQ => {
4167 validate_exact_payload(p, frame.message_id, 8)?;
4168 let value: WireCallParty = decode(frame.message_id, p)?;
4169 Ok(Self::DropParticipantRequest {
4170 conference_id: value.call_reference.into(),
4171 call_reference: value.passthrough_party_id.into(),
4172 })
4173 }
4174 wire_id::AUDIT_PARTICIPANT_REQ => {
4175 validate_exact_payload(p, frame.message_id, 4)?;
4176 let value: WireOneWord = decode(frame.message_id, p)?;
4177 Ok(Self::AuditParticipantRequest {
4178 conference_id: value.value.into(),
4179 })
4180 }
4181 wire_id::CHANGE_PARTICIPANT_REQ => {
4182 let (conference_id, participant) = decode_participant_request(p, frame.message_id)?;
4183 Ok(Self::ChangeParticipantRequest(ChangeParticipantRequest {
4184 conference_id,
4185 participant,
4186 }))
4187 }
4188 wire_id::STOP_MULTIMEDIA_TRANSMISSION | wire_id::CLOSE_MULTIMEDIA_RECEIVE_CHANNEL => {
4189 let value: WireMultimediaStreamControl = decode(frame.message_id, p)?;
4190 let message = MultimediaStreamControl {
4191 conference_id: value.conference_id.into(),
4192 passthrough_party_id: value.passthrough_party_id.into(),
4193 call_reference: value.call_reference.into(),
4194 port_handling_flag: value.port_handling_flag,
4195 };
4196 if frame.message_id == wire_id::STOP_MULTIMEDIA_TRANSMISSION {
4197 Ok(Self::StopMultimediaTransmission(message))
4198 } else {
4199 Ok(Self::CloseMultimediaReceiveChannel(message))
4200 }
4201 }
4202 wire_id::FLOW_CONTROL_COMMAND | wire_id::FLOW_CONTROL_NOTIFY => {
4203 let value: WireVideoFlowControl = decode(frame.message_id, p)?;
4204 let message = VideoFlowControl {
4205 conference_id: value.conference_id.into(),
4206 passthrough_party_id: value.passthrough_party_id.into(),
4207 call_reference: value.call_reference.into(),
4208 maximum_bit_rate: value.maximum_bit_rate,
4209 };
4210 if frame.message_id == wire_id::FLOW_CONTROL_COMMAND {
4211 Ok(Self::FlowControlCommand(message))
4212 } else {
4213 Ok(Self::FlowControlNotify(message))
4214 }
4215 }
4216 wire_id::VIDEO_DISPLAY_COMMAND => {
4217 let value: WireVideoDisplayCommand = decode(frame.message_id, p)?;
4218 Ok(Self::VideoDisplayCommand {
4219 conference_id: value.conference_id.into(),
4220 call_reference: value.call_reference.into(),
4221 layout_id: value.layout_id,
4222 })
4223 }
4224 wire_id::ACTIVATE_CALL_PLANE => {
4225 let value: WireOneWord = decode(frame.message_id, p)?;
4226 Ok(Self::ActivateCallPlane {
4227 line_instance: value.value,
4228 })
4229 }
4230 wire_id::DEACTIVATE_CALL_PLANE => Ok(Self::DeactivateCallPlane),
4231 wire_id::BACKSPACE_RESPONSE => {
4232 let value: WireLineCall = decode(frame.message_id, p)?;
4233 Ok(Self::BackspaceResponse {
4234 line_instance: value.line_instance,
4235 call_reference: value.call_reference,
4236 })
4237 }
4238 wire_id::REGISTER_TOKEN_ACK => Ok(Self::RegisterTokenAck),
4239 wire_id::REGISTER_TOKEN_REJECT => {
4240 let value: WireOneWord = decode(frame.message_id, p)?;
4241 Ok(Self::RegisterTokenReject {
4242 backoff_seconds: value.value,
4243 })
4244 }
4245 wire_id::SPCP_REGISTER_TOKEN_ACK => {
4246 let value: WireOneWord = decode(frame.message_id, p)?;
4247 Ok(Self::SpcpRegisterTokenAck {
4248 features: value.value,
4249 })
4250 }
4251 wire_id::SPCP_REGISTER_TOKEN_REJECT => {
4252 let value: WireOneWord = decode(frame.message_id, p)?;
4253 Ok(Self::SpcpRegisterTokenReject {
4254 backoff_seconds: value.value,
4255 })
4256 }
4257 wire_id::SET_RINGER => {
4258 let value: WireModeLineCall = decode(frame.message_id, p)?;
4259 Ok(Self::SetRinger {
4260 mode: RingerMode::from(value.mode),
4261 duration: RingDuration::from(value.duration),
4262 line_instance: value.line_instance,
4263 call_reference: value.call_reference,
4264 })
4265 }
4266 wire_id::SET_LAMP => {
4267 let value: WireLampState = decode(frame.message_id, p)?;
4268 Ok(Self::SetLamp {
4269 stimulus: ButtonType::from(value.stimulus),
4270 instance: value.instance,
4271 mode: LampMode::from(value.mode),
4272 })
4273 }
4274 wire_id::SET_HOOK_FLASH_DETECT => {
4275 validate_exact_payload(p, frame.message_id, 0)?;
4276 Ok(Self::SetHookFlashDetect)
4277 }
4278 wire_id::START_TONE => {
4279 let value: WireToneLineCall = decode(frame.message_id, p)?;
4280 Ok(Self::StartTone {
4281 tone: Tone::from(value.tone),
4282 direction: ToneDirection::from(value.direction),
4283 line_instance: value.line_instance,
4284 call_reference: value.call_reference,
4285 })
4286 }
4287 wire_id::STOP_TONE => {
4288 let (line_instance, call_reference) = match protocol.wire() {
4289 12.. => {
4290 let value: WireStopToneV12 = decode(frame.message_id, p)?;
4291 (value.line_instance, value.call_reference)
4292 }
4293 _ => {
4294 let value: WireLineCall = decode(frame.message_id, p)?;
4295 (value.line_instance, value.call_reference)
4296 }
4297 };
4298 Ok(Self::StopTone {
4299 line_instance,
4300 call_reference,
4301 })
4302 }
4303 wire_id::START_MULTICAST_MEDIA_RECEPTION => {
4304 decode_start_multicast_reception(p, protocol, frame.message_id)
4305 }
4306 wire_id::START_MULTICAST_MEDIA_TRANSMISSION => {
4307 decode_start_multicast_transmission(p, protocol, frame.message_id)
4308 }
4309 wire_id::STOP_MULTICAST_MEDIA_RECEPTION
4310 | wire_id::STOP_MULTICAST_MEDIA_TRANSMISSION => {
4311 validate_exact_payload(p, frame.message_id, 12)?;
4312 let value: WireStopMulticast = decode(frame.message_id, p)?;
4313 if frame.message_id == wire_id::STOP_MULTICAST_MEDIA_RECEPTION {
4314 Ok(Self::StopMulticastMediaReception {
4315 conference_id: value.conference_id.into(),
4316 passthrough_party_id: value.passthrough_party_id.into(),
4317 call_reference: value.call_reference.into(),
4318 })
4319 } else {
4320 Ok(Self::StopMulticastMediaTransmission {
4321 conference_id: value.conference_id.into(),
4322 passthrough_party_id: value.passthrough_party_id.into(),
4323 call_reference: value.call_reference.into(),
4324 })
4325 }
4326 }
4327 wire_id::OPEN_RECEIVE_CHANNEL => decode_open_receive(p, protocol, frame.message_id),
4328 wire_id::CLOSE_RECEIVE_CHANNEL => {
4329 validate_exact_payload(p, frame.message_id, 16)?;
4330 let value: WireAudioStreamControl = decode(frame.message_id, p)?;
4331 Ok(Self::CloseReceiveChannel(AudioStreamControl {
4332 conference_id: value.conference_id.into(),
4333 passthrough_party_id: value.passthrough_party_id.into(),
4334 call_reference: value.call_reference.into(),
4335 port_handling_flag: value.port_handling_flag,
4336 }))
4337 }
4338 wire_id::CONNECTION_STATISTICS_REQ => match protocol.wire() {
4339 19.. => decode_connection_statistics_request::<25, 3>(p, frame.message_id),
4340 _ => decode_connection_statistics_request::<24, 0>(p, frame.message_id),
4341 },
4342 wire_id::START_MEDIA_TRANSMISSION => decode_start_media(p, protocol, frame.message_id),
4343 wire_id::STOP_MEDIA_TRANSMISSION => {
4344 validate_exact_payload(p, frame.message_id, 16)?;
4345 let value: WireAudioStreamControl = decode(frame.message_id, p)?;
4346 Ok(Self::StopMediaTransmission(AudioStreamControl {
4347 conference_id: value.conference_id.into(),
4348 passthrough_party_id: value.passthrough_party_id.into(),
4349 call_reference: value.call_reference.into(),
4350 port_handling_flag: value.port_handling_flag,
4351 }))
4352 }
4353 wire_id::START_MEDIA_RECEPTION => {
4354 validate_exact_payload(p, frame.message_id, 0)?;
4355 Ok(Self::StartMediaReception)
4356 }
4357 wire_id::STOP_MEDIA_RECEPTION => {
4358 let value: WireStopMediaReception = decode(frame.message_id, p)?;
4359 Ok(Self::StopMediaReception {
4360 conference_id: value.conference_id.into(),
4361 passthrough_party_id: value.passthrough_party_id.into(),
4362 })
4363 }
4364 wire_id::SET_SPEAKER_MODE => {
4365 let value: WireOneWord = decode(frame.message_id, p)?;
4366 Ok(Self::SetSpeakerMode(SpeakerMode::from(value.value)))
4367 }
4368 wire_id::SET_MICROPHONE_MODE => {
4369 let value: WireOneWord = decode(frame.message_id, p)?;
4370 Ok(Self::SetMicrophoneMode(MicrophoneMode::from(value.value)))
4371 }
4372 wire_id::RESET => {
4373 let value: WireOneWord = decode(frame.message_id, p)?;
4374 Ok(Self::Reset(ResetType::from(value.value)))
4375 }
4376 wire_id::DISPLAY_TEXT => {
4377 let value: WireFixedText<32> = decode(frame.message_id, p)?;
4378 Ok(Self::DisplayText {
4379 text: value.text()?,
4380 })
4381 }
4382 wire_id::CLEAR_DISPLAY => Ok(Self::ClearDisplay),
4383 wire_id::FORWARD_STAT => match protocol.wire() {
4384 19.. => decode_forward_status::<25, 3>(p, frame.message_id),
4385 _ => decode_forward_status::<24, 0>(p, frame.message_id),
4386 },
4387 wire_id::SPEED_DIAL_STAT => {
4388 let value: WireSpeedDialStatus = decode(frame.message_id, p)?;
4389 Ok(Self::SpeedDialStatus {
4390 instance: value.instance,
4391 number: value.number.text()?,
4392 display_name: value.display_name.text()?,
4393 })
4394 }
4395 wire_id::SPEED_DIAL_STAT_DYNAMIC => decode_dynamic_speed_dial_status(p),
4396 wire_id::START_MEDIA_FAILURE_DETECTION => {
4397 let value: WireMediaFailureDetection = decode(frame.message_id, p)?;
4398 Ok(Self::StartMediaFailureDetection(MediaFailureDetection {
4399 conference_id: value.conference_id.into(),
4400 passthrough_party_id: value.passthrough_party_id,
4401 packet_millis: value.packet_millis,
4402 codec: Codec::from(value.codec),
4403 echo_cancellation: EchoCancellation::from(value.echo_cancellation),
4404 codec_qualifier: value.codec_qualifier,
4405 call_reference: value.call_reference.into(),
4406 }))
4407 }
4408 wire_id::OPEN_MULTIMEDIA_CHANNEL => {
4409 decode_open_multimedia(p, protocol, frame.message_id)
4410 .map(Self::OpenMultimediaChannel)
4411 }
4412 wire_id::START_MULTIMEDIA_TRANSMISSION => {
4413 decode_start_multimedia(p, protocol, frame.message_id)
4414 .map(Self::StartMultimediaTransmission)
4415 }
4416 wire_id::MISCELLANEOUS_COMMAND => {
4417 decode_miscellaneous_command(p, frame.message_id).map(Self::MiscellaneousCommand)
4418 }
4419 wire_id::DIALED_NUMBER => match protocol.wire() {
4420 19.. => decode_dialed_number::<25, 3>(p, frame.message_id),
4421 _ => decode_dialed_number::<24, 0>(p, frame.message_id),
4422 },
4423 wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ => {
4424 let value: WireDtmfPayloadRequest = decode(frame.message_id, p)?;
4425 Ok(Self::SubscribeDtmfPayloadRequest(
4426 dtmf_payload_request_from_wire(value),
4427 ))
4428 }
4429 wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR => {
4430 let value: WireDtmfPayloadIdentity = decode(frame.message_id, p)?;
4431 Ok(Self::SubscribeDtmfPayloadError(
4432 dtmf_payload_identity_from_wire(value),
4433 ))
4434 }
4435 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ => {
4436 let value: WireDtmfPayloadRequest = decode(frame.message_id, p)?;
4437 Ok(Self::UnsubscribeDtmfPayloadRequest(
4438 dtmf_payload_request_from_wire(value),
4439 ))
4440 }
4441 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR => {
4442 let value: WireDtmfPayloadIdentity = decode(frame.message_id, p)?;
4443 Ok(Self::UnsubscribeDtmfPayloadError(
4444 dtmf_payload_identity_from_wire(value),
4445 ))
4446 }
4447 wire_id::USER_TO_DEVICE_DATA => {
4448 decode_user_data(p, frame.message_id).map(Self::UserToDeviceData)
4449 }
4450 wire_id::USER_TO_DEVICE_DATA_V1 => {
4451 decode_user_data_v1(p, frame.message_id).map(Self::UserToDeviceDataV1)
4452 }
4453 wire_id::FEATURE_STAT => {
4454 let value: WireFeatureStatus = decode(frame.message_id, p)?;
4455 Ok(Self::FeatureStatus {
4456 instance: value.instance,
4457 button_type: ButtonType::from(value.button_type),
4458 label: value.label.text()?,
4459 state: value.state,
4460 })
4461 }
4462 wire_id::FEATURE_STAT_DYNAMIC => {
4463 let value: WireFeatureStatusDynamic = decode(frame.message_id, p)?;
4464 Ok(Self::FeatureStatus {
4465 instance: value.instance,
4466 button_type: ButtonType::from(value.button_type),
4467 label: value.label.text()?,
4468 state: value.state,
4469 })
4470 }
4471 wire_id::SERVICE_URL_STAT => {
4472 let value: WireServiceUrlStatus = decode(frame.message_id, p)?;
4473 Ok(Self::ServiceUrlStatus {
4474 index: value.index,
4475 url: value.url.text()?,
4476 label: value.label.text()?,
4477 extension_text: String::new(),
4478 })
4479 }
4480 wire_id::SERVICE_URL_STAT_DYNAMIC => decode_dynamic_service_url_status(p, protocol),
4481 wire_id::CALL_SELECT_STAT => {
4482 let value: WireCallSelectStatus = decode(frame.message_id, p)?;
4483 Ok(Self::CallSelectStatus {
4484 status: value.status,
4485 call_reference: value.call_reference,
4486 line_instance: value.line_instance,
4487 })
4488 }
4489 wire_id::PORT_REQUEST => {
4490 let request = match protocol.wire() {
4491 20.. => {
4492 let value: WirePortRequestV20 = decode(frame.message_id, p)?;
4493 PortRequest {
4494 conference_id: value.base.conference_id.into(),
4495 call_reference: value.base.call_reference.into(),
4496 passthrough_party_id: value.base.passthrough_party_id.into(),
4497 transport: MediaTransport::from(value.base.transport),
4498 address_type: Some(IpAddressType::from(value.address_type)),
4499 media_type: Some(MediaType::from(value.media_type)),
4500 }
4501 }
4502 _ => {
4503 let value: WirePortRequest = decode(frame.message_id, p)?;
4504 PortRequest {
4505 conference_id: value.conference_id.into(),
4506 call_reference: value.call_reference.into(),
4507 passthrough_party_id: value.passthrough_party_id.into(),
4508 transport: MediaTransport::from(value.transport),
4509 address_type: None,
4510 media_type: None,
4511 }
4512 }
4513 };
4514 Ok(Self::PortRequest(request))
4515 }
4516 wire_id::PORT_CLOSE => {
4517 let close = match protocol.wire() {
4518 20.. => {
4519 let value: WirePortCloseV20 = decode(frame.message_id, p)?;
4520 PortClose {
4521 conference_id: value.base.conference_id.into(),
4522 call_reference: value.base.call_reference.into(),
4523 passthrough_party_id: value.base.passthrough_party_id.into(),
4524 media_type: Some(MediaType::from(value.media_type)),
4525 }
4526 }
4527 _ => {
4528 let value: WirePortClose = decode(frame.message_id, p)?;
4529 PortClose {
4530 conference_id: value.conference_id.into(),
4531 call_reference: value.call_reference.into(),
4532 passthrough_party_id: value.passthrough_party_id.into(),
4533 media_type: None,
4534 }
4535 }
4536 };
4537 Ok(Self::PortClose(close))
4538 }
4539 wire_id::SUBSCRIPTION_STAT => {
4540 let value: WireSubscriptionStatus = decode(frame.message_id, p)?;
4541 Ok(Self::SubscriptionStatus {
4542 transaction_id: value.transaction_id,
4543 feature_id: value.feature_id,
4544 timer_seconds: value.timer_seconds,
4545 cause: SubscriptionCause::from(value.cause),
4546 })
4547 }
4548 wire_id::NOTIFICATION => {
4549 let value: WireNotification = decode(frame.message_id, p)?;
4550 Ok(Self::Notification {
4551 transaction_id: value.transaction_id,
4552 feature_id: value.feature_id,
4553 status: BusyLampFieldState::from(value.status),
4554 text: value.text.text()?,
4555 })
4556 }
4557 wire_id::CALL_HISTORY_DISPOSITION => {
4558 let value: WireCallHistoryDisposition = decode(frame.message_id, p)?;
4559 Ok(Self::CallHistoryDisposition {
4560 disposition: CallHistoryDisposition::from(value.disposition),
4561 line_instance: value.line_instance,
4562 call_reference: value.call_reference,
4563 })
4564 }
4565 wire_id::CALL_COUNT_RES => {
4566 let value: WireCallCountResponse = decode(frame.message_id, p)?;
4567 let line_data_entries = usize_from_wire(
4568 frame.message_id,
4569 "call-count line data",
4570 value.line_data_entries,
4571 )?;
4572 if line_data_entries > CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES {
4573 return Err(CodecError::CountTooLarge {
4574 message_id: frame.message_id,
4575 field: "call-count line data",
4576 count: line_data_entries,
4577 maximum: CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES,
4578 });
4579 }
4580 Ok(Self::CallCountResponse(CallCountResponse {
4581 total_configured_lines: value.total_configured_lines,
4582 starting_line_instance: value.starting_line_instance,
4583 line_data: value
4584 .line_data
4585 .into_iter()
4586 .take(line_data_entries)
4587 .map(|entry| CallCountLineData {
4588 max_calls: entry.max_calls,
4589 busy_trigger: entry.busy_trigger,
4590 })
4591 .collect(),
4592 }))
4593 }
4594 wire_id::RECORDING_STATUS => {
4595 let value: WireRecordingStatus = decode(frame.message_id, p)?;
4596 Ok(Self::RecordingStatus {
4597 call_reference: value.call_reference,
4598 active: decode_bool_word(value.active, frame.message_id, "recording active")?,
4599 })
4600 }
4601 _ => {
4602 let message_type = frame.message_type();
4603 if message_type.is_known() {
4604 preserve_known_message(frame, message_type).map(Self::KnownOpaque)
4605 } else {
4606 Ok(Self::Unknown(RawMessage {
4607 message_id: frame.message_id,
4608 protocol_version: frame.protocol_version,
4609 payload: frame.payload,
4610 }))
4611 }
4612 }
4613 }
4614 }
4615
4616 pub fn encode(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
4621 self.encode_for_session(protocol.into())
4622 }
4623
4624 pub fn encode_for_session(
4626 &self,
4627 session: StationSessionContext,
4628 ) -> Result<Vec<u8>, CodecError> {
4629 let (message_id, payload, header_protocol) = self.payload(session, None)?;
4630 reject_non_station_route(
4631 message_id,
4632 MessageRoute::ControlToStation,
4633 "control-to-station",
4634 )?;
4635 Frame::new(header_protocol, message_id, payload).encode()
4636 }
4637
4638 fn encode_unchecked(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
4639 let (message_id, payload, header_protocol) = self.payload(protocol.into(), None)?;
4640 Frame::new(header_protocol, message_id, payload).encode()
4641 }
4642
4643 pub fn encode_for_legacy_station(
4648 &self,
4649 protocol: ProtocolVersion,
4650 code_page: LegacyCodePage,
4651 ) -> Result<Vec<u8>, CodecError> {
4652 self.encode_for_legacy_session(protocol.into(), code_page)
4653 }
4654
4655 pub fn encode_for_legacy_session(
4658 &self,
4659 session: StationSessionContext,
4660 code_page: LegacyCodePage,
4661 ) -> Result<Vec<u8>, CodecError> {
4662 let (message_id, payload, header_protocol) = self.payload(session, Some(code_page))?;
4663 reject_non_station_route(
4664 message_id,
4665 MessageRoute::ControlToStation,
4666 "control-to-station",
4667 )?;
4668 Frame::new(header_protocol, message_id, payload).encode()
4669 }
4670
4671 fn payload(
4672 &self,
4673 session: StationSessionContext,
4674 legacy_code_page: Option<LegacyCodePage>,
4675 ) -> Result<(u32, Vec<u8>, u32), CodecError> {
4676 let protocol = session.protocol;
4677 let mut p = Vec::new();
4678 let id = match self {
4679 Self::RegisterAck {
4680 keepalive_seconds,
4681 secondary_keepalive_seconds,
4682 protocol,
4683 features,
4684 date_template,
4685 } => {
4686 if date_template.as_str().len() > 6 {
4687 return Err(CodecError::TextTooLong {
4688 message_id: wire_id::REGISTER_ACK,
4689 field: "date template",
4690 actual: date_template.as_str().len(),
4691 maximum: 6,
4692 });
4693 }
4694 let mut wire_date_template = [0_u8; 6];
4695 wire_date_template[..date_template.as_str().len()]
4696 .copy_from_slice(date_template.as_str().as_bytes());
4697 p = encode(
4698 wire_id::REGISTER_ACK,
4699 &WireRegisterAck {
4700 keepalive_seconds: *keepalive_seconds,
4701 date_template: wire_date_template,
4702 alignment: [0; 2],
4703 secondary_keepalive_seconds: *secondary_keepalive_seconds,
4704 protocol_features: {
4705 let mut bytes = features.bits().to_le_bytes();
4706 bytes[0] = protocol.wire() as u8;
4707 bytes
4708 },
4709 },
4710 )?;
4711 return Ok((wire_id::REGISTER_ACK, p, 0));
4712 }
4713 Self::RegisterReject { reason } => {
4714 p = encode(
4715 wire_id::REGISTER_REJECT,
4716 &WireFixedText::<33>::new(wire_id::REGISTER_REJECT, "reject reason", reason)?,
4717 )?;
4718 pad_dynamic_payload(&mut p);
4719 wire_id::REGISTER_REJECT
4720 }
4721 Self::KeepAliveAck => return Ok((wire_id::KEEP_ALIVE_ACK, p, 0)),
4722 Self::UnregisterAck => {
4723 p = encode(wire_id::UNREGISTER_ACK, &WireOneWord { value: 0 })?;
4724 return Ok((wire_id::UNREGISTER_ACK, p, 0));
4725 }
4726 Self::CapabilitiesRequest => wire_id::CAPABILITIES_REQ,
4727 Self::EnunciatorCommand => wire_id::ENUNCIATOR_COMMAND,
4728 Self::ConfigStatus(status) => {
4729 if session.uses_dynamic_general_ui() {
4730 p = encode_dynamic_config_status(status)?;
4731 wire_id::CONFIG_STAT_DYNAMIC
4732 } else {
4733 p = encode(
4734 wire_id::CONFIG_STAT,
4735 &WireConfigStatus {
4736 device_id: WireFixedText::new(
4737 wire_id::CONFIG_STAT,
4738 "device ID",
4739 &status.device_name,
4740 )?,
4741 station_user_id: status.station_user_id,
4742 station_instance: status.station_instance,
4743 user_name: WireFixedText::new_station(
4744 wire_id::CONFIG_STAT,
4745 "user name",
4746 &status.user_name,
4747 legacy_code_page,
4748 )?,
4749 server_name: WireFixedText::new_station(
4750 wire_id::CONFIG_STAT,
4751 "server name",
4752 &status.server_name,
4753 legacy_code_page,
4754 )?,
4755 line_count: status.line_count,
4756 speed_dial_count: status.speed_dial_count,
4757 },
4758 )?;
4759 wire_id::CONFIG_STAT
4760 }
4761 }
4762 Self::LineStatus {
4763 instance,
4764 directory_number,
4765 fully_qualified_display_name,
4766 display_label,
4767 } => {
4768 if session.uses_dynamic_general_ui() {
4769 p = encode_dynamic_line_status(
4770 *instance,
4771 directory_number,
4772 fully_qualified_display_name,
4773 display_label,
4774 legacy_code_page,
4775 )?;
4776 wire_id::LINE_STAT_DYNAMIC
4777 } else {
4778 p = encode(
4779 wire_id::LINE_STAT,
4780 &WireLineStatus {
4781 line_instance: *instance,
4782 directory_number: WireFixedText::new(
4783 wire_id::LINE_STAT,
4784 "line number",
4785 directory_number,
4786 )?,
4787 display_name: WireFixedText::new_station(
4788 wire_id::LINE_STAT,
4789 "display name",
4790 fully_qualified_display_name,
4791 legacy_code_page,
4792 )?,
4793 display_label: WireFixedText::new_station(
4794 wire_id::LINE_STAT,
4795 "line label",
4796 display_label,
4797 legacy_code_page,
4798 )?,
4799 reserved: 0,
4800 },
4801 )?;
4802 wire_id::LINE_STAT
4803 }
4804 }
4805 Self::ButtonTemplate {
4806 offset,
4807 total,
4808 buttons,
4809 } => {
4810 if buttons.len() > BUTTON_TEMPLATE_ENTRIES_PER_CHUNK {
4811 return Err(CodecError::CountTooLarge {
4812 message_id: wire_id::BUTTON_TEMPLATE,
4813 field: "button definitions",
4814 count: buttons.len(),
4815 maximum: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK,
4816 });
4817 }
4818 let count = u32::try_from(buttons.len()).map_err(|_| CodecError::InvalidValue {
4819 message_id: wire_id::BUTTON_TEMPLATE,
4820 field: "button definitions in message",
4821 value: buttons.len() as u64,
4822 })?;
4823 if offset.checked_add(count).is_none_or(|end| end > *total) {
4824 return Err(CodecError::InvalidValue {
4825 message_id: wire_id::BUTTON_TEMPLATE,
4826 field: "button template range",
4827 value: u64::from(*offset) + u64::from(count),
4828 });
4829 }
4830 let mut definitions =
4831 [WireButtonDefinition::default(); BUTTON_TEMPLATE_ENTRIES_PER_CHUNK];
4832 for (index, button) in buttons.iter().enumerate() {
4833 definitions[index] = WireButtonDefinition {
4834 instance: u8::try_from(button.instance).map_err(|_| {
4835 CodecError::InvalidValue {
4836 message_id: wire_id::BUTTON_TEMPLATE,
4837 field: "button instance",
4838 value: u64::from(button.instance),
4839 }
4840 })?,
4841 button_type: u8::try_from(button.button_type.wire_value()).map_err(
4842 |_| CodecError::InvalidValue {
4843 message_id: wire_id::BUTTON_TEMPLATE,
4844 field: "button type",
4845 value: u64::from(button.button_type.wire_value()),
4846 },
4847 )?,
4848 };
4849 }
4850 p = encode(
4851 wire_id::BUTTON_TEMPLATE,
4852 &WireButtonTemplate {
4853 offset: *offset,
4854 count,
4855 total: *total,
4856 definitions,
4857 },
4858 )?;
4859 wire_id::BUTTON_TEMPLATE
4860 }
4861 Self::Version { firmware } => {
4862 p = encode(
4863 wire_id::VERSION,
4864 &WireFixedText::<16>::new(wire_id::VERSION, "firmware", firmware)?,
4865 )?;
4866 wire_id::VERSION
4867 }
4868 Self::ServerResponse { servers } => {
4869 if servers.is_empty() {
4870 return Err(CodecError::InvalidValue {
4871 message_id: wire_id::SERVER_RES,
4872 field: "server endpoints",
4873 value: 0,
4874 });
4875 }
4876 if servers.len() > MAX_SIGNALING_SERVERS {
4877 return Err(CodecError::CountTooLarge {
4878 message_id: wire_id::SERVER_RES,
4879 field: "server endpoints",
4880 count: servers.len(),
4881 maximum: MAX_SIGNALING_SERVERS,
4882 });
4883 }
4884 if servers
4885 .iter()
4886 .any(|server| server.address.is_unspecified() || server.address.is_multicast())
4887 {
4888 return Err(CodecError::InvalidValue {
4889 message_id: wire_id::SERVER_RES,
4890 field: "server address",
4891 value: 0,
4892 });
4893 }
4894 let names: [WireFixedText<48>; MAX_SIGNALING_SERVERS] = (0..MAX_SIGNALING_SERVERS)
4895 .map(|index| {
4896 WireFixedText::new(
4897 wire_id::SERVER_RES,
4898 "server name",
4899 servers.get(index).map_or("", |server| server.name.as_str()),
4900 )
4901 })
4902 .collect::<Result<Vec<_>, _>>()?
4903 .try_into()
4904 .map_err(|_| CodecError::InvalidValue {
4905 message_id: wire_id::SERVER_RES,
4906 field: "server endpoint array",
4907 value: servers.len() as u64,
4908 })?;
4909 let ports = std::array::from_fn(|index| {
4910 servers
4911 .get(index)
4912 .map_or(0, |server| u32::from(server.port.get()))
4913 });
4914 p = match protocol.wire() {
4915 17.. => encode(
4916 wire_id::SERVER_RES,
4917 &WireServerResponse::<WireExtendedAddress> {
4918 names,
4919 ports,
4920 addresses: std::array::from_fn(|index| {
4921 WireExtendedAddress::from_ip(
4922 servers
4923 .get(index)
4924 .map_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED), |server| {
4925 server.address
4926 }),
4927 )
4928 }),
4929 },
4930 ),
4931 _ => {
4932 let addresses: [WireIpv4Address; MAX_SIGNALING_SERVERS] = (0
4933 ..MAX_SIGNALING_SERVERS)
4934 .map(|index| {
4935 WireIpv4Address::from_ip(
4936 servers
4937 .get(index)
4938 .map_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED), |server| {
4939 server.address
4940 }),
4941 wire_id::SERVER_RES,
4942 "IP address family for pre-v17 protocol",
4943 )
4944 })
4945 .collect::<Result<Vec<_>, _>>()?
4946 .try_into()
4947 .map_err(|_| CodecError::InvalidValue {
4948 message_id: wire_id::SERVER_RES,
4949 field: "server address array",
4950 value: servers.len() as u64,
4951 })?;
4952 encode(
4953 wire_id::SERVER_RES,
4954 &WireServerResponse::<WireIpv4Address> {
4955 names,
4956 ports,
4957 addresses,
4958 },
4959 )
4960 }
4961 }?;
4962 wire_id::SERVER_RES
4963 }
4964 Self::TimeDate {
4965 year,
4966 month,
4967 weekday,
4968 day,
4969 hour,
4970 minute,
4971 second,
4972 milliseconds,
4973 unix_seconds,
4974 } => {
4975 p = encode(
4976 wire_id::DEFINE_TIME_DATE,
4977 &WireTimeDate {
4978 year: *year,
4979 month: *month,
4980 weekday: *weekday,
4981 day: *day,
4982 hour: *hour,
4983 minute: *minute,
4984 second: *second,
4985 milliseconds: *milliseconds,
4986 unix_seconds: *unix_seconds,
4987 },
4988 )?;
4989 wire_id::DEFINE_TIME_DATE
4990 }
4991 Self::SoftKeyTemplate { actions } => {
4992 const LABELS: [u16; 32] = [
4996 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 202, 65, 67, 63,
4997 79, 78, 54, 62, 77, 80, 88, 60, 0, 201,
4998 ];
4999 let mut available = [false; 32];
5000 for action in actions {
5001 let value = action.wire_value();
5002 if !action.is_known() || value == 0 || value > available.len() as u32 {
5003 return Err(CodecError::InvalidDefinition(format!(
5004 "soft-key template contains unknown action {value}"
5005 )));
5006 }
5007 let slot = value as usize - 1;
5008 if std::mem::replace(&mut available[slot], true) {
5009 return Err(CodecError::InvalidDefinition(format!(
5010 "soft-key template repeats action {value}"
5011 )));
5012 }
5013 }
5014 p = encode(
5015 wire_id::SOFT_KEY_TEMPLATE_RES,
5016 &WireSoftKeyTemplate {
5017 offset: 0,
5018 count: 32,
5019 total: 32,
5020 definitions: std::array::from_fn(|index| {
5021 if !available[index] {
5022 return WireSoftKeyDefinition {
5023 label: [0; 16],
5024 event: 0,
5025 };
5026 }
5027 let label = LABELS[index];
5028 let mut encoded = [0; 16];
5029 match label {
5030 201 => encoded[..4].copy_from_slice(b"Dial"),
5031 0 => {}
5032 _ => {
5033 encoded[0] = 0x80;
5034 encoded[1] = label as u8;
5035 }
5036 }
5037 WireSoftKeyDefinition {
5038 label: encoded,
5039 event: index as u32 + 1,
5040 }
5041 }),
5042 },
5043 )?;
5044 wire_id::SOFT_KEY_TEMPLATE_RES
5045 }
5046 Self::SoftKeySet { profile } => {
5047 let definitions = (0_u32..16)
5048 .map(KeyMode::from)
5049 .map(|mode| profile.actions(mode))
5050 .map(|actions| {
5051 let mut indexes = [0_u8; 16];
5052 let mut info = [0_u16; 16];
5053 for (slot, action) in actions.iter().copied().enumerate() {
5054 let template = action.wire_value() as u8;
5055 indexes[slot] = template;
5056 info[slot] = u16::from(template) + 300;
5057 }
5058 WireSoftKeySetDefinition {
5059 template_indexes: indexes,
5060 info,
5061 }
5062 })
5063 .collect();
5064 p = encode(
5065 wire_id::SOFT_KEY_SET_RES,
5066 &WireSoftKeySet {
5067 offset: 0,
5068 count: 16,
5069 total: 16,
5070 sets: definitions,
5071 },
5072 )?;
5073 wire_id::SOFT_KEY_SET_RES
5074 }
5075 Self::SelectSoftKeys {
5076 line_instance,
5077 call_reference,
5078 set,
5079 valid_mask,
5080 } => {
5081 p = encode(
5082 wire_id::SELECT_SOFT_KEYS,
5083 &WireSelectSoftKeys {
5084 line_instance: *line_instance,
5085 call_reference: *call_reference,
5086 set: set.wire_value(),
5087 valid_mask: *valid_mask,
5088 },
5089 )?;
5090 wire_id::SELECT_SOFT_KEYS
5091 }
5092 Self::CallState {
5093 state,
5094 line_instance,
5095 call_reference,
5096 } => {
5097 p = encode(
5098 wire_id::CALL_STATE,
5099 &WireCallState {
5100 state: state.wire_value(),
5101 line_instance: *line_instance,
5102 call_reference: *call_reference,
5103 visibility: 0,
5104 precedence: call_state_precedence(*state),
5105 domain: 0,
5106 },
5107 )?;
5108 wire_id::CALL_STATE
5109 }
5110 Self::CallInfo {
5111 info,
5112 line_instance,
5113 call_reference,
5114 } => {
5115 if session.uses_dynamic_general_ui() {
5116 p = encode_dynamic_call_info(info, *line_instance, *call_reference, protocol)?;
5117 wire_id::CALL_INFO_DYNAMIC
5118 } else {
5119 p = encode(
5120 wire_id::CALL_INFO,
5121 &WireCallInfo {
5122 calling_name: WireFixedText::new(
5123 wire_id::CALL_INFO,
5124 "calling name",
5125 &info.calling_name,
5126 )?,
5127 calling_number: WireFixedText::new(
5128 wire_id::CALL_INFO,
5129 "calling number",
5130 &info.calling_number,
5131 )?,
5132 called_name: WireFixedText::new(
5133 wire_id::CALL_INFO,
5134 "called name",
5135 &info.called_name,
5136 )?,
5137 called_number: WireFixedText::new(
5138 wire_id::CALL_INFO,
5139 "called number",
5140 &info.called_number,
5141 )?,
5142 line_instance: *line_instance,
5143 call_reference: *call_reference,
5144 call_type: match info.direction {
5145 crate::types::CallDirection::Inbound => 1,
5146 crate::types::CallDirection::Outbound => 2,
5147 },
5148 original_called_name: WireFixedText::new(
5149 wire_id::CALL_INFO,
5150 "original called name",
5151 &info.original_called_name,
5152 )?,
5153 original_called_number: WireFixedText::new(
5154 wire_id::CALL_INFO,
5155 "original called number",
5156 &info.original_called_number,
5157 )?,
5158 last_redirecting_name: WireFixedText::new(
5159 wire_id::CALL_INFO,
5160 "last redirecting name",
5161 &info.last_redirecting_name,
5162 )?,
5163 last_redirecting_number: WireFixedText::new(
5164 wire_id::CALL_INFO,
5165 "last redirecting number",
5166 &info.last_redirecting_number,
5167 )?,
5168 original_redirect_reason: info.original_redirect_reason,
5169 last_redirect_reason: info.last_redirect_reason,
5170 voice_mailboxes: std::array::from_fn(|_| {
5171 WireFixedText::new(wire_id::CALL_INFO, "voice mailbox", "").unwrap()
5172 }),
5173 call_instance: 1,
5174 security_status: 0,
5175 party_restrictions: info.party_restrictions,
5176 },
5177 )?;
5178 wire_id::CALL_INFO
5179 }
5180 }
5181 Self::DisplayPrompt {
5182 timeout_seconds,
5183 text,
5184 line_instance,
5185 call_reference,
5186 } => {
5187 if session.uses_dynamic_general_ui() {
5188 p = encode(
5189 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS,
5190 &WireDynamicPromptHeader {
5191 timeout_seconds: *timeout_seconds,
5192 line_instance: *line_instance,
5193 call_reference: *call_reference,
5194 },
5195 )?;
5196 push_dynamic_text(
5197 &mut p,
5198 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS,
5199 "prompt",
5200 text,
5201 96,
5202 )?;
5203 pad_dynamic_payload(&mut p);
5204 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS
5205 } else {
5206 p = encode(
5207 wire_id::DISPLAY_PROMPT_STATUS,
5208 &WirePromptStatus {
5209 timeout_seconds: *timeout_seconds,
5210 text: WireFixedText::new(
5211 wire_id::DISPLAY_PROMPT_STATUS,
5212 "prompt",
5213 text,
5214 )?,
5215 line_instance: *line_instance,
5216 call_reference: *call_reference,
5217 },
5218 )?;
5219 wire_id::DISPLAY_PROMPT_STATUS
5220 }
5221 }
5222 Self::ClearPrompt {
5223 line_instance,
5224 call_reference,
5225 } => {
5226 p = encode(
5227 wire_id::CLEAR_PROMPT_STATUS,
5228 &WireLineCall {
5229 line_instance: *line_instance,
5230 call_reference: *call_reference,
5231 },
5232 )?;
5233 wire_id::CLEAR_PROMPT_STATUS
5234 }
5235 Self::DisplayNotify {
5236 timeout_seconds,
5237 text,
5238 } => {
5239 if session.uses_dynamic_general_ui() {
5240 p = encode(
5241 wire_id::DISPLAY_DYNAMIC_NOTIFY,
5242 &WireDynamicNotifyHeader {
5243 timeout_seconds: *timeout_seconds,
5244 },
5245 )?;
5246 push_dynamic_text(
5247 &mut p,
5248 wire_id::DISPLAY_DYNAMIC_NOTIFY,
5249 "notification",
5250 text,
5251 96,
5252 )?;
5253 pad_dynamic_payload(&mut p);
5254 wire_id::DISPLAY_DYNAMIC_NOTIFY
5255 } else {
5256 p = encode(
5257 wire_id::DISPLAY_NOTIFY,
5258 &WireNotify {
5259 timeout_seconds: *timeout_seconds,
5260 text: WireFixedText::new(
5261 wire_id::DISPLAY_NOTIFY,
5262 "notification",
5263 text,
5264 )?,
5265 },
5266 )?;
5267 wire_id::DISPLAY_NOTIFY
5268 }
5269 }
5270 Self::ClearNotify => wire_id::CLEAR_NOTIFY,
5271 Self::DisplayPriorityNotify {
5272 timeout_seconds,
5273 priority,
5274 text,
5275 } => {
5276 if session.uses_dynamic_general_ui() {
5277 p = encode(
5278 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY,
5279 &WireDynamicPriorityNotifyHeader {
5280 timeout_seconds: *timeout_seconds,
5281 priority: priority.wire_value(),
5282 },
5283 )?;
5284 push_dynamic_text(
5285 &mut p,
5286 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY,
5287 "notification",
5288 text,
5289 96,
5290 )?;
5291 pad_dynamic_payload(&mut p);
5292 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY
5293 } else {
5294 p = encode(
5295 wire_id::DISPLAY_PRIORITY_NOTIFY,
5296 &WirePriorityNotify {
5297 timeout_seconds: *timeout_seconds,
5298 priority: priority.wire_value(),
5299 text: WireFixedText::new(
5300 wire_id::DISPLAY_PRIORITY_NOTIFY,
5301 "notification",
5302 text,
5303 )?,
5304 },
5305 )?;
5306 wire_id::DISPLAY_PRIORITY_NOTIFY
5307 }
5308 }
5309 Self::ClearPriorityNotify { priority } => {
5310 p = encode(
5311 wire_id::CLEAR_PRIORITY_NOTIFY,
5312 &WireOneWord {
5313 value: priority.wire_value(),
5314 },
5315 )?;
5316 wire_id::CLEAR_PRIORITY_NOTIFY
5317 }
5318 Self::NotifyDtmfTone(message) | Self::SendDtmfTone(message) => {
5319 let message_id = if matches!(self, Self::NotifyDtmfTone(_)) {
5320 wire_id::NOTIFY_DTMF_TONE
5321 } else {
5322 wire_id::SEND_DTMF_TONE
5323 };
5324 p = encode(
5325 message_id,
5326 &WireDtmfToneControl {
5327 tone: message.tone.wire_value(),
5328 conference_id: message.conference_id.get(),
5329 passthrough_party_id: message.passthrough_party_id,
5330 },
5331 )?;
5332 message_id
5333 }
5334 Self::StartAnnouncement {
5335 announcements,
5336 end_of_ack,
5337 conference_id,
5338 matrix_conference_party_ids,
5339 hearing_conference_party_mask,
5340 play_mode,
5341 } => {
5342 if announcements.len() > 32 {
5343 return Err(CodecError::CountTooLarge {
5344 message_id: wire_id::START_ANNOUNCEMENT,
5345 field: "announcements",
5346 count: announcements.len(),
5347 maximum: 32,
5348 });
5349 }
5350 if matrix_conference_party_ids.len() > 16 {
5351 return Err(CodecError::CountTooLarge {
5352 message_id: wire_id::START_ANNOUNCEMENT,
5353 field: "matrix conference party identifiers",
5354 count: matrix_conference_party_ids.len(),
5355 maximum: 16,
5356 });
5357 }
5358 let mut wire_announcements = [WireAnnouncementEntry::default(); 32];
5359 for (wire, entry) in wire_announcements.iter_mut().zip(announcements) {
5360 *wire = WireAnnouncementEntry {
5361 locale: entry.locale,
5362 country: entry.country,
5363 tone: entry.tone.wire_value(),
5364 };
5365 }
5366 let mut wire_party_ids = [0; 16];
5367 wire_party_ids[..matrix_conference_party_ids.len()]
5368 .copy_from_slice(matrix_conference_party_ids);
5369 p = encode(
5370 wire_id::START_ANNOUNCEMENT,
5371 &WireStartAnnouncement {
5372 announcements: wire_announcements,
5373 end_of_ack: *end_of_ack,
5374 conference_id: *conference_id,
5375 matrix_conference_party_ids: wire_party_ids,
5376 hearing_conference_party_mask: *hearing_conference_party_mask,
5377 play_mode: *play_mode,
5378 },
5379 )?;
5380 wire_id::START_ANNOUNCEMENT
5381 }
5382 Self::StopAnnouncement { conference_id } => {
5383 p = encode(
5384 wire_id::STOP_ANNOUNCEMENT,
5385 &WireOneWord {
5386 value: *conference_id,
5387 },
5388 )?;
5389 wire_id::STOP_ANNOUNCEMENT
5390 }
5391 Self::AnnouncementFinish {
5392 conference_id,
5393 play_status,
5394 } => {
5395 p = encode(
5396 wire_id::ANNOUNCEMENT_FINISH,
5397 &WireAnnouncementFinish {
5398 conference_id: *conference_id,
5399 play_status: *play_status,
5400 },
5401 )?;
5402 wire_id::ANNOUNCEMENT_FINISH
5403 }
5404 Self::ClearConference {
5405 conference_id,
5406 service_number,
5407 } => {
5408 p = encode(
5409 wire_id::CLEAR_CONFERENCE,
5410 &WireCallParty {
5411 call_reference: conference_id.get(),
5412 passthrough_party_id: *service_number,
5413 },
5414 )?;
5415 wire_id::CLEAR_CONFERENCE
5416 }
5417 Self::CreateConferenceRequest(request) => {
5418 p = encode(
5419 wire_id::CREATE_CONFERENCE_REQ,
5420 &WireCreateConferenceRequest {
5421 conference_id: request.conference_id.get(),
5422 reserved_participants: request.reserved_participants,
5423 resource_type: request.resource_type.wire_value(),
5424 application_id: request.application_id.get(),
5425 application_conference_id: WireFixedText::new(
5426 wire_id::CREATE_CONFERENCE_REQ,
5427 "application conference ID",
5428 &request.application_conference_id,
5429 )?,
5430 application_data: WireFixedText::new(
5431 wire_id::CREATE_CONFERENCE_REQ,
5432 "application data",
5433 &request.application_data,
5434 )?,
5435 data_length: validate_conference_data_for_encode(
5436 wire_id::CREATE_CONFERENCE_REQ,
5437 &request.passthrough_data,
5438 )?,
5439 passthrough_data: request.passthrough_data.clone(),
5440 },
5441 )?;
5442 wire_id::CREATE_CONFERENCE_REQ
5443 }
5444 Self::DeleteConferenceRequest { conference_id } => {
5445 p = encode(
5446 wire_id::DELETE_CONFERENCE_REQ,
5447 &WireOneWord {
5448 value: conference_id.get(),
5449 },
5450 )?;
5451 wire_id::DELETE_CONFERENCE_REQ
5452 }
5453 Self::ModifyConferenceRequest(request) => {
5454 p = encode(
5455 wire_id::MODIFY_CONFERENCE_REQ,
5456 &WireModifyConferenceRequest {
5457 conference_id: request.conference_id.get(),
5458 reserved_participants: request.reserved_participants,
5459 application_id: request.application_id.get(),
5460 application_conference_id: WireFixedText::new(
5461 wire_id::MODIFY_CONFERENCE_REQ,
5462 "application conference ID",
5463 &request.application_conference_id,
5464 )?,
5465 application_data: WireFixedText::new(
5466 wire_id::MODIFY_CONFERENCE_REQ,
5467 "application data",
5468 &request.application_data,
5469 )?,
5470 data_length: validate_conference_data_for_encode(
5471 wire_id::MODIFY_CONFERENCE_REQ,
5472 &request.passthrough_data,
5473 )?,
5474 passthrough_data: request.passthrough_data.clone(),
5475 },
5476 )?;
5477 wire_id::MODIFY_CONFERENCE_REQ
5478 }
5479 Self::AuditConferenceRequest => wire_id::AUDIT_CONFERENCE_REQ,
5480 Self::AddParticipantRequest(request) => {
5481 p = encode(
5482 wire_id::ADD_PARTICIPANT_REQ,
5483 &encode_participant_request(
5484 wire_id::ADD_PARTICIPANT_REQ,
5485 request.conference_id,
5486 &request.participant,
5487 )?,
5488 )?;
5489 wire_id::ADD_PARTICIPANT_REQ
5490 }
5491 Self::DropParticipantRequest {
5492 conference_id,
5493 call_reference,
5494 } => {
5495 p = encode(
5496 wire_id::DROP_PARTICIPANT_REQ,
5497 &WireCallParty {
5498 call_reference: conference_id.get(),
5499 passthrough_party_id: call_reference.get(),
5500 },
5501 )?;
5502 wire_id::DROP_PARTICIPANT_REQ
5503 }
5504 Self::AuditParticipantRequest { conference_id } => {
5505 p = encode(
5506 wire_id::AUDIT_PARTICIPANT_REQ,
5507 &WireOneWord {
5508 value: conference_id.get(),
5509 },
5510 )?;
5511 wire_id::AUDIT_PARTICIPANT_REQ
5512 }
5513 Self::ChangeParticipantRequest(request) => {
5514 p = encode(
5515 wire_id::CHANGE_PARTICIPANT_REQ,
5516 &encode_participant_request(
5517 wire_id::CHANGE_PARTICIPANT_REQ,
5518 request.conference_id,
5519 &request.participant,
5520 )?,
5521 )?;
5522 wire_id::CHANGE_PARTICIPANT_REQ
5523 }
5524 Self::StopMultimediaTransmission(message)
5525 | Self::CloseMultimediaReceiveChannel(message) => {
5526 let message_id = if matches!(self, Self::StopMultimediaTransmission(_)) {
5527 wire_id::STOP_MULTIMEDIA_TRANSMISSION
5528 } else {
5529 wire_id::CLOSE_MULTIMEDIA_RECEIVE_CHANNEL
5530 };
5531 p = encode(
5532 message_id,
5533 &WireMultimediaStreamControl {
5534 conference_id: message.conference_id.get(),
5535 passthrough_party_id: message.passthrough_party_id.get(),
5536 call_reference: message.call_reference.get(),
5537 port_handling_flag: message.port_handling_flag,
5538 },
5539 )?;
5540 message_id
5541 }
5542 Self::FlowControlCommand(message) | Self::FlowControlNotify(message) => {
5543 let message_id = if matches!(self, Self::FlowControlCommand(_)) {
5544 wire_id::FLOW_CONTROL_COMMAND
5545 } else {
5546 wire_id::FLOW_CONTROL_NOTIFY
5547 };
5548 p = encode(
5549 message_id,
5550 &WireVideoFlowControl {
5551 conference_id: message.conference_id.get(),
5552 passthrough_party_id: message.passthrough_party_id.get(),
5553 call_reference: message.call_reference.get(),
5554 maximum_bit_rate: message.maximum_bit_rate,
5555 },
5556 )?;
5557 message_id
5558 }
5559 Self::VideoDisplayCommand {
5560 conference_id,
5561 call_reference,
5562 layout_id,
5563 } => {
5564 p = encode(
5565 wire_id::VIDEO_DISPLAY_COMMAND,
5566 &WireVideoDisplayCommand {
5567 conference_id: conference_id.get(),
5568 call_reference: call_reference.get(),
5569 layout_id: *layout_id,
5570 },
5571 )?;
5572 wire_id::VIDEO_DISPLAY_COMMAND
5573 }
5574 Self::ActivateCallPlane { line_instance } => {
5575 p = encode(
5576 wire_id::ACTIVATE_CALL_PLANE,
5577 &WireOneWord {
5578 value: *line_instance,
5579 },
5580 )?;
5581 wire_id::ACTIVATE_CALL_PLANE
5582 }
5583 Self::DeactivateCallPlane => wire_id::DEACTIVATE_CALL_PLANE,
5584 Self::BackspaceResponse {
5585 line_instance,
5586 call_reference,
5587 } => {
5588 p = encode(
5589 wire_id::BACKSPACE_RESPONSE,
5590 &WireLineCall {
5591 line_instance: *line_instance,
5592 call_reference: *call_reference,
5593 },
5594 )?;
5595 wire_id::BACKSPACE_RESPONSE
5596 }
5597 Self::RegisterTokenAck => wire_id::REGISTER_TOKEN_ACK,
5598 Self::RegisterTokenReject { backoff_seconds } => {
5599 p = encode(
5600 wire_id::REGISTER_TOKEN_REJECT,
5601 &WireOneWord {
5602 value: *backoff_seconds,
5603 },
5604 )?;
5605 wire_id::REGISTER_TOKEN_REJECT
5606 }
5607 Self::SpcpRegisterTokenAck { features } => {
5608 p = encode(
5609 wire_id::SPCP_REGISTER_TOKEN_ACK,
5610 &WireOneWord { value: *features },
5611 )?;
5612 wire_id::SPCP_REGISTER_TOKEN_ACK
5613 }
5614 Self::SpcpRegisterTokenReject { backoff_seconds } => {
5615 p = encode(
5616 wire_id::SPCP_REGISTER_TOKEN_REJECT,
5617 &WireOneWord {
5618 value: *backoff_seconds,
5619 },
5620 )?;
5621 wire_id::SPCP_REGISTER_TOKEN_REJECT
5622 }
5623 Self::SetRinger {
5624 mode,
5625 duration,
5626 line_instance,
5627 call_reference,
5628 } => {
5629 p = encode(
5630 wire_id::SET_RINGER,
5631 &WireModeLineCall {
5632 mode: mode.wire_value(),
5633 duration: duration.wire_value(),
5634 line_instance: *line_instance,
5635 call_reference: *call_reference,
5636 },
5637 )?;
5638 wire_id::SET_RINGER
5639 }
5640 Self::SetLamp {
5641 stimulus,
5642 instance,
5643 mode,
5644 } => {
5645 p = encode(
5646 wire_id::SET_LAMP,
5647 &WireLampState {
5648 stimulus: stimulus.wire_value(),
5649 instance: *instance,
5650 mode: mode.wire_value(),
5651 },
5652 )?;
5653 wire_id::SET_LAMP
5654 }
5655 Self::SetHookFlashDetect => wire_id::SET_HOOK_FLASH_DETECT,
5656 Self::StartTone {
5657 tone,
5658 direction,
5659 line_instance,
5660 call_reference,
5661 } => {
5662 p = encode(
5663 wire_id::START_TONE,
5664 &WireToneLineCall {
5665 tone: tone.wire_value(),
5666 direction: direction.wire_value(),
5667 line_instance: *line_instance,
5668 call_reference: *call_reference,
5669 },
5670 )?;
5671 wire_id::START_TONE
5672 }
5673 Self::StopTone {
5674 line_instance,
5675 call_reference,
5676 } => {
5677 p = match protocol.wire() {
5678 12.. => encode(
5679 wire_id::STOP_TONE,
5680 &WireStopToneV12 {
5681 line_instance: *line_instance,
5682 call_reference: *call_reference,
5683 tone: 0,
5684 },
5685 ),
5686 _ => encode(
5687 wire_id::STOP_TONE,
5688 &WireLineCall {
5689 line_instance: *line_instance,
5690 call_reference: *call_reference,
5691 },
5692 ),
5693 }?;
5694 wire_id::STOP_TONE
5695 }
5696 Self::StartMulticastMediaReception(message) => {
5697 p = encode_start_multicast_reception(message, protocol)?;
5698 wire_id::START_MULTICAST_MEDIA_RECEPTION
5699 }
5700 Self::StartMulticastMediaTransmission(message) => {
5701 p = encode_start_multicast_transmission(message, protocol)?;
5702 wire_id::START_MULTICAST_MEDIA_TRANSMISSION
5703 }
5704 Self::StopMulticastMediaReception {
5705 conference_id,
5706 passthrough_party_id,
5707 call_reference,
5708 }
5709 | Self::StopMulticastMediaTransmission {
5710 conference_id,
5711 passthrough_party_id,
5712 call_reference,
5713 } => {
5714 let message_id = if matches!(self, Self::StopMulticastMediaReception { .. }) {
5715 wire_id::STOP_MULTICAST_MEDIA_RECEPTION
5716 } else {
5717 wire_id::STOP_MULTICAST_MEDIA_TRANSMISSION
5718 };
5719 p = encode(
5720 message_id,
5721 &WireStopMulticast {
5722 conference_id: conference_id.get(),
5723 passthrough_party_id: passthrough_party_id.get(),
5724 call_reference: call_reference.get(),
5725 },
5726 )?;
5727 message_id
5728 }
5729 Self::OpenReceiveChannel {
5730 call_reference,
5731 passthrough_party_id,
5732 packet_ms,
5733 codec,
5734 echo_cancellation,
5735 telephone_event_payload,
5736 source_address,
5737 source_port,
5738 encryption,
5739 wire,
5740 } => {
5741 p = encode_open_receive(
5742 *call_reference,
5743 *passthrough_party_id,
5744 OpenReceiveParameters {
5745 packet_ms: *packet_ms,
5746 codec: *codec,
5747 echo_cancellation: *echo_cancellation,
5748 telephone_event_payload: *telephone_event_payload,
5749 source_address: *source_address,
5750 source_port: *source_port,
5751 },
5752 encryption.as_ref(),
5753 wire.as_ref(),
5754 protocol,
5755 )?;
5756 wire_id::OPEN_RECEIVE_CHANNEL
5757 }
5758 Self::CloseReceiveChannel(control) => {
5759 p = encode(
5760 wire_id::CLOSE_RECEIVE_CHANNEL,
5761 &WireAudioStreamControl {
5762 conference_id: control.conference_id.get(),
5763 passthrough_party_id: control.passthrough_party_id.get(),
5764 call_reference: control.call_reference.get(),
5765 port_handling_flag: control.port_handling_flag,
5766 },
5767 )?;
5768 wire_id::CLOSE_RECEIVE_CHANNEL
5769 }
5770 Self::ConnectionStatisticsRequest {
5771 directory_number,
5772 call_reference,
5773 processing,
5774 } => {
5775 p = match protocol.wire() {
5776 19.. => encode_connection_statistics_request::<25, 3>(
5777 directory_number,
5778 *call_reference,
5779 *processing,
5780 ),
5781 _ => encode_connection_statistics_request::<24, 0>(
5782 directory_number,
5783 *call_reference,
5784 *processing,
5785 ),
5786 }?;
5787 wire_id::CONNECTION_STATISTICS_REQ
5788 }
5789 Self::StartMediaTransmission {
5790 call_reference,
5791 passthrough_party_id,
5792 endpoint,
5793 silence_suppression,
5794 traffic_class,
5795 encryption,
5796 wire,
5797 } => {
5798 p = encode_start_media(
5799 *call_reference,
5800 *passthrough_party_id,
5801 StartMediaParameters {
5802 endpoint: *endpoint,
5803 silence_suppression: *silence_suppression,
5804 traffic_class: *traffic_class,
5805 },
5806 encryption.as_ref(),
5807 wire.as_ref(),
5808 protocol,
5809 )?;
5810 wire_id::START_MEDIA_TRANSMISSION
5811 }
5812 Self::StopMediaTransmission(control) => {
5813 p = encode(
5814 wire_id::STOP_MEDIA_TRANSMISSION,
5815 &WireAudioStreamControl {
5816 conference_id: control.conference_id.get(),
5817 passthrough_party_id: control.passthrough_party_id.get(),
5818 call_reference: control.call_reference.get(),
5819 port_handling_flag: control.port_handling_flag,
5820 },
5821 )?;
5822 wire_id::STOP_MEDIA_TRANSMISSION
5823 }
5824 Self::StartMediaReception => wire_id::START_MEDIA_RECEPTION,
5825 Self::StopMediaReception {
5826 conference_id,
5827 passthrough_party_id,
5828 } => {
5829 p = encode(
5830 wire_id::STOP_MEDIA_RECEPTION,
5831 &WireStopMediaReception {
5832 conference_id: conference_id.get(),
5833 passthrough_party_id: passthrough_party_id.get(),
5834 },
5835 )?;
5836 wire_id::STOP_MEDIA_RECEPTION
5837 }
5838 Self::SetSpeakerMode(mode) => {
5839 p = encode(
5840 wire_id::SET_SPEAKER_MODE,
5841 &WireOneWord {
5842 value: mode.wire_value(),
5843 },
5844 )?;
5845 wire_id::SET_SPEAKER_MODE
5846 }
5847 Self::SetMicrophoneMode(mode) => {
5848 p = encode(
5849 wire_id::SET_MICROPHONE_MODE,
5850 &WireOneWord {
5851 value: mode.wire_value(),
5852 },
5853 )?;
5854 wire_id::SET_MICROPHONE_MODE
5855 }
5856 Self::Reset(reset) => {
5857 p = encode(
5858 wire_id::RESET,
5859 &WireOneWord {
5860 value: reset.wire_value(),
5861 },
5862 )?;
5863 wire_id::RESET
5864 }
5865 Self::DisplayText { text } => {
5866 p = encode(
5867 wire_id::DISPLAY_TEXT,
5868 &WireFixedText::<32>::new(wire_id::DISPLAY_TEXT, "display text", text)?,
5869 )?;
5870 wire_id::DISPLAY_TEXT
5871 }
5872 Self::ClearDisplay => wire_id::CLEAR_DISPLAY,
5873 Self::ForwardStatus {
5874 line_instance,
5875 forward_all,
5876 forward_busy,
5877 forward_no_answer,
5878 } => {
5879 p = match protocol.wire() {
5880 19.. => encode_forward_status::<25, 3>(
5881 *line_instance,
5882 forward_all.as_deref(),
5883 forward_busy.as_deref(),
5884 forward_no_answer.as_deref(),
5885 ),
5886 _ => encode_forward_status::<24, 0>(
5887 *line_instance,
5888 forward_all.as_deref(),
5889 forward_busy.as_deref(),
5890 forward_no_answer.as_deref(),
5891 ),
5892 }?;
5893 wire_id::FORWARD_STAT
5894 }
5895 Self::SpeedDialStatus {
5896 instance,
5897 number,
5898 display_name,
5899 } => {
5900 if session.uses_dynamic_speed_dial_status() {
5901 p = encode_dynamic_speed_dial_status(
5902 *instance,
5903 number,
5904 display_name,
5905 legacy_code_page,
5906 )?;
5907 wire_id::SPEED_DIAL_STAT_DYNAMIC
5908 } else {
5909 p = encode(
5910 wire_id::SPEED_DIAL_STAT,
5911 &WireSpeedDialStatus {
5912 instance: *instance,
5913 number: WireFixedText::new(wire_id::SPEED_DIAL_STAT, "number", number)?,
5914 display_name: WireFixedText::new_station(
5915 wire_id::SPEED_DIAL_STAT,
5916 "display name",
5917 display_name,
5918 legacy_code_page,
5919 )?,
5920 },
5921 )?;
5922 wire_id::SPEED_DIAL_STAT
5923 }
5924 }
5925 Self::DialedNumber {
5926 number,
5927 line_instance,
5928 call_reference,
5929 } => {
5930 p = match protocol.wire() {
5931 19.. => encode_dialed_number::<25, 3>(number, *line_instance, *call_reference),
5932 _ => encode_dialed_number::<24, 0>(number, *line_instance, *call_reference),
5933 }?;
5934 wire_id::DIALED_NUMBER
5935 }
5936 Self::StartMediaFailureDetection(detection) => {
5937 p = encode(
5938 wire_id::START_MEDIA_FAILURE_DETECTION,
5939 &WireMediaFailureDetection {
5940 conference_id: detection.conference_id.get(),
5941 passthrough_party_id: detection.passthrough_party_id,
5942 packet_millis: detection.packet_millis,
5943 codec: detection.codec.wire_value(),
5944 echo_cancellation: detection.echo_cancellation.wire_value(),
5945 codec_qualifier: detection.codec_qualifier,
5946 call_reference: detection.call_reference.get(),
5947 },
5948 )?;
5949 wire_id::START_MEDIA_FAILURE_DETECTION
5950 }
5951 Self::OpenMultimediaChannel(message) => {
5952 p = encode_open_multimedia(message, protocol)?;
5953 wire_id::OPEN_MULTIMEDIA_CHANNEL
5954 }
5955 Self::StartMultimediaTransmission(message) => {
5956 p = encode_start_multimedia(message, protocol)?;
5957 wire_id::START_MULTIMEDIA_TRANSMISSION
5958 }
5959 Self::MiscellaneousCommand(message) => {
5960 p = encode_miscellaneous_command(message)?;
5961 wire_id::MISCELLANEOUS_COMMAND
5962 }
5963 Self::UserToDeviceData(data) => {
5964 p = encode_user_data(data, wire_id::USER_TO_DEVICE_DATA)?;
5965 wire_id::USER_TO_DEVICE_DATA
5966 }
5967 Self::UserToDeviceDataV1(data) => {
5968 p = encode_user_data_v1(data, wire_id::USER_TO_DEVICE_DATA_V1)?;
5969 wire_id::USER_TO_DEVICE_DATA_V1
5970 }
5971 Self::SubscribeDtmfPayloadRequest(request) => {
5972 p = encode(
5973 wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ,
5974 &dtmf_payload_request_to_wire(*request),
5975 )?;
5976 wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ
5977 }
5978 Self::SubscribeDtmfPayloadError(identity) => {
5979 p = encode(
5980 wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR,
5981 &dtmf_payload_identity_to_wire(*identity),
5982 )?;
5983 wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR
5984 }
5985 Self::UnsubscribeDtmfPayloadRequest(request) => {
5986 p = encode(
5987 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ,
5988 &dtmf_payload_request_to_wire(*request),
5989 )?;
5990 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ
5991 }
5992 Self::UnsubscribeDtmfPayloadError(identity) => {
5993 p = encode(
5994 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR,
5995 &dtmf_payload_identity_to_wire(*identity),
5996 )?;
5997 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR
5998 }
5999 Self::FeatureStatus {
6000 instance,
6001 button_type,
6002 label,
6003 state,
6004 } => {
6005 if session.uses_dynamic_feature_status() {
6006 p = encode(
6007 wire_id::FEATURE_STAT_DYNAMIC,
6008 &WireFeatureStatusDynamic {
6009 instance: *instance,
6010 button_type: button_type.wire_value(),
6011 state: *state,
6012 label: WireFixedText::new_station(
6013 wire_id::FEATURE_STAT_DYNAMIC,
6014 "feature label",
6015 label,
6016 legacy_code_page,
6017 )?,
6018 padding: [0; 3],
6019 },
6020 )?;
6021 wire_id::FEATURE_STAT_DYNAMIC
6022 } else {
6023 p = encode(
6024 wire_id::FEATURE_STAT,
6025 &WireFeatureStatus {
6026 instance: *instance,
6027 button_type: button_type.wire_value(),
6028 label: WireFixedText::new_station(
6029 wire_id::FEATURE_STAT,
6030 "feature label",
6031 label,
6032 legacy_code_page,
6033 )?,
6034 state: *state,
6035 },
6036 )?;
6037 wire_id::FEATURE_STAT
6038 }
6039 }
6040 Self::ServiceUrlStatus {
6041 index,
6042 url,
6043 label,
6044 extension_text,
6045 } => {
6046 match (protocol.wire(), extension_text.is_empty()) {
6047 (0..=18, false) => Err(CodecError::InvalidValue {
6048 message_id: wire_id::SERVICE_URL_STAT_DYNAMIC,
6049 field: "service URL extension for this protocol version",
6050 value: extension_text.len() as u64,
6051 }),
6052 _ => Ok(()),
6053 }?;
6054 if session.uses_dynamic_general_ui() {
6055 p = encode_dynamic_service_url_status(
6056 *index,
6057 url,
6058 label,
6059 extension_text,
6060 protocol,
6061 legacy_code_page,
6062 )?;
6063 wire_id::SERVICE_URL_STAT_DYNAMIC
6064 } else {
6065 p = encode(
6066 wire_id::SERVICE_URL_STAT,
6067 &WireServiceUrlStatus {
6068 index: *index,
6069 url: WireFixedText::new(wire_id::SERVICE_URL_STAT, "service URL", url)?,
6070 label: WireFixedText::new_station(
6071 wire_id::SERVICE_URL_STAT,
6072 "service label",
6073 label,
6074 legacy_code_page,
6075 )?,
6076 },
6077 )?;
6078 wire_id::SERVICE_URL_STAT
6079 }
6080 }
6081 Self::CallSelectStatus {
6082 status,
6083 call_reference,
6084 line_instance,
6085 } => {
6086 p = encode(
6087 wire_id::CALL_SELECT_STAT,
6088 &WireCallSelectStatus {
6089 status: *status,
6090 call_reference: *call_reference,
6091 line_instance: *line_instance,
6092 },
6093 )?;
6094 wire_id::CALL_SELECT_STAT
6095 }
6096 Self::PortRequest(request) => {
6097 let base = WirePortRequest {
6098 conference_id: request.conference_id.get(),
6099 call_reference: request.call_reference.get(),
6100 passthrough_party_id: request.passthrough_party_id.get(),
6101 transport: request.transport.wire_value(),
6102 };
6103 p = match protocol.wire() {
6104 20.. => encode(
6105 wire_id::PORT_REQUEST,
6106 &WirePortRequestV20 {
6107 base,
6108 address_type: request
6109 .address_type
6110 .ok_or(CodecError::InvalidValue {
6111 message_id: wire_id::PORT_REQUEST,
6112 field: "address type required from protocol 20",
6113 value: 0,
6114 })?
6115 .wire_value(),
6116 media_type: request
6117 .media_type
6118 .ok_or(CodecError::InvalidValue {
6119 message_id: wire_id::PORT_REQUEST,
6120 field: "media type required from protocol 20",
6121 value: 0,
6122 })?
6123 .wire_value(),
6124 },
6125 ),
6126 _ => encode(wire_id::PORT_REQUEST, &base),
6127 }?;
6128 wire_id::PORT_REQUEST
6129 }
6130 Self::PortClose(close) => {
6131 let base = WirePortClose {
6132 conference_id: close.conference_id.get(),
6133 call_reference: close.call_reference.get(),
6134 passthrough_party_id: close.passthrough_party_id.get(),
6135 };
6136 p = match protocol.wire() {
6137 20.. => encode(
6138 wire_id::PORT_CLOSE,
6139 &WirePortCloseV20 {
6140 base,
6141 media_type: close
6142 .media_type
6143 .ok_or(CodecError::InvalidValue {
6144 message_id: wire_id::PORT_CLOSE,
6145 field: "media type required from protocol 20",
6146 value: 0,
6147 })?
6148 .wire_value(),
6149 },
6150 ),
6151 _ => encode(wire_id::PORT_CLOSE, &base),
6152 }?;
6153 wire_id::PORT_CLOSE
6154 }
6155 Self::SubscriptionStatus {
6156 transaction_id,
6157 feature_id,
6158 timer_seconds,
6159 cause,
6160 } => {
6161 p = encode(
6162 wire_id::SUBSCRIPTION_STAT,
6163 &WireSubscriptionStatus {
6164 transaction_id: *transaction_id,
6165 feature_id: *feature_id,
6166 timer_seconds: *timer_seconds,
6167 cause: cause.wire_value(),
6168 },
6169 )?;
6170 wire_id::SUBSCRIPTION_STAT
6171 }
6172 Self::Notification {
6173 transaction_id,
6174 feature_id,
6175 status,
6176 text,
6177 } => {
6178 p = encode(
6179 wire_id::NOTIFICATION,
6180 &WireNotification {
6181 transaction_id: *transaction_id,
6182 feature_id: *feature_id,
6183 status: status.wire_value(),
6184 text: WireFixedText::new(wire_id::NOTIFICATION, "notification", text)?,
6185 },
6186 )?;
6187 wire_id::NOTIFICATION
6188 }
6189 Self::CallHistoryDisposition {
6190 disposition,
6191 line_instance,
6192 call_reference,
6193 } => {
6194 p = encode(
6195 wire_id::CALL_HISTORY_DISPOSITION,
6196 &WireCallHistoryDisposition {
6197 disposition: disposition.wire_value(),
6198 line_instance: *line_instance,
6199 call_reference: *call_reference,
6200 },
6201 )?;
6202 wire_id::CALL_HISTORY_DISPOSITION
6203 }
6204 Self::CallCountResponse(response) => {
6205 if response.line_data.len() > CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES {
6206 return Err(CodecError::CountTooLarge {
6207 message_id: wire_id::CALL_COUNT_RES,
6208 field: "call-count line data",
6209 count: response.line_data.len(),
6210 maximum: CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES,
6211 });
6212 }
6213 let mut line_data =
6214 [WireCallCountLineData::default(); CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES];
6215 for (wire, entry) in line_data.iter_mut().zip(&response.line_data) {
6216 *wire = WireCallCountLineData {
6217 max_calls: entry.max_calls,
6218 busy_trigger: entry.busy_trigger,
6219 };
6220 }
6221 p = encode(
6222 wire_id::CALL_COUNT_RES,
6223 &WireCallCountResponse {
6224 total_configured_lines: response.total_configured_lines,
6225 starting_line_instance: response.starting_line_instance,
6226 line_data_entries: wire_count(
6227 wire_id::CALL_COUNT_RES,
6228 "call-count line data",
6229 response.line_data.len(),
6230 )?,
6231 line_data,
6232 },
6233 )?;
6234 wire_id::CALL_COUNT_RES
6235 }
6236 Self::RecordingStatus {
6237 call_reference,
6238 active,
6239 } => {
6240 p = encode(
6241 wire_id::RECORDING_STATUS,
6242 &WireRecordingStatus {
6243 call_reference: *call_reference,
6244 active: u32::from(*active),
6245 },
6246 )?;
6247 wire_id::RECORDING_STATUS
6248 }
6249 Self::KnownOpaque(message) => {
6250 ensure_preserve_only(message.id)?;
6251 return Ok((
6252 message.id.wire_value(),
6253 message.payload.as_bytes().to_vec(),
6254 message.protocol_version,
6255 ));
6256 }
6257 Self::Unknown(message) => {
6258 return Ok((
6259 message.message_id,
6260 message.payload.clone(),
6261 message.protocol_version,
6262 ));
6263 }
6264 };
6265 pad_typed_payload(id, &mut p);
6266 Ok((id, p, protocol.wire()))
6267 }
6268}
6269
6270fn reject_non_station_route(
6271 message_id: u32,
6272 expected_route: MessageRoute,
6273 expected: &'static str,
6274) -> Result<(), CodecError> {
6275 if let Some(actual) = MessageId::from(message_id).route()
6276 && actual != expected_route
6277 {
6278 return Err(CodecError::UnexpectedRoute {
6279 message_id,
6280 actual,
6281 expected,
6282 });
6283 }
6284 Ok(())
6285}
6286
6287impl ControlMessage {
6288 pub fn decode(frame: Frame, protocol: ProtocolVersion) -> Result<Self, CodecError> {
6292 let message_id = MessageId::from(frame.message_id);
6293 let route = message_id.route().ok_or(CodecError::InvalidValue {
6294 message_id: frame.message_id,
6295 field: "known control message identifier",
6296 value: u64::from(frame.message_id),
6297 })?;
6298 if matches!(
6299 route,
6300 MessageRoute::StationToControl | MessageRoute::ControlToStation
6301 ) {
6302 return Err(CodecError::UnexpectedRoute {
6303 message_id: frame.message_id,
6304 actual: route,
6305 expected: "control/service-node or intra-control route",
6306 });
6307 }
6308
6309 let p = &frame.payload;
6310 match frame.message_id {
6311 wire_id::START_SESSION_TRANSMISSION | wire_id::STOP_SESSION_TRANSMISSION => {
6312 let message = decode_session_transmission(p, protocol, frame.message_id)?;
6313 if frame.message_id == wire_id::START_SESSION_TRANSMISSION {
6314 Ok(Self::StartSessionTransmission(message))
6315 } else {
6316 Ok(Self::StopSessionTransmission(message))
6317 }
6318 }
6319 wire_id::QOS_RESERVATION_NOTIFY => {
6320 let value: WireQosReservationNotify = decode(frame.message_id, p)?;
6321 Ok(Self::QosReservationNotify {
6322 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6323 direction: QosDirection::from(value.direction),
6324 })
6325 }
6326 wire_id::QOS_ERROR_NOTIFY => {
6327 let value: WireQosErrorNotify = decode(frame.message_id, p)?;
6328 Ok(Self::QosErrorNotify {
6329 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6330 direction: QosDirection::from(value.direction),
6331 error_code: QosErrorCode::from(value.error_code),
6332 failure_node: Ipv4Addr::from(value.failure_node),
6333 rsvp_error_code: RsvpErrorCode::from(value.rsvp_error_code),
6334 rsvp_error_subcode: value.rsvp_error_subcode,
6335 rsvp_error_flags: value.rsvp_error_flags,
6336 })
6337 }
6338 wire_id::QOS_LISTEN => {
6339 let value: WireQosListen = decode(frame.message_id, p)?;
6340 Ok(Self::QosListen {
6341 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6342 reservation_style: QosReservationStyle::from(value.reservation_style),
6343 maximum_retries: value.maximum_retries,
6344 retry_timer: value.retry_timer,
6345 confirmation_required: decode_bool_word(
6346 value.confirmation_required,
6347 frame.message_id,
6348 "QoS confirmation required",
6349 )?,
6350 preemption_priority: value.preemption_priority,
6351 defending_priority: value.defending_priority,
6352 traffic: qos_traffic(
6353 value.compression_type,
6354 value.average_bit_rate,
6355 value.burst_size,
6356 value.peak_rate,
6357 ),
6358 application: qos_application_from_wire(value.application)?,
6359 })
6360 }
6361 wire_id::QOS_PATH => {
6362 let value: WireQosPath = decode(frame.message_id, p)?;
6363 Ok(Self::QosPath {
6364 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6365 reservation_style: QosReservationStyle::from(value.reservation_style),
6366 maximum_retries: value.maximum_retries,
6367 retry_timer: value.retry_timer,
6368 preemption_priority: value.preemption_priority,
6369 defending_priority: value.defending_priority,
6370 traffic: qos_traffic(
6371 value.compression_type,
6372 value.average_bit_rate,
6373 value.burst_size,
6374 value.peak_rate,
6375 ),
6376 application: qos_application_from_wire(value.application)?,
6377 })
6378 }
6379 wire_id::QOS_TEARDOWN => {
6380 let value: WireQosReservationNotify = decode(frame.message_id, p)?;
6381 Ok(Self::QosTeardown {
6382 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6383 direction: QosDirection::from(value.direction),
6384 })
6385 }
6386 wire_id::UPDATE_DSCP => {
6387 let value: WireUpdateDscp = decode(frame.message_id, p)?;
6388 let dscp = u8::try_from(value.dscp).map_err(|_| CodecError::InvalidValue {
6389 message_id: frame.message_id,
6390 field: "DSCP",
6391 value: u64::from(value.dscp),
6392 })?;
6393 if dscp > 63 {
6394 return Err(CodecError::InvalidValue {
6395 message_id: frame.message_id,
6396 field: "DSCP",
6397 value: u64::from(dscp),
6398 });
6399 }
6400 Ok(Self::UpdateDscp {
6401 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6402 dscp,
6403 })
6404 }
6405 wire_id::QOS_MODIFY => {
6406 let value: WireQosModify = decode(frame.message_id, p)?;
6407 Ok(Self::QosModify {
6408 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6409 direction: QosDirection::from(value.direction),
6410 traffic: qos_traffic(
6411 value.compression_type,
6412 value.average_bit_rate,
6413 value.burst_size,
6414 value.peak_rate,
6415 ),
6416 application: qos_application_from_wire(value.application)?,
6417 })
6418 }
6419 wire_id::MWI_NOTIFICATION => {
6420 let value: WireMessageWaitingNotification = decode(frame.message_id, p)?;
6421 validate_zero_payload(&value.alignment, frame.message_id, 2)?;
6422 Ok(Self::MessageWaitingNotification(
6423 MessageWaitingNotification {
6424 target_number: value.target_number.text()?,
6425 control_number: value.control_number.text()?,
6426 messages_waiting: decode_bool_word(
6427 value.messages_waiting,
6428 frame.message_id,
6429 "messages waiting",
6430 )?,
6431 total_voicemail: MessageWaitingCounts {
6432 new: value.total_voicemail_new,
6433 old: value.total_voicemail_old,
6434 },
6435 priority_voicemail: MessageWaitingCounts {
6436 new: value.priority_voicemail_new,
6437 old: value.priority_voicemail_old,
6438 },
6439 total_fax: MessageWaitingCounts {
6440 new: value.total_fax_new,
6441 old: value.total_fax_old,
6442 },
6443 priority_fax: MessageWaitingCounts {
6444 new: value.priority_fax_new,
6445 old: value.priority_fax_old,
6446 },
6447 },
6448 ))
6449 }
6450 wire_id::MWI_RESPONSE => {
6451 let value: WireMessageWaitingResponse = decode(frame.message_id, p)?;
6452 validate_zero_payload(&value.alignment, frame.message_id, 3)?;
6453 Ok(Self::MessageWaitingResponse {
6454 target_number: value.target_number.text()?,
6455 result: MessageWaitingResult::from(value.result),
6456 })
6457 }
6458 wire_id::MEDIA_RESOURCE_NOTIFICATION
6459 | wire_id::PORT_RESPONSE
6460 | wire_id::CREATE_CONFERENCE_RES
6461 | wire_id::DELETE_CONFERENCE_RES
6462 | wire_id::MODIFY_CONFERENCE_RES
6463 | wire_id::ADD_PARTICIPANT_RES
6464 | wire_id::AUDIT_CONFERENCE_RES
6465 | wire_id::AUDIT_PARTICIPANT_RES => Self::from_client_message(
6466 ClientMessage::decode_using_protocol(frame, protocol.wire())?,
6467 ),
6468 wire_id::CLEAR_CONFERENCE
6469 | wire_id::START_ANNOUNCEMENT
6470 | wire_id::STOP_ANNOUNCEMENT
6471 | wire_id::ANNOUNCEMENT_FINISH
6472 | wire_id::CREATE_CONFERENCE_REQ
6473 | wire_id::DELETE_CONFERENCE_REQ
6474 | wire_id::MODIFY_CONFERENCE_REQ
6475 | wire_id::ADD_PARTICIPANT_REQ
6476 | wire_id::DROP_PARTICIPANT_REQ
6477 | wire_id::AUDIT_CONFERENCE_REQ
6478 | wire_id::AUDIT_PARTICIPANT_REQ
6479 | wire_id::CHANGE_PARTICIPANT_REQ => {
6480 Self::from_server_message(ServerMessage::decode_unchecked(frame, protocol)?)
6481 }
6482 _ => preserve_known_message(frame, message_id).map(Self::KnownOpaque),
6483 }
6484 }
6485
6486 fn from_client_message(message: ClientMessage) -> Result<Self, CodecError> {
6487 Ok(match message {
6488 ClientMessage::MediaResourceNotification(value) => {
6489 Self::MediaResourceNotification(value)
6490 }
6491 ClientMessage::PortResponse(value) => Self::PortResponse(value),
6492 ClientMessage::CreateConferenceResponse(value) => Self::CreateConferenceResponse(value),
6493 ClientMessage::DeleteConferenceResponse {
6494 conference_id,
6495 result,
6496 } => Self::DeleteConferenceResponse {
6497 conference_id,
6498 result,
6499 },
6500 ClientMessage::ModifyConferenceResponse(value) => Self::ModifyConferenceResponse(value),
6501 ClientMessage::AddParticipantResponse(value) => Self::AddParticipantResponse(value),
6502 ClientMessage::AuditConferenceResponse(value) => Self::AuditConferenceResponse(value),
6503 ClientMessage::AuditParticipantResponse(value) => Self::AuditParticipantResponse(value),
6504 _ => {
6505 return Err(CodecError::InvalidValue {
6506 message_id: 0,
6507 field: "control message decoded through station codec",
6508 value: 0,
6509 });
6510 }
6511 })
6512 }
6513
6514 fn from_server_message(message: ServerMessage) -> Result<Self, CodecError> {
6515 Ok(match message {
6516 ServerMessage::ClearConference {
6517 conference_id,
6518 service_number,
6519 } => Self::ClearConference {
6520 conference_id,
6521 service_number,
6522 },
6523 ServerMessage::CreateConferenceRequest(value) => Self::CreateConferenceRequest(value),
6524 ServerMessage::DeleteConferenceRequest { conference_id } => {
6525 Self::DeleteConferenceRequest { conference_id }
6526 }
6527 ServerMessage::ModifyConferenceRequest(value) => Self::ModifyConferenceRequest(value),
6528 ServerMessage::AddParticipantRequest(value) => Self::AddParticipantRequest(value),
6529 ServerMessage::DropParticipantRequest {
6530 conference_id,
6531 call_reference,
6532 } => Self::DropParticipantRequest {
6533 conference_id,
6534 call_reference,
6535 },
6536 ServerMessage::AuditConferenceRequest => Self::AuditConferenceRequest,
6537 ServerMessage::AuditParticipantRequest { conference_id } => {
6538 Self::AuditParticipantRequest { conference_id }
6539 }
6540 ServerMessage::ChangeParticipantRequest(value) => Self::ChangeParticipantRequest(value),
6541 ServerMessage::StartAnnouncement {
6542 announcements,
6543 end_of_ack,
6544 conference_id,
6545 matrix_conference_party_ids,
6546 hearing_conference_party_mask,
6547 play_mode,
6548 } => Self::StartAnnouncement {
6549 announcements,
6550 end_of_ack: EndOfAnnouncementAck::from(end_of_ack),
6551 conference_id,
6552 matrix_conference_party_ids,
6553 hearing_conference_party_mask,
6554 play_mode: AnnouncementPlayMode::from(play_mode),
6555 },
6556 ServerMessage::StopAnnouncement { conference_id } => {
6557 Self::StopAnnouncement { conference_id }
6558 }
6559 ServerMessage::AnnouncementFinish {
6560 conference_id,
6561 play_status,
6562 } => Self::AnnouncementFinish {
6563 conference_id,
6564 play_status: AnnouncementPlayStatus::from(play_status),
6565 },
6566 _ => {
6567 return Err(CodecError::InvalidValue {
6568 message_id: 0,
6569 field: "control message decoded through station codec",
6570 value: 0,
6571 });
6572 }
6573 })
6574 }
6575
6576 pub fn encode(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
6581 let (message_id, payload, protocol_version) = match self {
6582 Self::StartSessionTransmission(message) | Self::StopSessionTransmission(message) => {
6583 let message_id = if matches!(self, Self::StartSessionTransmission(_)) {
6584 wire_id::START_SESSION_TRANSMISSION
6585 } else {
6586 wire_id::STOP_SESSION_TRANSMISSION
6587 };
6588 (
6589 message_id,
6590 encode_session_transmission(*message, protocol, message_id)?,
6591 protocol.wire(),
6592 )
6593 }
6594 Self::QosReservationNotify { flow, direction } => (
6595 wire_id::QOS_RESERVATION_NOTIFY,
6596 encode(
6597 wire_id::QOS_RESERVATION_NOTIFY,
6598 &WireQosReservationNotify {
6599 flow: qos_flow_to_wire(*flow),
6600 direction: direction.wire_value(),
6601 },
6602 )?,
6603 protocol.wire(),
6604 ),
6605 Self::QosErrorNotify {
6606 flow,
6607 direction,
6608 error_code,
6609 failure_node,
6610 rsvp_error_code,
6611 rsvp_error_subcode,
6612 rsvp_error_flags,
6613 } => (
6614 wire_id::QOS_ERROR_NOTIFY,
6615 encode(
6616 wire_id::QOS_ERROR_NOTIFY,
6617 &WireQosErrorNotify {
6618 flow: qos_flow_to_wire(*flow),
6619 direction: direction.wire_value(),
6620 error_code: error_code.wire_value(),
6621 failure_node: u32::from(*failure_node),
6622 rsvp_error_code: rsvp_error_code.wire_value(),
6623 rsvp_error_subcode: *rsvp_error_subcode,
6624 rsvp_error_flags: *rsvp_error_flags,
6625 },
6626 )?,
6627 protocol.wire(),
6628 ),
6629 Self::QosListen {
6630 flow,
6631 reservation_style,
6632 maximum_retries,
6633 retry_timer,
6634 confirmation_required,
6635 preemption_priority,
6636 defending_priority,
6637 traffic,
6638 application,
6639 } => (
6640 wire_id::QOS_LISTEN,
6641 encode(
6642 wire_id::QOS_LISTEN,
6643 &WireQosListen {
6644 flow: qos_flow_to_wire(*flow),
6645 reservation_style: reservation_style.wire_value(),
6646 maximum_retries: *maximum_retries,
6647 retry_timer: *retry_timer,
6648 confirmation_required: u32::from(*confirmation_required),
6649 preemption_priority: *preemption_priority,
6650 defending_priority: *defending_priority,
6651 compression_type: traffic.codec.wire_value(),
6652 average_bit_rate: traffic.average_bit_rate,
6653 burst_size: traffic.burst_size,
6654 peak_rate: traffic.peak_rate,
6655 application: qos_application_to_wire(wire_id::QOS_LISTEN, application)?,
6656 },
6657 )?,
6658 protocol.wire(),
6659 ),
6660 Self::QosPath {
6661 flow,
6662 reservation_style,
6663 maximum_retries,
6664 retry_timer,
6665 preemption_priority,
6666 defending_priority,
6667 traffic,
6668 application,
6669 } => (
6670 wire_id::QOS_PATH,
6671 encode(
6672 wire_id::QOS_PATH,
6673 &WireQosPath {
6674 flow: qos_flow_to_wire(*flow),
6675 reservation_style: reservation_style.wire_value(),
6676 maximum_retries: *maximum_retries,
6677 retry_timer: *retry_timer,
6678 preemption_priority: *preemption_priority,
6679 defending_priority: *defending_priority,
6680 compression_type: traffic.codec.wire_value(),
6681 average_bit_rate: traffic.average_bit_rate,
6682 burst_size: traffic.burst_size,
6683 peak_rate: traffic.peak_rate,
6684 application: qos_application_to_wire(wire_id::QOS_PATH, application)?,
6685 },
6686 )?,
6687 protocol.wire(),
6688 ),
6689 Self::QosTeardown { flow, direction } => (
6690 wire_id::QOS_TEARDOWN,
6691 encode(
6692 wire_id::QOS_TEARDOWN,
6693 &WireQosReservationNotify {
6694 flow: qos_flow_to_wire(*flow),
6695 direction: direction.wire_value(),
6696 },
6697 )?,
6698 protocol.wire(),
6699 ),
6700 Self::UpdateDscp { flow, dscp } => {
6701 if *dscp > 63 {
6702 return Err(CodecError::InvalidValue {
6703 message_id: wire_id::UPDATE_DSCP,
6704 field: "DSCP",
6705 value: u64::from(*dscp),
6706 });
6707 }
6708 (
6709 wire_id::UPDATE_DSCP,
6710 encode(
6711 wire_id::UPDATE_DSCP,
6712 &WireUpdateDscp {
6713 flow: qos_flow_to_wire(*flow),
6714 dscp: u32::from(*dscp),
6715 },
6716 )?,
6717 protocol.wire(),
6718 )
6719 }
6720 Self::QosModify {
6721 flow,
6722 direction,
6723 traffic,
6724 application,
6725 } => (
6726 wire_id::QOS_MODIFY,
6727 encode(
6728 wire_id::QOS_MODIFY,
6729 &WireQosModify {
6730 flow: qos_flow_to_wire(*flow),
6731 direction: direction.wire_value(),
6732 compression_type: traffic.codec.wire_value(),
6733 average_bit_rate: traffic.average_bit_rate,
6734 burst_size: traffic.burst_size,
6735 peak_rate: traffic.peak_rate,
6736 application: qos_application_to_wire(wire_id::QOS_MODIFY, application)?,
6737 },
6738 )?,
6739 protocol.wire(),
6740 ),
6741 Self::MessageWaitingNotification(value) => (
6742 wire_id::MWI_NOTIFICATION,
6743 encode(
6744 wire_id::MWI_NOTIFICATION,
6745 &WireMessageWaitingNotification {
6746 target_number: WireFixedText::new(
6747 wire_id::MWI_NOTIFICATION,
6748 "MWI target number",
6749 &value.target_number,
6750 )?,
6751 control_number: WireFixedText::new(
6752 wire_id::MWI_NOTIFICATION,
6753 "MWI control number",
6754 &value.control_number,
6755 )?,
6756 alignment: [0; 2],
6757 messages_waiting: u32::from(value.messages_waiting),
6758 total_voicemail_new: value.total_voicemail.new,
6759 total_voicemail_old: value.total_voicemail.old,
6760 priority_voicemail_new: value.priority_voicemail.new,
6761 priority_voicemail_old: value.priority_voicemail.old,
6762 total_fax_new: value.total_fax.new,
6763 total_fax_old: value.total_fax.old,
6764 priority_fax_new: value.priority_fax.new,
6765 priority_fax_old: value.priority_fax.old,
6766 },
6767 )?,
6768 protocol.wire(),
6769 ),
6770 Self::MessageWaitingResponse {
6771 target_number,
6772 result,
6773 } => (
6774 wire_id::MWI_RESPONSE,
6775 encode(
6776 wire_id::MWI_RESPONSE,
6777 &WireMessageWaitingResponse {
6778 target_number: WireFixedText::new(
6779 wire_id::MWI_RESPONSE,
6780 "MWI target number",
6781 target_number,
6782 )?,
6783 alignment: [0; 3],
6784 result: result.wire_value(),
6785 },
6786 )?,
6787 protocol.wire(),
6788 ),
6789 Self::KnownOpaque(message) => {
6790 ensure_preserve_only(message.id)?;
6791 return Frame::new(
6792 message.protocol_version,
6793 message.id.wire_value(),
6794 message.payload.as_bytes().to_vec(),
6795 )
6796 .encode();
6797 }
6798 other => return other.encode_via_existing(protocol),
6799 };
6800 Frame::new(protocol_version, message_id, payload).encode()
6801 }
6802
6803 fn encode_via_existing(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
6804 match self {
6805 Self::MediaResourceNotification(value) => {
6806 ClientMessage::MediaResourceNotification(value.clone()).encode_unchecked(protocol)
6807 }
6808 Self::PortResponse(value) => {
6809 ClientMessage::PortResponse(value.clone()).encode_unchecked(protocol)
6810 }
6811 Self::CreateConferenceResponse(value) => {
6812 ClientMessage::CreateConferenceResponse(value.clone()).encode_unchecked(protocol)
6813 }
6814 Self::DeleteConferenceResponse {
6815 conference_id,
6816 result,
6817 } => ClientMessage::DeleteConferenceResponse {
6818 conference_id: *conference_id,
6819 result: *result,
6820 }
6821 .encode_unchecked(protocol),
6822 Self::ModifyConferenceResponse(value) => {
6823 ClientMessage::ModifyConferenceResponse(value.clone()).encode_unchecked(protocol)
6824 }
6825 Self::AddParticipantResponse(value) => {
6826 ClientMessage::AddParticipantResponse(value.clone()).encode_unchecked(protocol)
6827 }
6828 Self::AuditConferenceResponse(value) => {
6829 ClientMessage::AuditConferenceResponse(value.clone()).encode_unchecked(protocol)
6830 }
6831 Self::AuditParticipantResponse(value) => {
6832 ClientMessage::AuditParticipantResponse(value.clone()).encode_unchecked(protocol)
6833 }
6834 Self::ClearConference {
6835 conference_id,
6836 service_number,
6837 } => ServerMessage::ClearConference {
6838 conference_id: *conference_id,
6839 service_number: *service_number,
6840 }
6841 .encode_unchecked(protocol),
6842 Self::CreateConferenceRequest(value) => {
6843 ServerMessage::CreateConferenceRequest(value.clone()).encode_unchecked(protocol)
6844 }
6845 Self::DeleteConferenceRequest { conference_id } => {
6846 ServerMessage::DeleteConferenceRequest {
6847 conference_id: *conference_id,
6848 }
6849 .encode_unchecked(protocol)
6850 }
6851 Self::ModifyConferenceRequest(value) => {
6852 ServerMessage::ModifyConferenceRequest(value.clone()).encode_unchecked(protocol)
6853 }
6854 Self::AddParticipantRequest(value) => {
6855 ServerMessage::AddParticipantRequest(value.clone()).encode_unchecked(protocol)
6856 }
6857 Self::DropParticipantRequest {
6858 conference_id,
6859 call_reference,
6860 } => ServerMessage::DropParticipantRequest {
6861 conference_id: *conference_id,
6862 call_reference: *call_reference,
6863 }
6864 .encode_unchecked(protocol),
6865 Self::AuditConferenceRequest => {
6866 ServerMessage::AuditConferenceRequest.encode_unchecked(protocol)
6867 }
6868 Self::AuditParticipantRequest { conference_id } => {
6869 ServerMessage::AuditParticipantRequest {
6870 conference_id: *conference_id,
6871 }
6872 .encode_unchecked(protocol)
6873 }
6874 Self::ChangeParticipantRequest(value) => {
6875 ServerMessage::ChangeParticipantRequest(value.clone()).encode_unchecked(protocol)
6876 }
6877 Self::StartAnnouncement {
6878 announcements,
6879 end_of_ack,
6880 conference_id,
6881 matrix_conference_party_ids,
6882 hearing_conference_party_mask,
6883 play_mode,
6884 } => ServerMessage::StartAnnouncement {
6885 announcements: announcements.clone(),
6886 end_of_ack: end_of_ack.wire_value(),
6887 conference_id: *conference_id,
6888 matrix_conference_party_ids: matrix_conference_party_ids.clone(),
6889 hearing_conference_party_mask: *hearing_conference_party_mask,
6890 play_mode: play_mode.wire_value(),
6891 }
6892 .encode_unchecked(protocol),
6893 Self::StopAnnouncement { conference_id } => ServerMessage::StopAnnouncement {
6894 conference_id: *conference_id,
6895 }
6896 .encode_unchecked(protocol),
6897 Self::AnnouncementFinish {
6898 conference_id,
6899 play_status,
6900 } => ServerMessage::AnnouncementFinish {
6901 conference_id: *conference_id,
6902 play_status: play_status.wire_value(),
6903 }
6904 .encode_unchecked(protocol),
6905 _ => unreachable!("directly encoded control message"),
6906 }
6907 }
6908}
6909
6910const fn call_state_precedence(state: CallState) -> u32 {
6911 match state {
6912 CallState::OffHook | CallState::Proceed | CallState::Connected | CallState::Transfer => 3,
6913 CallState::RingOut => 4,
6914 _ => 2,
6915 }
6916}
6917
6918#[derive(Clone, Copy, Debug)]
6919struct OpenReceiveParameters {
6920 packet_ms: u32,
6921 codec: Codec,
6922 echo_cancellation: EchoCancellation,
6923 telephone_event_payload: u8,
6924 source_address: IpAddr,
6925 source_port: u16,
6926}
6927
6928fn encode_open_receive(
6929 call: u32,
6930 party: u32,
6931 parameters: OpenReceiveParameters,
6932 encryption: Option<&MediaEncryption>,
6933 wire: Option<&OpenReceiveChannelWire>,
6934 protocol: ProtocolVersion,
6935) -> Result<Vec<u8>, CodecError> {
6936 let OpenReceiveParameters {
6937 packet_ms,
6938 codec,
6939 echo_cancellation,
6940 telephone_event_payload,
6941 source_address,
6942 source_port,
6943 } = parameters;
6944 let conference_id = wire.map_or(call, |value| value.conference_id);
6945 let g723_bitrate = wire.map_or(0, |value| value.g723_bitrate);
6946 let stream_passthrough_id = wire.map_or(0, |value| value.stream_passthrough_id);
6947 let associated_stream_id = wire.map_or(0, |value| value.associated_stream_id);
6948 let dtmf_type = wire.map_or(10, |value| value.dtmf_type);
6949 let mixing_mode = wire.map_or(0, |value| value.mixing_mode);
6950 let direction = wire.map_or(1, |value| value.direction);
6951 let requested_address_type = wire.map_or_else(
6952 || u32::from(matches!(source_address, IpAddr::V6(_))),
6953 |value| value.requested_address_type,
6954 );
6955 let encryption = WireEncryptionInfo::from_public(encryption);
6956 let base = WireOpenReceiveV11 {
6957 conference_id,
6958 passthrough_party_id: party,
6959 packet_millis: packet_ms,
6960 codec: codec.skinny(),
6961 vad: echo_cancellation.wire_value(),
6962 g723_bitrate,
6963 call_reference: call,
6964 encryption,
6965 stream_passthrough_id,
6966 associated_stream_id,
6967 rfc2833_payload: u32::from(telephone_event_payload),
6968 dtmf_type,
6969 };
6970 match protocol.wire() {
6971 21.. => encode(
6972 wire_id::OPEN_RECEIVE_CHANNEL,
6973 &WireOpenReceiveV21 {
6974 base: WireOpenReceiveV18 {
6975 base: WireOpenReceiveV17 {
6976 base: WireOpenReceiveAddressed {
6977 base,
6978 mixing_mode,
6979 direction,
6980 remote: WireExtendedAddress::from_ip(source_address),
6981 remote_port: u32::from(source_port),
6982 },
6983 requested_address_type,
6984 },
6985 audio_level_adjustment: wire.map_or(0, |value| value.audio_level_adjustment),
6986 },
6987 latent_capabilities: WireLatentCapabilities {
6988 bytes: wire.map_or([0; 36], |value| value.latent_capabilities),
6989 },
6990 },
6991 ),
6992 18..=20 => encode(
6993 wire_id::OPEN_RECEIVE_CHANNEL,
6994 &WireOpenReceiveV18 {
6995 base: WireOpenReceiveV17 {
6996 base: WireOpenReceiveAddressed {
6997 base,
6998 mixing_mode,
6999 direction,
7000 remote: WireExtendedAddress::from_ip(source_address),
7001 remote_port: u32::from(source_port),
7002 },
7003 requested_address_type,
7004 },
7005 audio_level_adjustment: wire.map_or(0, |value| value.audio_level_adjustment),
7006 },
7007 ),
7008 17 => encode(
7009 wire_id::OPEN_RECEIVE_CHANNEL,
7010 &WireOpenReceiveV17 {
7011 base: WireOpenReceiveAddressed {
7012 base,
7013 mixing_mode,
7014 direction,
7015 remote: WireExtendedAddress::from_ip(source_address),
7016 remote_port: u32::from(source_port),
7017 },
7018 requested_address_type,
7019 },
7020 ),
7021 version => {
7022 let remote = WireIpv4Address::from_ip(
7023 source_address,
7024 wire_id::OPEN_RECEIVE_CHANNEL,
7025 "IP address family for pre-v17 protocol",
7026 )?;
7027 match version {
7028 12.. => encode(
7029 wire_id::OPEN_RECEIVE_CHANNEL,
7030 &WireOpenReceiveV12 {
7031 base,
7032 mixing_mode,
7033 direction,
7034 remote,
7035 remote_port: u32::from(source_port),
7036 },
7037 ),
7038 _ => encode(wire_id::OPEN_RECEIVE_CHANNEL, &base),
7039 }
7040 }
7041 }
7042}
7043
7044struct StartMediaParameters {
7045 endpoint: MediaEndpoint,
7046 silence_suppression: SilenceSuppression,
7047 traffic_class: MediaTrafficClass,
7048}
7049
7050fn encode_start_media(
7051 call: u32,
7052 party: u32,
7053 parameters: StartMediaParameters,
7054 encryption: Option<&MediaEncryption>,
7055 wire: Option<&StartMediaTransmissionWire>,
7056 protocol: ProtocolVersion,
7057) -> Result<Vec<u8>, CodecError> {
7058 let StartMediaParameters {
7059 endpoint,
7060 silence_suppression,
7061 traffic_class,
7062 } = parameters;
7063 let conference_id = wire.map_or(call, |value| value.conference_id);
7064 let precedence = u32::from(traffic_class);
7065 let g723_bitrate = wire.map_or(0, |value| value.g723_bitrate);
7066 let stream_passthrough_id = wire.map_or(0, |value| value.stream_passthrough_id);
7067 let associated_stream_id = wire.map_or(0, |value| value.associated_stream_id);
7068 let dtmf_type = wire.map_or(10, |value| value.dtmf_type);
7069 let mixing_mode = wire.map_or(0, |value| value.mixing_mode);
7070 let direction = wire.map_or(1, |value| value.direction);
7071 let encryption = WireEncryptionInfo::from_public(encryption);
7072 match protocol.wire() {
7073 21.. => encode(
7074 wire_id::START_MEDIA_TRANSMISSION,
7075 &WireStartMediaV21 {
7076 base: WireStartMediaV17 {
7077 base: WireStartMediaBase {
7078 conference_id,
7079 passthrough_party_id: party,
7080 remote: WireExtendedAddress::from_ip(endpoint.address),
7081 remote_port: u32::from(endpoint.rtp_port),
7082 packet_millis: endpoint.packet_ms,
7083 codec: endpoint.codec.skinny(),
7084 precedence,
7085 silence_suppression: silence_suppression.wire_value(),
7086 max_frames_per_packet: endpoint.max_frames_per_packet,
7087 g723_bitrate,
7088 call_reference: call,
7089 encryption,
7090 stream_passthrough_id,
7091 associated_stream_id,
7092 rfc2833_payload: u32::from(endpoint.telephone_event_payload),
7093 dtmf_type,
7094 },
7095 mixing_mode,
7096 direction,
7097 },
7098 latent_capabilities: WireLatentCapabilities {
7099 bytes: wire.map_or([0; 36], |value| value.latent_capabilities),
7100 },
7101 },
7102 ),
7103 17..=20 => encode(
7104 wire_id::START_MEDIA_TRANSMISSION,
7105 &WireStartMediaV17 {
7106 base: WireStartMediaBase {
7107 conference_id,
7108 passthrough_party_id: party,
7109 remote: WireExtendedAddress::from_ip(endpoint.address),
7110 remote_port: u32::from(endpoint.rtp_port),
7111 packet_millis: endpoint.packet_ms,
7112 codec: endpoint.codec.skinny(),
7113 precedence,
7114 silence_suppression: silence_suppression.wire_value(),
7115 max_frames_per_packet: endpoint.max_frames_per_packet,
7116 g723_bitrate,
7117 call_reference: call,
7118 encryption,
7119 stream_passthrough_id,
7120 associated_stream_id,
7121 rfc2833_payload: u32::from(endpoint.telephone_event_payload),
7122 dtmf_type,
7123 },
7124 mixing_mode,
7125 direction,
7126 },
7127 ),
7128 version => {
7129 let base = WireStartMediaV11 {
7130 conference_id,
7131 passthrough_party_id: party,
7132 remote: WireIpv4Address::from_ip(
7133 endpoint.address,
7134 wire_id::START_MEDIA_TRANSMISSION,
7135 "IP address family for pre-v17 protocol",
7136 )?,
7137 remote_port: u32::from(endpoint.rtp_port),
7138 packet_millis: endpoint.packet_ms,
7139 codec: endpoint.codec.skinny(),
7140 precedence,
7141 silence_suppression: silence_suppression.wire_value(),
7142 max_frames_per_packet: endpoint.max_frames_per_packet,
7143 g723_bitrate,
7144 call_reference: call,
7145 encryption,
7146 stream_passthrough_id,
7147 associated_stream_id,
7148 rfc2833_payload: u32::from(endpoint.telephone_event_payload),
7149 dtmf_type,
7150 };
7151 match version {
7152 12.. => encode(
7153 wire_id::START_MEDIA_TRANSMISSION,
7154 &WireStartMediaV12 {
7155 base,
7156 mixing_mode,
7157 direction,
7158 },
7159 ),
7160 _ => encode(wire_id::START_MEDIA_TRANSMISSION, &base),
7161 }
7162 }
7163 }
7164}
7165
7166fn encode_start_multicast_reception(
7167 message: &MulticastMediaReception,
7168 protocol: ProtocolVersion,
7169) -> Result<Vec<u8>, CodecError> {
7170 match protocol.wire() {
7171 17.. => encode(
7172 wire_id::START_MULTICAST_MEDIA_RECEPTION,
7173 &WireStartMulticastReception::<WireExtendedAddress> {
7174 conference_id: message.conference_id.get(),
7175 passthrough_party_id: message.passthrough_party_id.get(),
7176 address: WireExtendedAddress::from_ip(message.address),
7177 port: u32::from(message.port),
7178 packet_millis: message.packet_millis,
7179 codec: message.codec.wire_value(),
7180 echo_cancellation: message.echo_cancellation.wire_value(),
7181 g723_bitrate: message.g723_bitrate.wire_value(),
7182 call_reference: message.call_reference.get(),
7183 },
7184 ),
7185 _ => encode(
7186 wire_id::START_MULTICAST_MEDIA_RECEPTION,
7187 &WireStartMulticastReception::<WireIpv4Address> {
7188 conference_id: message.conference_id.get(),
7189 passthrough_party_id: message.passthrough_party_id.get(),
7190 address: WireIpv4Address::from_ip(
7191 message.address,
7192 wire_id::START_MULTICAST_MEDIA_RECEPTION,
7193 "IP address family for pre-v17 protocol",
7194 )?,
7195 port: u32::from(message.port),
7196 packet_millis: message.packet_millis,
7197 codec: message.codec.wire_value(),
7198 echo_cancellation: message.echo_cancellation.wire_value(),
7199 g723_bitrate: message.g723_bitrate.wire_value(),
7200 call_reference: message.call_reference.get(),
7201 },
7202 ),
7203 }
7204}
7205
7206fn decode_start_multicast_reception(
7207 payload: &[u8],
7208 protocol: ProtocolVersion,
7209 message_id: u32,
7210) -> Result<ServerMessage, CodecError> {
7211 let (conference_id, party_id, address, port, packet_millis, codec, echo, g723, call_reference) =
7212 match protocol.wire() {
7213 17.. => {
7214 validate_exact_payload(payload, message_id, 52)?;
7215 let value: WireStartMulticastReception<WireExtendedAddress> =
7216 decode(message_id, payload)?;
7217 (
7218 value.conference_id,
7219 value.passthrough_party_id,
7220 value.address.to_ip(message_id)?,
7221 value.port,
7222 value.packet_millis,
7223 value.codec,
7224 value.echo_cancellation,
7225 value.g723_bitrate,
7226 value.call_reference,
7227 )
7228 }
7229 _ => {
7230 validate_exact_payload(payload, message_id, 36)?;
7231 let value: WireStartMulticastReception<WireIpv4Address> =
7232 decode(message_id, payload)?;
7233 (
7234 value.conference_id,
7235 value.passthrough_party_id,
7236 value.address.to_ip(message_id)?,
7237 value.port,
7238 value.packet_millis,
7239 value.codec,
7240 value.echo_cancellation,
7241 value.g723_bitrate,
7242 value.call_reference,
7243 )
7244 }
7245 };
7246 Ok(ServerMessage::StartMulticastMediaReception(
7247 MulticastMediaReception {
7248 conference_id: conference_id.into(),
7249 passthrough_party_id: party_id.into(),
7250 call_reference: call_reference.into(),
7251 address,
7252 port: decode_port(port, message_id, "multicast port")?,
7253 packet_millis,
7254 codec: Codec::from(codec),
7255 echo_cancellation: EchoCancellation::from(echo),
7256 g723_bitrate: G723BitRate::from(g723),
7257 },
7258 ))
7259}
7260
7261fn encode_start_multicast_transmission(
7262 message: &MulticastMediaTransmission,
7263 protocol: ProtocolVersion,
7264) -> Result<Vec<u8>, CodecError> {
7265 match protocol.wire() {
7266 17.. => encode(
7267 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
7268 &WireStartMulticastTransmission::<WireExtendedAddress> {
7269 conference_id: message.conference_id.get(),
7270 passthrough_party_id: message.passthrough_party_id.get(),
7271 address: WireExtendedAddress::from_ip(message.address),
7272 port: u32::from(message.port),
7273 packet_millis: message.packet_millis,
7274 codec: message.codec.wire_value(),
7275 precedence: message.precedence,
7276 silence_suppression: message.silence_suppression,
7277 max_frames_per_packet: message.max_frames_per_packet,
7278 g723_bitrate: message.g723_bitrate.wire_value(),
7279 call_reference: message.call_reference.get(),
7280 },
7281 ),
7282 _ => encode(
7283 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
7284 &WireStartMulticastTransmission::<WireIpv4Address> {
7285 conference_id: message.conference_id.get(),
7286 passthrough_party_id: message.passthrough_party_id.get(),
7287 address: WireIpv4Address::from_ip(
7288 message.address,
7289 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
7290 "IP address family for pre-v17 protocol",
7291 )?,
7292 port: u32::from(message.port),
7293 packet_millis: message.packet_millis,
7294 codec: message.codec.wire_value(),
7295 precedence: message.precedence,
7296 silence_suppression: message.silence_suppression,
7297 max_frames_per_packet: message.max_frames_per_packet,
7298 g723_bitrate: message.g723_bitrate.wire_value(),
7299 call_reference: message.call_reference.get(),
7300 },
7301 ),
7302 }
7303}
7304
7305fn decode_start_multicast_transmission(
7306 payload: &[u8],
7307 protocol: ProtocolVersion,
7308 message_id: u32,
7309) -> Result<ServerMessage, CodecError> {
7310 let (
7311 conference_id,
7312 party_id,
7313 address,
7314 port,
7315 packet_millis,
7316 codec,
7317 precedence,
7318 silence,
7319 max_frames,
7320 g723,
7321 call_reference,
7322 ) = match protocol.wire() {
7323 17.. => {
7324 validate_exact_payload(payload, message_id, 60)?;
7325 let value: WireStartMulticastTransmission<WireExtendedAddress> =
7326 decode(message_id, payload)?;
7327 (
7328 value.conference_id,
7329 value.passthrough_party_id,
7330 value.address.to_ip(message_id)?,
7331 value.port,
7332 value.packet_millis,
7333 value.codec,
7334 value.precedence,
7335 value.silence_suppression,
7336 value.max_frames_per_packet,
7337 value.g723_bitrate,
7338 value.call_reference,
7339 )
7340 }
7341 _ => {
7342 validate_exact_payload(payload, message_id, 44)?;
7343 let value: WireStartMulticastTransmission<WireIpv4Address> =
7344 decode(message_id, payload)?;
7345 (
7346 value.conference_id,
7347 value.passthrough_party_id,
7348 value.address.to_ip(message_id)?,
7349 value.port,
7350 value.packet_millis,
7351 value.codec,
7352 value.precedence,
7353 value.silence_suppression,
7354 value.max_frames_per_packet,
7355 value.g723_bitrate,
7356 value.call_reference,
7357 )
7358 }
7359 };
7360 Ok(ServerMessage::StartMulticastMediaTransmission(
7361 MulticastMediaTransmission {
7362 conference_id: conference_id.into(),
7363 passthrough_party_id: party_id.into(),
7364 call_reference: call_reference.into(),
7365 address,
7366 port: decode_port(port, message_id, "multicast port")?,
7367 packet_millis,
7368 codec: Codec::from(codec),
7369 precedence,
7370 silence_suppression: silence,
7371 max_frames_per_packet: max_frames,
7372 g723_bitrate: G723BitRate::from(g723),
7373 },
7374 ))
7375}
7376
7377fn decode_open_receive(
7378 payload: &[u8],
7379 protocol: ProtocolVersion,
7380 message_id: u32,
7381) -> Result<ServerMessage, CodecError> {
7382 let (
7383 call_reference,
7384 passthrough_party_id,
7385 packet_ms,
7386 codec,
7387 echo,
7388 rfc2833,
7389 source_address,
7390 source_port,
7391 encryption,
7392 wire,
7393 ) = match protocol.wire() {
7394 21.. => {
7395 let value: WireOpenReceiveV21 = decode(message_id, payload)?;
7396 (
7397 value.base.base.base.base.call_reference,
7398 value.base.base.base.base.passthrough_party_id,
7399 value.base.base.base.base.packet_millis,
7400 value.base.base.base.base.codec,
7401 value.base.base.base.base.vad,
7402 value.base.base.base.base.rfc2833_payload,
7403 value.base.base.base.remote.to_ip(message_id)?,
7404 decode_port(
7405 value.base.base.base.remote_port,
7406 message_id,
7407 "source RTP port",
7408 )?,
7409 value.base.base.base.base.encryption,
7410 OpenReceiveChannelWire {
7411 conference_id: value.base.base.base.base.conference_id,
7412 g723_bitrate: value.base.base.base.base.g723_bitrate,
7413 stream_passthrough_id: value.base.base.base.base.stream_passthrough_id,
7414 associated_stream_id: value.base.base.base.base.associated_stream_id,
7415 dtmf_type: value.base.base.base.base.dtmf_type,
7416 mixing_mode: value.base.base.base.mixing_mode,
7417 direction: value.base.base.base.direction,
7418 requested_address_type: value.base.base.requested_address_type,
7419 audio_level_adjustment: value.base.audio_level_adjustment,
7420 latent_capabilities: value.latent_capabilities.bytes,
7421 },
7422 )
7423 }
7424 18..=20 => {
7425 let value: WireOpenReceiveV18 = decode(message_id, payload)?;
7426 (
7427 value.base.base.base.call_reference,
7428 value.base.base.base.passthrough_party_id,
7429 value.base.base.base.packet_millis,
7430 value.base.base.base.codec,
7431 value.base.base.base.vad,
7432 value.base.base.base.rfc2833_payload,
7433 value.base.base.remote.to_ip(message_id)?,
7434 decode_port(value.base.base.remote_port, message_id, "source RTP port")?,
7435 value.base.base.base.encryption,
7436 OpenReceiveChannelWire {
7437 conference_id: value.base.base.base.conference_id,
7438 g723_bitrate: value.base.base.base.g723_bitrate,
7439 stream_passthrough_id: value.base.base.base.stream_passthrough_id,
7440 associated_stream_id: value.base.base.base.associated_stream_id,
7441 dtmf_type: value.base.base.base.dtmf_type,
7442 mixing_mode: value.base.base.mixing_mode,
7443 direction: value.base.base.direction,
7444 requested_address_type: value.base.requested_address_type,
7445 audio_level_adjustment: value.audio_level_adjustment,
7446 latent_capabilities: [0; 36],
7447 },
7448 )
7449 }
7450 17 => {
7451 let value: WireOpenReceiveV17 = decode(message_id, payload)?;
7452 (
7453 value.base.base.call_reference,
7454 value.base.base.passthrough_party_id,
7455 value.base.base.packet_millis,
7456 value.base.base.codec,
7457 value.base.base.vad,
7458 value.base.base.rfc2833_payload,
7459 value.base.remote.to_ip(message_id)?,
7460 decode_port(value.base.remote_port, message_id, "source RTP port")?,
7461 value.base.base.encryption,
7462 OpenReceiveChannelWire {
7463 conference_id: value.base.base.conference_id,
7464 g723_bitrate: value.base.base.g723_bitrate,
7465 stream_passthrough_id: value.base.base.stream_passthrough_id,
7466 associated_stream_id: value.base.base.associated_stream_id,
7467 dtmf_type: value.base.base.dtmf_type,
7468 mixing_mode: value.base.mixing_mode,
7469 direction: value.base.direction,
7470 requested_address_type: value.requested_address_type,
7471 audio_level_adjustment: 0,
7472 latent_capabilities: [0; 36],
7473 },
7474 )
7475 }
7476 12..=16 => {
7477 let value: WireOpenReceiveV12 = decode(message_id, payload)?;
7478 (
7479 value.base.call_reference,
7480 value.base.passthrough_party_id,
7481 value.base.packet_millis,
7482 value.base.codec,
7483 value.base.vad,
7484 value.base.rfc2833_payload,
7485 value.remote.to_ip(message_id)?,
7486 decode_port(value.remote_port, message_id, "source RTP port")?,
7487 value.base.encryption,
7488 OpenReceiveChannelWire {
7489 conference_id: value.base.conference_id,
7490 g723_bitrate: value.base.g723_bitrate,
7491 stream_passthrough_id: value.base.stream_passthrough_id,
7492 associated_stream_id: value.base.associated_stream_id,
7493 dtmf_type: value.base.dtmf_type,
7494 mixing_mode: value.mixing_mode,
7495 direction: value.direction,
7496 requested_address_type: 0,
7497 audio_level_adjustment: 0,
7498 latent_capabilities: [0; 36],
7499 },
7500 )
7501 }
7502 _ => {
7503 let value: WireOpenReceiveV11 = decode(message_id, payload)?;
7504 (
7505 value.call_reference,
7506 value.passthrough_party_id,
7507 value.packet_millis,
7508 value.codec,
7509 value.vad,
7510 value.rfc2833_payload,
7511 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
7512 0,
7513 value.encryption,
7514 OpenReceiveChannelWire {
7515 conference_id: value.conference_id,
7516 g723_bitrate: value.g723_bitrate,
7517 stream_passthrough_id: value.stream_passthrough_id,
7518 associated_stream_id: value.associated_stream_id,
7519 dtmf_type: value.dtmf_type,
7520 mixing_mode: 0,
7521 direction: 0,
7522 requested_address_type: 0,
7523 audio_level_adjustment: 0,
7524 latent_capabilities: [0; 36],
7525 },
7526 )
7527 }
7528 };
7529 let telephone_event_payload = u8::try_from(rfc2833).map_err(|_| CodecError::InvalidValue {
7530 message_id,
7531 field: "RFC2833 payload",
7532 value: u64::from(rfc2833),
7533 })?;
7534 Ok(ServerMessage::OpenReceiveChannel {
7535 call_reference,
7536 passthrough_party_id,
7537 packet_ms,
7538 codec: Codec::from(codec),
7539 echo_cancellation: EchoCancellation::from(echo),
7540 telephone_event_payload,
7541 source_address,
7542 source_port,
7543 encryption: encryption.to_public(message_id)?,
7544 wire: (wire != canonical_open_receive_wire(call_reference, source_address, protocol))
7545 .then_some(wire),
7546 })
7547}
7548
7549fn decode_start_media(
7550 payload: &[u8],
7551 protocol: ProtocolVersion,
7552 message_id: u32,
7553) -> Result<ServerMessage, CodecError> {
7554 let (
7555 call_reference,
7556 passthrough_party_id,
7557 address,
7558 port,
7559 packet_ms,
7560 codec,
7561 precedence,
7562 silence_suppression,
7563 max_frames_per_packet,
7564 rfc2833,
7565 encryption,
7566 wire,
7567 ) = match protocol.wire() {
7568 21.. => {
7569 let value: WireStartMediaV21 = decode(message_id, payload)?;
7570 (
7571 value.base.base.call_reference,
7572 value.base.base.passthrough_party_id,
7573 value.base.base.remote.to_ip(message_id)?,
7574 value.base.base.remote_port,
7575 value.base.base.packet_millis,
7576 value.base.base.codec,
7577 value.base.base.precedence,
7578 value.base.base.silence_suppression,
7579 value.base.base.max_frames_per_packet,
7580 value.base.base.rfc2833_payload,
7581 value.base.base.encryption,
7582 StartMediaTransmissionWire {
7583 conference_id: value.base.base.conference_id,
7584 g723_bitrate: value.base.base.g723_bitrate,
7585 stream_passthrough_id: value.base.base.stream_passthrough_id,
7586 associated_stream_id: value.base.base.associated_stream_id,
7587 dtmf_type: value.base.base.dtmf_type,
7588 mixing_mode: value.base.mixing_mode,
7589 direction: value.base.direction,
7590 latent_capabilities: value.latent_capabilities.bytes,
7591 },
7592 )
7593 }
7594 17..=20 => {
7595 let value: WireStartMediaV17 = decode(message_id, payload)?;
7596 (
7597 value.base.call_reference,
7598 value.base.passthrough_party_id,
7599 value.base.remote.to_ip(message_id)?,
7600 value.base.remote_port,
7601 value.base.packet_millis,
7602 value.base.codec,
7603 value.base.precedence,
7604 value.base.silence_suppression,
7605 value.base.max_frames_per_packet,
7606 value.base.rfc2833_payload,
7607 value.base.encryption,
7608 StartMediaTransmissionWire {
7609 conference_id: value.base.conference_id,
7610 g723_bitrate: value.base.g723_bitrate,
7611 stream_passthrough_id: value.base.stream_passthrough_id,
7612 associated_stream_id: value.base.associated_stream_id,
7613 dtmf_type: value.base.dtmf_type,
7614 mixing_mode: value.mixing_mode,
7615 direction: value.direction,
7616 latent_capabilities: [0; 36],
7617 },
7618 )
7619 }
7620 12..=16 => {
7621 let value: WireStartMediaV12 = decode(message_id, payload)?;
7622 (
7623 value.base.call_reference,
7624 value.base.passthrough_party_id,
7625 value.base.remote.to_ip(message_id)?,
7626 value.base.remote_port,
7627 value.base.packet_millis,
7628 value.base.codec,
7629 value.base.precedence,
7630 value.base.silence_suppression,
7631 value.base.max_frames_per_packet,
7632 value.base.rfc2833_payload,
7633 value.base.encryption,
7634 StartMediaTransmissionWire {
7635 conference_id: value.base.conference_id,
7636 g723_bitrate: value.base.g723_bitrate,
7637 stream_passthrough_id: value.base.stream_passthrough_id,
7638 associated_stream_id: value.base.associated_stream_id,
7639 dtmf_type: value.base.dtmf_type,
7640 mixing_mode: value.mixing_mode,
7641 direction: value.direction,
7642 latent_capabilities: [0; 36],
7643 },
7644 )
7645 }
7646 _ => {
7647 let value: WireStartMediaV11 = decode(message_id, payload)?;
7648 (
7649 value.call_reference,
7650 value.passthrough_party_id,
7651 value.remote.to_ip(message_id)?,
7652 value.remote_port,
7653 value.packet_millis,
7654 value.codec,
7655 value.precedence,
7656 value.silence_suppression,
7657 value.max_frames_per_packet,
7658 value.rfc2833_payload,
7659 value.encryption,
7660 StartMediaTransmissionWire {
7661 conference_id: value.conference_id,
7662 g723_bitrate: value.g723_bitrate,
7663 stream_passthrough_id: value.stream_passthrough_id,
7664 associated_stream_id: value.associated_stream_id,
7665 dtmf_type: value.dtmf_type,
7666 mixing_mode: 0,
7667 direction: 0,
7668 latent_capabilities: [0; 36],
7669 },
7670 )
7671 }
7672 };
7673 let rtp_port = decode_port(port, message_id, "RTP port")?;
7674 let telephone_event_payload = u8::try_from(rfc2833).map_err(|_| CodecError::InvalidValue {
7675 message_id,
7676 field: "RFC2833 payload",
7677 value: u64::from(rfc2833),
7678 })?;
7679 Ok(ServerMessage::StartMediaTransmission {
7680 call_reference,
7681 passthrough_party_id,
7682 endpoint: MediaEndpoint {
7683 address,
7684 rtp_port,
7685 rtcp_port: rtp_port.saturating_add(1),
7686 codec: Codec::from(codec),
7687 packet_ms,
7688 max_frames_per_packet,
7689 telephone_event_payload,
7690 },
7691 silence_suppression: SilenceSuppression::from(silence_suppression),
7692 traffic_class: MediaTrafficClass::from_wire(u8::try_from(precedence).map_err(|_| {
7693 CodecError::InvalidValue {
7694 message_id,
7695 field: "media traffic class",
7696 value: u64::from(precedence),
7697 }
7698 })?),
7699 encryption: encryption.to_public(message_id)?,
7700 wire: (wire != canonical_start_media_wire(call_reference, protocol)).then_some(wire),
7701 })
7702}
7703
7704#[cfg(test)]
7705mod tests {
7706 use super::catalog::MessageDirection;
7707 use super::values::SoftKey;
7708 use super::wire::{FrameDecoder, MAX_FRAME_SIZE};
7709 use super::*;
7710
7711 fn fixture(source: &str) -> Vec<u8> {
7712 source
7713 .split_whitespace()
7714 .map(|byte| u8::from_str_radix(byte, 16).expect("valid fixture byte"))
7715 .collect()
7716 }
7717
7718 fn deterministic_payload(message_id: u32, protocol: u32, length: usize) -> Vec<u8> {
7719 let mut state = u64::from(message_id)
7720 ^ (u64::from(protocol) << 32)
7721 ^ (length as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
7722 (0..length)
7723 .map(|_| {
7724 state ^= state << 13;
7725 state ^= state >> 7;
7726 state ^= state << 17;
7727 state as u8
7728 })
7729 .collect()
7730 }
7731
7732 fn fuzz_lengths() -> impl Iterator<Item = usize> {
7733 (0..=96).chain([127, 255, 511, 1024, MAX_FRAME_SIZE - 12])
7734 }
7735
7736 const fn test_rtp_payload_number(value: u32) -> RtpPayloadNumber {
7737 match RtpPayloadNumber::new(value) {
7738 Ok(value) => value,
7739 Err(_) => panic!("test RTP payload number is out of range"),
7740 }
7741 }
7742
7743 fn typed_video_payload(arm: MultimediaVideoCapabilityArm) -> MultimediaPayload {
7744 let payload_number = match arm.codec() {
7745 Codec::H261 => 31,
7746 Codec::H263 => 34,
7747 Codec::H263Plus => 96,
7748 Codec::H264 => 97,
7749 _ => unreachable!("typed video arms always have a modeled codec"),
7750 };
7751 MultimediaPayload::new(
7752 test_rtp_payload_number(payload_number),
7753 MultimediaVideoCapability::new(
7754 1_024,
7755 [
7756 MultimediaPictureFormat {
7757 format: VideoFormat::Cif4,
7758 minimum_picture_interval: 1,
7759 },
7760 MultimediaPictureFormat {
7761 format: VideoFormat::Cif,
7762 minimum_picture_interval: 2,
7763 },
7764 ],
7765 7,
7766 arm,
7767 )
7768 .unwrap(),
7769 )
7770 }
7771
7772 #[test]
7773 fn every_catalogued_client_decoder_is_panic_free_for_bounded_property_corpus() {
7774 let protocols = [
7775 ProtocolVersion::V3,
7776 ProtocolVersion::V8,
7777 ProtocolVersion::V17,
7778 ProtocolVersion::V22,
7779 ];
7780 let mut cases = 0_usize;
7781 for message_id in MessageId::ALL_KNOWN
7782 .iter()
7783 .copied()
7784 .filter(|id| id.direction() == Some(MessageDirection::DeviceToServer))
7785 {
7786 for protocol in protocols {
7787 for length in fuzz_lengths() {
7788 let frame = Frame::new(
7789 protocol.wire(),
7790 message_id.wire_value(),
7791 deterministic_payload(message_id.wire_value(), protocol.wire(), length),
7792 );
7793 let _ = ClientMessage::decode_with_version(frame, protocol);
7794 cases += 1;
7795 }
7796 }
7797 }
7798 assert!(
7799 cases > 20_000,
7800 "property corpus unexpectedly shrank: {cases}"
7801 );
7802 }
7803
7804 #[test]
7805 fn every_catalogued_server_encoder_round_trips_all_decodable_bounded_inputs() {
7806 let protocols = [
7807 ProtocolVersion::V3,
7808 ProtocolVersion::V8,
7809 ProtocolVersion::V17,
7810 ProtocolVersion::V22,
7811 ];
7812 let mut decoded = 0_usize;
7813 let mut encoded = 0_usize;
7814 for message_id in MessageId::ALL_KNOWN
7815 .iter()
7816 .copied()
7817 .filter(|id| id.direction() == Some(MessageDirection::ServerToDevice))
7818 {
7819 for protocol in protocols {
7820 for length in fuzz_lengths() {
7821 let frame = Frame::new(
7822 protocol.wire(),
7823 message_id.wire_value(),
7824 deterministic_payload(message_id.wire_value(), protocol.wire(), length),
7825 );
7826 let Ok(message) = ServerMessage::decode(frame, protocol) else {
7827 continue;
7828 };
7829 decoded += 1;
7830 let Ok(bytes) = message.encode(protocol) else {
7831 continue;
7832 };
7833 assert!(bytes.len() <= MAX_FRAME_SIZE);
7834 let frames = FrameDecoder::new().push(&bytes).unwrap();
7835 assert_eq!(frames.len(), 1);
7836 assert_eq!(
7837 ServerMessage::decode(frames.into_iter().next().unwrap(), protocol)
7838 .unwrap(),
7839 message
7840 );
7841 encoded += 1;
7842 }
7843 }
7844 }
7845 assert!(
7846 decoded > 1_000,
7847 "decodable encoder corpus unexpectedly shrank: {decoded}"
7848 );
7849 assert!(
7850 encoded > 1_000,
7851 "encodable property corpus unexpectedly shrank: {encoded}"
7852 );
7853 }
7854
7855 #[test]
7856 fn registration_preserves_both_reported_address_families() {
7857 let message = ClientMessage::Register(RegistrationMessage {
7858 device_id: DeviceId::new("SEP001122334455").unwrap(),
7859 reported_address: Some(Ipv4Addr::new(192, 0, 2, 10)),
7860 reported_ipv6_address: Some("2001:db8::10".parse().unwrap()),
7861 device_type: DeviceType::Cisco7962,
7862 advertised_protocol: Some(ProtocolVersion::V22.wire()),
7863 features: PhoneFeatures::empty(),
7864 firmware: "test-load".into(),
7865 configuration_version_stamp: BoundedBytes::default(),
7866 wire: Some(RegistrationWireDetails {
7867 layout: RegistrationWireLayout::default(),
7868 station_user_id: 17,
7869 station_instance: 2,
7870 max_streams: 5,
7871 active_streams: 1,
7872 mac_address_and_padding: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0, 0, 0, 0, 0, 0],
7873 max_conferences: 3,
7874 active_conferences: 1,
7875 ipv4_address_scope: 3,
7876 max_lines: 6,
7877 ipv6_address_scope: 2,
7878 }),
7879 });
7880 let bytes = message.encode(ProtocolVersion::V22).unwrap();
7881 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
7882 assert_eq!(
7883 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
7884 message
7885 );
7886 }
7887
7888 #[test]
7889 fn registration_preserves_every_complete_canonical_prefix() {
7890 let mut payload = [0_u8; REGISTER_CANONICAL_BYTES];
7891 let device_id = b"SEP001122334455";
7892 payload[..device_id.len()].copy_from_slice(device_id);
7893 payload[16..20].copy_from_slice(&17_u32.to_le_bytes());
7894 payload[20..24].copy_from_slice(&2_u32.to_le_bytes());
7895 payload[24..28].copy_from_slice(&[192, 0, 2, 25]);
7896 payload[28..32].copy_from_slice(&DeviceType::Cisco7925.wire_value().to_le_bytes());
7897 payload[32..36].copy_from_slice(&5_u32.to_le_bytes());
7898 payload[36..40].copy_from_slice(&1_u32.to_le_bytes());
7899 payload[40..44].copy_from_slice(&ProtocolVersion::V11.wire().to_le_bytes());
7900 payload[44..48].copy_from_slice(&3_u32.to_le_bytes());
7901 payload[48..52].copy_from_slice(&1_u32.to_le_bytes());
7902 payload[52..64].copy_from_slice(&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 1, 2, 3, 4, 5, 6]);
7903 payload[64..68].copy_from_slice(&3_u32.to_le_bytes());
7904 payload[68..72].copy_from_slice(&6_u32.to_le_bytes());
7905 payload[72..88].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
7906 payload[88..92].copy_from_slice(&2_u32.to_le_bytes());
7907 payload[92..101].copy_from_slice(b"SCCP-test");
7908
7909 for prefix_bytes in [36, 40, 44, 48, 52, 64, 68, 72, 88, 92, 124] {
7910 let expected_payload = payload[..prefix_bytes].to_vec();
7911 let message = ClientMessage::decode_with_version(
7912 Frame::new(0, wire_id::REGISTER, expected_payload.clone()),
7913 ProtocolVersion::V22,
7914 )
7915 .unwrap();
7916 let ClientMessage::Register(registration) = &message else {
7917 unreachable!("registration frame decoded as another message")
7918 };
7919 assert_eq!(registration.device_type, DeviceType::Cisco7925);
7920 assert_eq!(
7921 registration.advertised_protocol,
7922 (prefix_bytes >= 44).then_some(ProtocolVersion::V11.wire())
7923 );
7924 assert_eq!(
7925 registration.reported_ipv6_address,
7926 (prefix_bytes >= 88).then_some(Ipv6Addr::LOCALHOST)
7927 );
7928 assert_eq!(
7929 registration.firmware,
7930 if prefix_bytes == REGISTER_CANONICAL_BYTES {
7931 "SCCP-test"
7932 } else {
7933 ""
7934 }
7935 );
7936 assert!(matches!(
7937 registration.wire,
7938 Some(RegistrationWireDetails {
7939 layout: RegistrationWireLayout::Canonical {
7940 prefix_bytes: actual
7941 },
7942 ..
7943 }) if usize::from(actual) == prefix_bytes
7944 ));
7945
7946 let encoded = message.encode(ProtocolVersion::V22).unwrap();
7947 let encoded_frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
7948 assert_eq!(encoded_frame.payload, expected_payload);
7949 }
7950 }
7951
7952 #[test]
7953 fn registration_preserves_alternate_32_byte_layout() {
7954 let message = ClientMessage::Register(RegistrationMessage {
7955 device_id: DeviceId::new("SEP001122334455").unwrap(),
7956 reported_address: None,
7957 reported_ipv6_address: None,
7958 device_type: DeviceType::Cisco7920,
7959 advertised_protocol: Some(ProtocolVersion::V3.wire()),
7960 features: PhoneFeatures::empty(),
7961 firmware: String::new(),
7962 configuration_version_stamp: BoundedBytes::default(),
7963 wire: Some(RegistrationWireDetails {
7964 layout: RegistrationWireLayout::Alternate32,
7965 station_user_id: 17,
7966 station_instance: 2,
7967 max_streams: 0,
7968 active_streams: 0,
7969 mac_address_and_padding: [0; 12],
7970 max_conferences: 0,
7971 active_conferences: 0,
7972 ipv4_address_scope: 0,
7973 max_lines: 0,
7974 ipv6_address_scope: 0,
7975 }),
7976 });
7977 let bytes = message.encode(ProtocolVersion::V22).unwrap();
7978 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
7979 assert_eq!(frame.payload.len(), REGISTER_ALTERNATE_BYTES);
7980 assert_eq!(
7981 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
7982 message
7983 );
7984 }
7985
7986 #[test]
7987 fn short_zero_protocol_registration_uses_legacy_fallback_and_round_trips() {
7988 for payload_bytes in [32, 44, 48] {
7989 let mut payload = vec![0_u8; payload_bytes];
7990 let device_id = b"SEP001122334455";
7991 payload[..device_id.len()].copy_from_slice(device_id);
7992 payload[28..32].copy_from_slice(&DeviceType::Cisco7925.wire_value().to_le_bytes());
7993
7994 let message = ClientMessage::decode_with_version(
7995 Frame::new(0, wire_id::REGISTER, payload.clone()),
7996 ProtocolVersion::V22,
7997 )
7998 .unwrap();
7999 let ClientMessage::Register(registration) = &message else {
8000 unreachable!("registration frame decoded as another message")
8001 };
8002 assert_eq!(registration.advertised_protocol, None);
8003
8004 let encoded = message.encode(ProtocolVersion::V22).unwrap();
8005 let encoded_frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
8006 assert_eq!(encoded_frame.payload, payload);
8007 }
8008 }
8009
8010 #[test]
8011 fn registration_encoder_rejects_values_omitted_by_selected_prefix() {
8012 let mut message = ClientMessage::Register(RegistrationMessage {
8013 device_id: DeviceId::new("SEP001122334455").unwrap(),
8014 reported_address: Some(Ipv4Addr::LOCALHOST),
8015 reported_ipv6_address: None,
8016 device_type: DeviceType::Cisco7925,
8017 advertised_protocol: None,
8018 features: PhoneFeatures::empty(),
8019 firmware: String::new(),
8020 configuration_version_stamp: BoundedBytes::default(),
8021 wire: Some(RegistrationWireDetails {
8022 layout: RegistrationWireLayout::Canonical { prefix_bytes: 36 },
8023 station_user_id: 0,
8024 station_instance: 1,
8025 max_streams: 5,
8026 active_streams: 0,
8027 mac_address_and_padding: [0; 12],
8028 max_conferences: 0,
8029 active_conferences: 0,
8030 ipv4_address_scope: 0,
8031 max_lines: 0,
8032 ipv6_address_scope: 0,
8033 }),
8034 });
8035 assert!(message.encode(ProtocolVersion::V22).is_ok());
8036
8037 let ClientMessage::Register(registration) = &mut message else {
8038 unreachable!("test message is registration")
8039 };
8040 registration.wire.as_mut().unwrap().active_streams = 1;
8041 assert!(matches!(
8042 message.encode(ProtocolVersion::V22),
8043 Err(CodecError::InvalidValue {
8044 field: "active streams",
8045 ..
8046 })
8047 ));
8048 }
8049
8050 #[test]
8051 fn registration_preserves_every_bounded_configuration_suffix() {
8052 let base = ClientMessage::Register(RegistrationMessage {
8053 device_id: DeviceId::new("SEP001122334455").unwrap(),
8054 reported_address: None,
8055 reported_ipv6_address: None,
8056 device_type: DeviceType::Cisco7962,
8057 advertised_protocol: Some(ProtocolVersion::V22.wire()),
8058 features: PhoneFeatures::empty(),
8059 firmware: "test-load".into(),
8060 configuration_version_stamp: BoundedBytes::default(),
8061 wire: Some(RegistrationWireDetails {
8062 layout: RegistrationWireLayout::default(),
8063 station_user_id: 0,
8064 station_instance: 1,
8065 max_streams: 0,
8066 active_streams: 0,
8067 mac_address_and_padding: [0; 12],
8068 max_conferences: 0,
8069 active_conferences: 0,
8070 ipv4_address_scope: 0,
8071 max_lines: 0,
8072 ipv6_address_scope: 0,
8073 }),
8074 });
8075
8076 for length in 0..=48 {
8077 let mut message = base.clone();
8078 let ClientMessage::Register(registration) = &mut message else {
8079 unreachable!("test message is registration")
8080 };
8081 registration.configuration_version_stamp =
8082 BoundedBytes::try_from(vec![0xa5; length]).unwrap();
8083 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8084 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8085 assert_eq!(frame.payload.len(), 124 + length);
8086 assert_eq!(
8087 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
8088 message
8089 );
8090 }
8091 }
8092
8093 #[test]
8094 fn registration_rejects_unsupported_payload_lengths() {
8095 for invalid_length in [33, 35, 37, 56, 76, 96, 123] {
8096 assert!(matches!(
8097 ClientMessage::decode_with_version(
8098 Frame::new(0, wire_id::REGISTER, vec![0; invalid_length]),
8099 ProtocolVersion::V22,
8100 ),
8101 Err(CodecError::InvalidValue {
8102 field: "registration payload length",
8103 ..
8104 })
8105 ));
8106 }
8107 assert!(matches!(
8108 ClientMessage::decode_with_version(
8109 Frame::new(0, wire_id::REGISTER, vec![0; 31]),
8110 ProtocolVersion::V22,
8111 ),
8112 Err(CodecError::Truncated { needed: 32, .. })
8113 ));
8114 assert!(matches!(
8115 ClientMessage::decode_with_version(
8116 Frame::new(0, wire_id::REGISTER, vec![0; 173]),
8117 ProtocolVersion::V22,
8118 ),
8119 Err(CodecError::TrailingBytes { .. })
8120 ));
8121 }
8122
8123 #[test]
8124 fn alarm_preserves_both_supported_wire_lengths() {
8125 for parameters in [None, Some([0x1122_3344, 0xaabb_ccdd])] {
8126 let message = ClientMessage::Alarm {
8127 severity: AlarmSeverity::Warning,
8128 text: "TFTP load failed".into(),
8129 parameters,
8130 };
8131 let bytes = message.encode(ProtocolVersion::V17).unwrap();
8132 assert_eq!(bytes.len(), if parameters.is_some() { 104 } else { 96 });
8133 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8134 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8135 }
8136 }
8137
8138 #[test]
8139 fn capabilities_response_consumes_all_eighteen_fixed_slots() {
8140 let capabilities = (0_u32..12)
8141 .map(|index| MediaCapability {
8142 codec: if index.is_multiple_of(2) {
8143 Codec::Pcmu
8144 } else {
8145 Codec::Pcma
8146 },
8147 max_packet_ms: index + 1,
8148 codec_parameters: [index as u8; 8],
8149 })
8150 .collect::<Vec<_>>();
8151 let message = ClientMessage::CapabilitiesResponse(capabilities);
8152
8153 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8154 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8155
8156 assert_eq!(frame.payload.len(), 4 + 18 * 16);
8157 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8158 }
8159
8160 #[test]
8161 fn capabilities_response_selects_the_extended_fixed_reservoir() {
8162 let capabilities = (0_u32..20)
8163 .map(|index| MediaCapability {
8164 codec: Codec::Unknown(0x1000 + index),
8165 max_packet_ms: index + 1,
8166 codec_parameters: [index as u8; 8],
8167 })
8168 .collect::<Vec<_>>();
8169 let message = ClientMessage::CapabilitiesResponse(capabilities);
8170
8171 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8172 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8173
8174 assert_eq!(frame.payload.len(), 4 + 24 * 16);
8175 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8176 }
8177
8178 #[test]
8179 fn capabilities_response_accepts_exact_counted_prefixes() {
8180 let capabilities = vec![
8181 MediaCapability {
8182 codec: Codec::Pcmu,
8183 max_packet_ms: 2,
8184 codec_parameters: [1; 8],
8185 },
8186 MediaCapability {
8187 codec: Codec::Pcma,
8188 max_packet_ms: 3,
8189 codec_parameters: [2; 8],
8190 },
8191 ];
8192 let mut payload = 2_u32.to_le_bytes().to_vec();
8193 for capability in &capabilities {
8194 payload.extend_from_slice(
8195 &encode(
8196 wire_id::CAPABILITIES_RES,
8197 &WireMediaCapability {
8198 codec: capability.codec.wire_value(),
8199 max_frames_per_packet: capability.max_packet_ms,
8200 codec_parameters: capability.codec_parameters,
8201 },
8202 )
8203 .unwrap(),
8204 );
8205 }
8206 assert_eq!(payload.len(), 4 + 2 * 16);
8207 assert_eq!(
8208 ClientMessage::decode(Frame::new(
8209 ProtocolVersion::V22.wire(),
8210 wire_id::CAPABILITIES_RES,
8211 payload,
8212 ))
8213 .unwrap(),
8214 ClientMessage::CapabilitiesResponse(capabilities)
8215 );
8216 }
8217
8218 #[test]
8219 fn capabilities_response_rejects_incomplete_or_mismatched_counted_storage() {
8220 let mut partial = vec![0; 4 + 19 * 16];
8221 partial[..4].copy_from_slice(&1_u32.to_le_bytes());
8222 assert!(matches!(
8223 ClientMessage::decode(Frame::new(
8224 ProtocolVersion::V22.wire(),
8225 wire_id::CAPABILITIES_RES,
8226 partial,
8227 )),
8228 Err(CodecError::TrailingBytes { count: 288, .. })
8229 ));
8230
8231 let mut too_many = vec![0; 4 + 18 * 16];
8232 too_many[..4].copy_from_slice(&19_u32.to_le_bytes());
8233 assert!(matches!(
8234 ClientMessage::decode(Frame::new(
8235 ProtocolVersion::V22.wire(),
8236 wire_id::CAPABILITIES_RES,
8237 too_many,
8238 )),
8239 Err(CodecError::CountTooLarge {
8240 field: "audio capabilities",
8241 maximum: 18,
8242 ..
8243 })
8244 ));
8245 }
8246
8247 #[test]
8248 fn capabilities_response_discards_nonzero_inactive_reservoir_storage() {
8249 let mut payload = vec![0xa5; 4 + 24 * 16];
8250 payload[..4].copy_from_slice(&1_u32.to_le_bytes());
8251 payload[4..20].copy_from_slice(
8252 &encode(
8253 wire_id::CAPABILITIES_RES,
8254 &WireMediaCapability {
8255 codec: Codec::Pcmu.wire_value(),
8256 max_frames_per_packet: 2,
8257 codec_parameters: [1; 8],
8258 },
8259 )
8260 .unwrap(),
8261 );
8262 assert_eq!(
8263 ClientMessage::decode(Frame::new(
8264 ProtocolVersion::V22.wire(),
8265 wire_id::CAPABILITIES_RES,
8266 payload,
8267 ))
8268 .unwrap(),
8269 ClientMessage::CapabilitiesResponse(vec![MediaCapability {
8270 codec: Codec::Pcmu,
8271 max_packet_ms: 2,
8272 codec_parameters: [1; 8],
8273 }])
8274 );
8275 }
8276
8277 #[test]
8278 fn enbloc_call_selects_exact_version_and_length_layouts() {
8279 for (version, line_instance, payload_bytes) in [
8280 (ProtocolVersion::V3, 0, 24),
8281 (ProtocolVersion::V17, 2, 28),
8282 (ProtocolVersion::V22, 2, 32),
8283 ] {
8284 let message = ClientMessage::EnblocCall {
8285 called_party: "2001".into(),
8286 line_instance,
8287 };
8288 let bytes = message.encode(version).unwrap();
8289 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8290 assert_eq!(frame.payload.len(), payload_bytes);
8291 assert_eq!(
8292 ClientMessage::decode_with_version(frame, version).unwrap(),
8293 message
8294 );
8295 }
8296
8297 let early_with_line = encode(
8298 wire_id::ENBLOC_CALL,
8299 &WireEnblocWithLine::<24, 0> {
8300 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8301 .unwrap(),
8302 line_instance: 2,
8303 },
8304 )
8305 .unwrap();
8306 assert_eq!(
8307 ClientMessage::decode_with_version(
8308 Frame::new(
8309 ProtocolVersion::V3.wire(),
8310 wire_id::ENBLOC_CALL,
8311 early_with_line,
8312 ),
8313 ProtocolVersion::V3,
8314 )
8315 .unwrap(),
8316 ClientMessage::EnblocCall {
8317 called_party: "2001".into(),
8318 line_instance: 2,
8319 }
8320 );
8321
8322 let packed = encode(
8323 wire_id::ENBLOC_CALL,
8324 &WireEnblocWithLine::<25, 0> {
8325 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8326 .unwrap(),
8327 line_instance: 2,
8328 },
8329 )
8330 .unwrap();
8331 assert_eq!(packed.len(), 29);
8332 assert_eq!(
8333 ClientMessage::decode_with_version(
8334 Frame::new(
8335 ProtocolVersion::V22.wire(),
8336 wire_id::ENBLOC_CALL,
8337 packed.clone(),
8338 ),
8339 ProtocolVersion::V22,
8340 )
8341 .unwrap(),
8342 ClientMessage::EnblocCall {
8343 called_party: "2001".into(),
8344 line_instance: 2,
8345 }
8346 );
8347
8348 let mut padded = encode(
8349 wire_id::ENBLOC_CALL,
8350 &WireEnblocWithLine::<25, 0> {
8351 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8352 .unwrap(),
8353 line_instance: 1,
8354 },
8355 )
8356 .unwrap();
8357 padded.extend_from_slice(&[0; 3]);
8358 assert_eq!(padded.len(), 32);
8359 assert_eq!(
8360 ClientMessage::decode_with_version(
8361 Frame::new(ProtocolVersion::V22.wire(), wire_id::ENBLOC_CALL, padded,),
8362 ProtocolVersion::V22,
8363 )
8364 .unwrap(),
8365 ClientMessage::EnblocCall {
8366 called_party: "2001".into(),
8367 line_instance: 1,
8368 }
8369 );
8370
8371 let aligned = encode(
8372 wire_id::ENBLOC_CALL,
8373 &WireEnblocWithLine::<25, 3> {
8374 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8375 .unwrap(),
8376 line_instance: MAX_STATION_BUTTON_INSTANCE,
8377 },
8378 )
8379 .unwrap();
8380 assert_eq!(
8381 ClientMessage::decode_with_version(
8382 Frame::new(ProtocolVersion::V22.wire(), wire_id::ENBLOC_CALL, aligned),
8383 ProtocolVersion::V22,
8384 )
8385 .unwrap(),
8386 ClientMessage::EnblocCall {
8387 called_party: "2001".into(),
8388 line_instance: MAX_STATION_BUTTON_INSTANCE,
8389 }
8390 );
8391
8392 let invalid_aligned_line = encode(
8393 wire_id::ENBLOC_CALL,
8394 &WireEnblocWithLine::<25, 3> {
8395 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8396 .unwrap(),
8397 line_instance: MAX_STATION_BUTTON_INSTANCE + 1,
8398 },
8399 )
8400 .unwrap();
8401 assert!(
8402 ClientMessage::decode_with_version(
8403 Frame::new(
8404 ProtocolVersion::V22.wire(),
8405 wire_id::ENBLOC_CALL,
8406 invalid_aligned_line,
8407 ),
8408 ProtocolVersion::V22,
8409 )
8410 .is_err()
8411 );
8412
8413 let invalid_line = encode(
8414 wire_id::ENBLOC_CALL,
8415 &WireEnblocWithLine::<25, 0> {
8416 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8417 .unwrap(),
8418 line_instance: MAX_STATION_BUTTON_INSTANCE + 1,
8419 },
8420 )
8421 .unwrap();
8422 assert!(matches!(
8423 ClientMessage::decode_with_version(
8424 Frame::new(
8425 ProtocolVersion::V22.wire(),
8426 wire_id::ENBLOC_CALL,
8427 invalid_line,
8428 ),
8429 ProtocolVersion::V22,
8430 ),
8431 Err(CodecError::InvalidValue {
8432 field: "line instance",
8433 ..
8434 })
8435 ));
8436 assert!(matches!(
8437 ClientMessage::EnblocCall {
8438 called_party: "2001".into(),
8439 line_instance: MAX_STATION_BUTTON_INSTANCE + 1,
8440 }
8441 .encode(ProtocolVersion::V22),
8442 Err(CodecError::InvalidValue {
8443 field: "line instance",
8444 ..
8445 })
8446 ));
8447 assert!(matches!(
8448 ClientMessage::decode_with_version(
8449 Frame::new(ProtocolVersion::V18.wire(), wire_id::ENBLOC_CALL, packed,),
8450 ProtocolVersion::V18,
8451 ),
8452 Err(CodecError::InvalidLength(wire_id::ENBLOC_CALL))
8453 ));
8454 }
8455
8456 #[test]
8457 fn on_hook_accepts_only_fieldless_and_identified_layouts() {
8458 assert_eq!(
8459 ClientMessage::decode_with_version(
8460 Frame::new(ProtocolVersion::V22.wire(), wire_id::ON_HOOK, Vec::new()),
8461 ProtocolVersion::V22,
8462 )
8463 .unwrap(),
8464 ClientMessage::OnHook {
8465 line_instance: 0,
8466 call_reference: 0,
8467 }
8468 );
8469
8470 let identified = encode(
8471 wire_id::ON_HOOK,
8472 &WireLineCall {
8473 line_instance: 2,
8474 call_reference: 42,
8475 },
8476 )
8477 .unwrap();
8478 assert_eq!(
8479 ClientMessage::decode_with_version(
8480 Frame::new(ProtocolVersion::V22.wire(), wire_id::ON_HOOK, identified),
8481 ProtocolVersion::V22,
8482 )
8483 .unwrap(),
8484 ClientMessage::OnHook {
8485 line_instance: 2,
8486 call_reference: 42,
8487 }
8488 );
8489
8490 for payload_bytes in [1, 2, 3, 4, 5, 6, 7, 9, 12] {
8491 assert!(matches!(
8492 ClientMessage::decode_with_version(
8493 Frame::new(
8494 ProtocolVersion::V22.wire(),
8495 wire_id::ON_HOOK,
8496 vec![0; payload_bytes],
8497 ),
8498 ProtocolVersion::V22,
8499 ),
8500 Err(CodecError::InvalidLength(wire_id::ON_HOOK))
8501 ));
8502 }
8503 }
8504
8505 #[test]
8506 fn call_count_request_preserves_known_length_selected_dialects() {
8507 for (message, expected_payload_len) in [
8508 (
8509 ClientMessage::CallCountRequest(CallCountRequestPayload::Empty),
8510 0,
8511 ),
8512 (
8513 ClientMessage::CallCountRequest(CallCountRequestPayload::LegacyWord(0x1234_5678)),
8514 4,
8515 ),
8516 (
8517 ClientMessage::CallCountRequest(CallCountRequestPayload::Extended(
8518 [0xa5; CALL_COUNT_REQUEST_EXTENDED_BYTES],
8519 )),
8520 CALL_COUNT_REQUEST_EXTENDED_BYTES,
8521 ),
8522 ] {
8523 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8524 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8525
8526 assert_eq!(frame.payload.len(), expected_payload_len);
8527 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8528 }
8529 }
8530
8531 #[test]
8532 fn call_count_request_rejects_unknown_payload_length() {
8533 let frame = Frame {
8534 protocol_version: ProtocolVersion::V22.wire(),
8535 message_id: wire_id::CALL_COUNT_REQ,
8536 payload: vec![0; 8],
8537 };
8538
8539 assert!(matches!(
8540 ClientMessage::decode(frame),
8541 Err(CodecError::InvalidValue {
8542 field: "call-count request payload length",
8543 value: 8,
8544 ..
8545 })
8546 ));
8547 }
8548
8549 #[test]
8550 fn call_count_response_zero_pads_all_forty_two_line_slots() {
8551 let message = ServerMessage::CallCountResponse(CallCountResponse {
8552 total_configured_lines: 2,
8553 starting_line_instance: 1,
8554 line_data: vec![
8555 CallCountLineData {
8556 max_calls: 4,
8557 busy_trigger: 2,
8558 },
8559 CallCountLineData {
8560 max_calls: 2,
8561 busy_trigger: 1,
8562 },
8563 ],
8564 });
8565
8566 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8567 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8568
8569 assert_eq!(frame.payload.len(), 12 + 42 * 4);
8570 assert_eq!(
8571 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
8572 message
8573 );
8574 }
8575
8576 #[test]
8577 fn connection_statistics_reject_oversized_quality_payloads() {
8578 let payload = WireConnectionStatisticsV19 {
8579 directory_number: WireAlignedText::new(
8580 wire_id::CONNECTION_STATISTICS_RES,
8581 "directory number",
8582 "2002",
8583 )
8584 .unwrap(),
8585 call_reference: 42,
8586 processing: StatisticsProcessing::Clear.wire_value(),
8587 statistics: WireConnectionStatisticsTail {
8588 counters: WireConnectionStatisticsCounters {
8589 packets_sent: 1,
8590 octets_sent: 2,
8591 packets_received: 3,
8592 octets_received: 4,
8593 packets_lost: 5,
8594 jitter_millis: 6,
8595 latency_millis: 7,
8596 },
8597 quality_size: (CONNECTION_QUALITY_MAX_BYTES + 1) as u32,
8598 },
8599 quality: vec![0; CONNECTION_QUALITY_MAX_BYTES + 1],
8600 };
8601 let mut encoded = encode(wire_id::CONNECTION_STATISTICS_RES, &payload).unwrap();
8602 pad_dynamic_payload(&mut encoded);
8603 let frame = Frame::new(
8604 ProtocolVersion::V22.wire(),
8605 wire_id::CONNECTION_STATISTICS_RES,
8606 encoded,
8607 );
8608 assert!(matches!(
8609 ClientMessage::decode_with_version(frame, ProtocolVersion::V22),
8610 Err(CodecError::CountTooLarge {
8611 field: "quality statistics",
8612 maximum: CONNECTION_QUALITY_MAX_BYTES,
8613 ..
8614 })
8615 ));
8616 }
8617
8618 #[test]
8619 fn packed_connection_statistics_accept_zero_padding_without_a_quality_size() {
8620 let mut payload = encode(
8621 wire_id::CONNECTION_STATISTICS_RES,
8622 &WireConnectionStatisticsPackedBase {
8623 directory_number: WireAlignedText::new(
8624 wire_id::CONNECTION_STATISTICS_RES,
8625 "directory number",
8626 "2002",
8627 )
8628 .unwrap(),
8629 call_reference: 0x1122_3344,
8630 processing: StatisticsProcessing::DoNotClear.wire_value() as u8,
8631 counters: WireConnectionStatisticsCounters {
8632 packets_sent: 0x0102_0304,
8633 octets_sent: 0x1112_1314,
8634 packets_received: 0x2122_2324,
8635 octets_received: 0x3132_3334,
8636 packets_lost: 0x4142_4344,
8637 jitter_millis: 0x5152_5354,
8638 latency_millis: 0x6162_6364,
8639 },
8640 },
8641 )
8642 .unwrap();
8643 assert_eq!(payload.len(), 61);
8644 payload.extend_from_slice(&[0; 3]);
8645
8646 let message = ClientMessage::decode_with_version(
8647 Frame::new(
8648 ProtocolVersion::V22.wire(),
8649 wire_id::CONNECTION_STATISTICS_RES,
8650 payload,
8651 ),
8652 ProtocolVersion::V22,
8653 )
8654 .unwrap();
8655 assert_eq!(
8656 message,
8657 ClientMessage::ConnectionStatisticsResponse(ConnectionStatistics {
8658 directory_number: "2002".into(),
8659 call_reference: 0x1122_3344,
8660 processing: StatisticsProcessing::DoNotClear,
8661 packets_sent: 0x0102_0304,
8662 octets_sent: 0x1112_1314,
8663 packets_received: 0x2122_2324,
8664 octets_received: 0x3132_3334,
8665 packets_lost: 0x4142_4344,
8666 jitter_millis: 0x5152_5354,
8667 latency_millis: 0x6162_6364,
8668 quality: ConnectionQualityStatistics::new(Vec::new()).unwrap(),
8669 })
8670 );
8671 }
8672
8673 #[test]
8674 fn packed_connection_statistics_accepts_a_non_word_aligned_declared_tail() {
8675 let quality = (0_u8..110).collect::<Vec<_>>();
8676 let mut payload = encode(
8677 wire_id::CONNECTION_STATISTICS_RES,
8678 &WireConnectionStatisticsPackedPrefix {
8679 base: WireConnectionStatisticsPackedBase {
8680 directory_number: WireAlignedText::new(
8681 wire_id::CONNECTION_STATISTICS_RES,
8682 "directory number",
8683 "2002",
8684 )
8685 .unwrap(),
8686 call_reference: 42,
8687 processing: StatisticsProcessing::Clear.wire_value() as u8,
8688 counters: WireConnectionStatisticsCounters {
8689 packets_sent: 1,
8690 octets_sent: 2,
8691 packets_received: 3,
8692 octets_received: 4,
8693 packets_lost: 5,
8694 jitter_millis: 6,
8695 latency_millis: 7,
8696 },
8697 },
8698 quality_size: quality.len() as u32,
8699 },
8700 )
8701 .unwrap();
8702 payload.extend_from_slice(&quality);
8703 assert_eq!(payload.len(), 175);
8704
8705 let decoded = ClientMessage::decode_with_version(
8706 Frame::new(
8707 ProtocolVersion::V22.wire(),
8708 wire_id::CONNECTION_STATISTICS_RES,
8709 payload.clone(),
8710 ),
8711 ProtocolVersion::V22,
8712 )
8713 .unwrap();
8714 let ClientMessage::ConnectionStatisticsResponse(statistics) = decoded else {
8715 panic!("expected connection statistics response");
8716 };
8717 assert_eq!(statistics.directory_number, "2002");
8718 assert_eq!(statistics.call_reference, 42);
8719 assert_eq!(statistics.quality.as_bytes(), quality);
8720
8721 payload.extend_from_slice(&[0; 3]);
8722 assert!(
8723 ClientMessage::decode_with_version(
8724 Frame::new(
8725 ProtocolVersion::V22.wire(),
8726 wire_id::CONNECTION_STATISTICS_RES,
8727 payload.clone(),
8728 ),
8729 ProtocolVersion::V22,
8730 )
8731 .is_ok()
8732 );
8733 *payload.last_mut().unwrap() = 1;
8734 assert!(
8735 ClientMessage::decode_with_version(
8736 Frame::new(
8737 ProtocolVersion::V22.wire(),
8738 wire_id::CONNECTION_STATISTICS_RES,
8739 payload,
8740 ),
8741 ProtocolVersion::V22,
8742 )
8743 .is_err()
8744 );
8745 }
8746
8747 #[test]
8748 fn aligned_connection_statistics_discards_inactive_fixed_reservoir_storage() {
8749 let quality = (0_u8..113).collect::<Vec<_>>();
8750 let mut payload = encode(
8751 wire_id::CONNECTION_STATISTICS_RES,
8752 &WireConnectionStatisticsV19Prefix {
8753 directory_number: WireAlignedText::new(
8754 wire_id::CONNECTION_STATISTICS_RES,
8755 "directory number",
8756 "2002",
8757 )
8758 .unwrap(),
8759 call_reference: 42,
8760 processing: StatisticsProcessing::Clear.wire_value(),
8761 statistics: WireConnectionStatisticsTail {
8762 counters: WireConnectionStatisticsCounters {
8763 packets_sent: 1,
8764 octets_sent: 2,
8765 packets_received: 3,
8766 octets_received: 4,
8767 packets_lost: 5,
8768 jitter_millis: 6,
8769 latency_millis: 7,
8770 },
8771 quality_size: quality.len() as u32,
8772 },
8773 },
8774 )
8775 .unwrap();
8776 payload.extend_from_slice(&quality);
8777 payload.extend_from_slice(&[0; CONNECTION_QUALITY_MAX_BYTES - 113]);
8778 assert_eq!(payload.len(), 668);
8779 assert_eq!(payload.len() - 68 - quality.len(), 487);
8780
8781 let decoded = ClientMessage::decode_with_version(
8782 Frame::new(
8783 ProtocolVersion::V22.wire(),
8784 wire_id::CONNECTION_STATISTICS_RES,
8785 payload.clone(),
8786 ),
8787 ProtocolVersion::V22,
8788 )
8789 .unwrap();
8790 let ClientMessage::ConnectionStatisticsResponse(statistics) = decoded else {
8791 panic!("expected connection statistics response");
8792 };
8793 assert_eq!(statistics.quality.as_bytes(), quality);
8794
8795 payload[68 + quality.len()..].fill(0xa5);
8796 let decoded = ClientMessage::decode_with_version(
8797 Frame::new(
8798 ProtocolVersion::V22.wire(),
8799 wire_id::CONNECTION_STATISTICS_RES,
8800 payload,
8801 ),
8802 ProtocolVersion::V22,
8803 )
8804 .unwrap();
8805 let ClientMessage::ConnectionStatisticsResponse(statistics) = decoded else {
8806 panic!("expected connection statistics response");
8807 };
8808 assert_eq!(statistics.quality.as_bytes(), quality);
8809 }
8810
8811 #[test]
8812 fn protocol_19_connection_statistics_accepts_each_transition_shape() {
8813 let message = ClientMessage::ConnectionStatisticsResponse(ConnectionStatistics {
8814 directory_number: "2002".into(),
8815 call_reference: 42,
8816 processing: StatisticsProcessing::Clear,
8817 packets_sent: 1,
8818 octets_sent: 2,
8819 packets_received: 3,
8820 octets_received: 4,
8821 packets_lost: 5,
8822 jitter_millis: 6,
8823 latency_millis: 8,
8824 quality: ConnectionQualityStatistics::new(vec![0xa1, 0xb2, 0xc3]).unwrap(),
8825 });
8826
8827 for encoded_as in [ProtocolVersion::V18, ProtocolVersion::V19] {
8828 let frame = FrameDecoder::new()
8829 .push(&message.encode(encoded_as).unwrap())
8830 .unwrap()
8831 .remove(0);
8832 assert_eq!(
8833 ClientMessage::decode_with_version(
8834 Frame::new(
8835 ProtocolVersion::V19.wire(),
8836 wire_id::CONNECTION_STATISTICS_RES,
8837 frame.payload,
8838 ),
8839 ProtocolVersion::V19,
8840 )
8841 .unwrap(),
8842 message,
8843 "encoded with protocol {}",
8844 encoded_as.wire(),
8845 );
8846 }
8847 }
8848
8849 #[test]
8850 fn canonical_connection_statistics_from_protocol_19_use_the_aligned_layout() {
8851 let quality = vec![0xa1, 0xb2, 0xc3];
8852 let message = ClientMessage::ConnectionStatisticsResponse(ConnectionStatistics {
8853 directory_number: "2002".into(),
8854 call_reference: 0x1122_3344,
8855 processing: StatisticsProcessing::DoNotClear,
8856 packets_sent: 0x0102_0304,
8857 octets_sent: 0x1112_1314,
8858 packets_received: 0x2122_2324,
8859 octets_received: 0x3132_3334,
8860 packets_lost: 0x4142_4344,
8861 jitter_millis: 0x5152_5354,
8862 latency_millis: 0x6162_6364,
8863 quality: ConnectionQualityStatistics::new(quality.clone()).unwrap(),
8864 });
8865 let encoded = message.encode(ProtocolVersion::V22).unwrap();
8866 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
8867
8868 assert_eq!(&frame.payload[28..32], &0x1122_3344_u32.to_le_bytes());
8869 assert_eq!(&frame.payload[32..36], &1_u32.to_le_bytes());
8870 assert_eq!(&frame.payload[36..40], &0x0102_0304_u32.to_le_bytes());
8871 assert_eq!(&frame.payload[40..44], &0x1112_1314_u32.to_le_bytes());
8872 assert_eq!(&frame.payload[44..48], &0x2122_2324_u32.to_le_bytes());
8873 assert_eq!(&frame.payload[48..52], &0x3132_3334_u32.to_le_bytes());
8874 assert_eq!(&frame.payload[52..56], &0x4142_4344_u32.to_le_bytes());
8875 assert_eq!(&frame.payload[56..60], &0x5152_5354_u32.to_le_bytes());
8876 assert_eq!(&frame.payload[60..64], &0x6162_6364_u32.to_le_bytes());
8877 assert_eq!(&frame.payload[64..68], &3_u32.to_le_bytes());
8878 assert_eq!(&frame.payload[68..71], quality.as_slice());
8879 assert_eq!(
8880 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
8881 message
8882 );
8883 }
8884
8885 #[test]
8886 fn media_wire_schemas_round_trip_byte_for_byte() {
8887 let start = fixture(include_str!(
8888 "../../tests/fixtures/golden/start_media_transmission_v17.hex"
8889 ));
8890 let start_value: WireStartMediaV17 = decode(0x008a, &start[12..]).unwrap();
8891 assert_eq!(encode(0x008a, &start_value).unwrap(), &start[12..]);
8892 let start_frame = FrameDecoder::new().push(&start).unwrap().remove(0);
8893 let start_message = ServerMessage::decode(start_frame, ProtocolVersion::V17).unwrap();
8894 assert_eq!(start_message.encode(ProtocolVersion::V17).unwrap(), start);
8895
8896 let open = fixture(include_str!(
8897 "../../tests/fixtures/golden/open_receive_channel_v17.hex"
8898 ));
8899 let open_value: WireOpenReceiveV17 = decode(0x0105, &open[12..]).unwrap();
8900 assert_eq!(encode(0x0105, &open_value).unwrap(), &open[12..]);
8901 let open_frame = FrameDecoder::new().push(&open).unwrap().remove(0);
8902 let open_message = ServerMessage::decode(open_frame, ProtocolVersion::V17).unwrap();
8903 assert_eq!(open_message.encode(ProtocolVersion::V17).unwrap(), open);
8904
8905 let ack = fixture(include_str!(
8906 "../../tests/fixtures/golden/start_media_transmission_ack_v20.hex"
8907 ));
8908 let ack_value: WireStartMediaAckV20 = decode(0x0154, &ack[12..]).unwrap();
8909 assert_eq!(encode(0x0154, &ack_value).unwrap(), &ack[12..]);
8910 let ack_frame = FrameDecoder::new().push(&ack).unwrap().remove(0);
8911 let ack_message =
8912 ClientMessage::decode_with_version(ack_frame, ProtocolVersion::V20).unwrap();
8913 assert_eq!(ack_message.encode(ProtocolVersion::V20).unwrap(), ack);
8914 }
8915
8916 #[test]
8917 fn audio_media_version_boundaries_have_exact_payload_sizes() {
8918 let endpoint = MediaEndpoint {
8919 address: "192.0.2.20".parse().unwrap(),
8920 rtp_port: 16_000,
8921 rtcp_port: 16_001,
8922 codec: Codec::Pcmu,
8923 packet_ms: 20,
8924 max_frames_per_packet: 2,
8925 telephone_event_payload: 101,
8926 };
8927 for (version, open_size, start_size) in [
8928 (11, 92, 108),
8929 (12, 108, 116),
8930 (16, 108, 116),
8931 (17, 128, 132),
8932 (18, 132, 132),
8933 (20, 132, 132),
8934 (21, 168, 168),
8935 (22, 168, 168),
8936 ] {
8937 let protocol = ProtocolVersion::new(version).unwrap();
8938 let open = ServerMessage::OpenReceiveChannel {
8939 call_reference: 7,
8940 passthrough_party_id: 9,
8941 packet_ms: 20,
8942 codec: Codec::Pcmu,
8943 echo_cancellation: EchoCancellation::On,
8944 telephone_event_payload: 101,
8945 source_address: endpoint.address,
8946 source_port: endpoint.rtp_port,
8947 encryption: None,
8948 wire: None,
8949 };
8950 let open_bytes = open.encode(protocol).unwrap();
8951 assert_eq!(open_bytes.len() - 12, open_size, "protocol {version}");
8952 let decoded = ServerMessage::decode(
8953 FrameDecoder::new().push(&open_bytes).unwrap().remove(0),
8954 protocol,
8955 )
8956 .unwrap();
8957 assert_eq!(decoded.encode(protocol).unwrap(), open_bytes);
8958
8959 let start = ServerMessage::StartMediaTransmission {
8960 call_reference: 7,
8961 passthrough_party_id: 9,
8962 endpoint,
8963 silence_suppression: SilenceSuppression::Off,
8964 traffic_class: MediaTrafficClass::default(),
8965 encryption: None,
8966 wire: None,
8967 };
8968 let start_bytes = start.encode(protocol).unwrap();
8969 assert_eq!(start_bytes.len() - 12, start_size, "protocol {version}");
8970 let decoded = ServerMessage::decode(
8971 FrameDecoder::new().push(&start_bytes).unwrap().remove(0),
8972 protocol,
8973 )
8974 .unwrap();
8975 assert_eq!(decoded.encode(protocol).unwrap(), start_bytes);
8976 }
8977 }
8978
8979 #[test]
8980 fn audio_acknowledgements_keep_conference_and_call_references_distinct() {
8981 for (protocol, address, open_size, start_size, failure_size) in [
8982 (
8983 ProtocolVersion::V16,
8984 "192.0.2.21".parse().unwrap(),
8985 20,
8986 24,
8987 20,
8988 ),
8989 (
8990 ProtocolVersion::V17,
8991 "2001:db8::21".parse().unwrap(),
8992 36,
8993 40,
8994 36,
8995 ),
8996 ] {
8997 let open = ClientMessage::OpenReceiveChannelAck {
8998 status: MediaStatus::Ok,
8999 address,
9000 port: 16_000,
9001 passthrough_party_id: 9,
9002 call_reference: 7,
9003 };
9004 let open_bytes = open.encode(protocol).unwrap();
9005 assert_eq!(open_bytes.len() - 12, open_size);
9006 assert_eq!(
9007 ClientMessage::decode_with_version(
9008 FrameDecoder::new().push(&open_bytes).unwrap().remove(0),
9009 protocol,
9010 )
9011 .unwrap(),
9012 open
9013 );
9014
9015 let start = ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
9016 conference_id: 6,
9017 passthrough_party_id: 9,
9018 call_reference: 7,
9019 status: MediaStatus::Ok,
9020 address,
9021 port: 16_000,
9022 wire: None,
9023 });
9024 let start_bytes = start.encode(protocol).unwrap();
9025 assert_eq!(start_bytes.len() - 12, start_size);
9026 let decoded = ClientMessage::decode_with_version(
9027 FrameDecoder::new().push(&start_bytes).unwrap().remove(0),
9028 protocol,
9029 )
9030 .unwrap();
9031 assert_eq!(decoded, start);
9032
9033 let failure = ClientMessage::MediaTransmissionFailure {
9034 conference_id: 6,
9035 passthrough_party_id: 9,
9036 address,
9037 port: 16_000,
9038 call_reference: 7,
9039 status: MediaStatus::UnspecifiedError,
9040 };
9041 let failure_bytes = failure.encode(protocol).unwrap();
9042 assert_eq!(failure_bytes.len() - 12, failure_size);
9043 assert_eq!(
9044 ClientMessage::decode_with_version(
9045 FrameDecoder::new().push(&failure_bytes).unwrap().remove(0),
9046 protocol,
9047 )
9048 .unwrap(),
9049 failure
9050 );
9051 }
9052
9053 for message_id in [
9054 wire_id::CLOSE_RECEIVE_CHANNEL,
9055 wire_id::STOP_MEDIA_TRANSMISSION,
9056 ] {
9057 let payload = [0_u8; 16];
9058 let frame = Frame::new(ProtocolVersion::V22.wire(), message_id, payload.to_vec());
9059 let message = ServerMessage::decode(frame, ProtocolVersion::V22).unwrap();
9060 assert_eq!(message.encode(ProtocolVersion::V22).unwrap().len() - 12, 16);
9061
9062 let truncated = Frame::new(
9063 ProtocolVersion::V22.wire(),
9064 message_id,
9065 payload[..12].to_vec(),
9066 );
9067 assert!(matches!(
9068 ServerMessage::decode(truncated, ProtocolVersion::V22),
9069 Err(CodecError::Truncated { .. })
9070 ));
9071 }
9072 }
9073
9074 #[test]
9075 fn session_and_video_envelopes_preserve_every_wire_byte() {
9076 for (protocol, address, expected_size) in [
9077 (ProtocolVersion::V16, "192.0.2.30".parse().unwrap(), 8),
9078 (ProtocolVersion::V17, "2001:db8::30".parse().unwrap(), 24),
9079 ] {
9080 for message in [
9081 ControlMessage::StartSessionTransmission(SessionTransmission {
9082 remote_address: address,
9083 session_type: 0x1122_3344,
9084 }),
9085 ControlMessage::StopSessionTransmission(SessionTransmission {
9086 remote_address: address,
9087 session_type: 0x5566_7788,
9088 }),
9089 ] {
9090 let bytes = message.encode(protocol).unwrap();
9091 assert_eq!(bytes.len() - 12, expected_size);
9092 let decoded = ControlMessage::decode(
9093 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9094 protocol,
9095 )
9096 .unwrap();
9097 assert_eq!(decoded.encode(protocol).unwrap(), bytes);
9098 }
9099 }
9100
9101 for (version, address, expected_size) in [
9102 (11, "0.0.0.0".parse().unwrap(), 164),
9103 (12, "192.0.2.31".parse().unwrap(), 172),
9104 (16, "192.0.2.31".parse().unwrap(), 172),
9105 (17, "2001:db8::31".parse().unwrap(), 192),
9106 ] {
9107 let protocol = ProtocolVersion::new(version).unwrap();
9108 let message = ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
9109 conference_id: 42.into(),
9110 passthrough_party_id: 9.into(),
9111 line_instance: 1,
9112 call_reference: 7.into(),
9113 payload: typed_video_payload(MultimediaVideoCapabilityArm::H264 {
9114 profile: 100,
9115 level: 42,
9116 custom_max_mbps: 40_500,
9117 custom_max_fs: 1_620,
9118 custom_max_dpb: 8_100,
9119 custom_max_br_and_cpb: 10_000,
9120 }),
9121 conference_creator: true,
9122 encryption: None,
9123 stream_passthrough_id: 10,
9124 associated_stream_id: 11,
9125 source: MediaEndpointAddress {
9126 address,
9127 port: if version < 12 { 0 } else { 16_000 },
9128 },
9129 requested_address_type: if version >= 17 {
9130 IpAddressType::Ipv6
9131 } else {
9132 IpAddressType::Ipv4
9133 },
9134 });
9135 let bytes = message.encode(protocol).unwrap();
9136 assert_eq!(bytes.len() - 12, expected_size, "protocol {version}");
9137 let decoded = ServerMessage::decode(
9138 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9139 protocol,
9140 )
9141 .unwrap();
9142 assert_eq!(decoded.encode(protocol).unwrap(), bytes);
9143 }
9144
9145 for (protocol, address, expected_size) in [
9146 (ProtocolVersion::V16, "192.0.2.32".parse().unwrap(), 168),
9147 (ProtocolVersion::V17, "2001:db8::32".parse().unwrap(), 184),
9148 ] {
9149 let message = ServerMessage::StartMultimediaTransmission(StartMultimediaTransmission {
9150 conference_id: 42.into(),
9151 passthrough_party_id: 9.into(),
9152 endpoint: MediaEndpointAddress {
9153 address,
9154 port: 16_002,
9155 },
9156 call_reference: 7.into(),
9157 payload: typed_video_payload(MultimediaVideoCapabilityArm::H264 {
9158 profile: 100,
9159 level: 42,
9160 custom_max_mbps: 40_500,
9161 custom_max_fs: 1_620,
9162 custom_max_dpb: 8_100,
9163 custom_max_br_and_cpb: 10_000,
9164 }),
9165 traffic_class: MediaTrafficClass::from_wire(184),
9166 encryption: None,
9167 stream_passthrough_id: 10,
9168 associated_stream_id: 11,
9169 });
9170 let bytes = message.encode(protocol).unwrap();
9171 assert_eq!(bytes.len() - 12, expected_size);
9172 let traffic_class_offset = match protocol.wire() {
9173 17.. => 60,
9174 _ => 44,
9175 };
9176 assert_eq!(
9177 &bytes[traffic_class_offset..traffic_class_offset + 4],
9178 &184_u32.to_le_bytes()
9179 );
9180 let decoded = ServerMessage::decode(
9181 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9182 protocol,
9183 )
9184 .unwrap();
9185 assert_eq!(decoded.encode(protocol).unwrap(), bytes);
9186 }
9187
9188 let miscellaneous = ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
9189 conference_id: 42.into(),
9190 passthrough_party_id: 9.into(),
9191 call_reference: 7.into(),
9192 command: values::MiscCommandType::LostPartialPicture,
9193 data: BoundedBytes::try_from((0_u8..36).collect::<Vec<_>>()).unwrap(),
9194 });
9195 let bytes = miscellaneous.encode(ProtocolVersion::V22).unwrap();
9196 assert_eq!(bytes.len() - 12, 52);
9197 let decoded = ServerMessage::decode(
9198 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9199 ProtocolVersion::V22,
9200 )
9201 .unwrap();
9202 assert_eq!(decoded, miscellaneous);
9203
9204 for (message_id, protocol, expected) in [
9205 (wire_id::OPEN_MULTIMEDIA_CHANNEL, ProtocolVersion::V17, 192),
9206 (
9207 wire_id::START_MULTIMEDIA_TRANSMISSION,
9208 ProtocolVersion::V17,
9209 184,
9210 ),
9211 (wire_id::MISCELLANEOUS_COMMAND, ProtocolVersion::V22, 52),
9212 ] {
9213 for actual in [expected - 1, expected + 1] {
9214 let frame = Frame::new(protocol.wire(), message_id, vec![0; actual]);
9215 assert!(ServerMessage::decode(frame, protocol).is_err());
9216 }
9217 }
9218 for (protocol, expected) in [(ProtocolVersion::V16, 8), (ProtocolVersion::V17, 24)] {
9219 for actual in [expected - 1, expected + 1] {
9220 let frame = Frame::new(
9221 protocol.wire(),
9222 wire_id::START_SESSION_TRANSMISSION,
9223 vec![0; actual],
9224 );
9225 assert!(ControlMessage::decode(frame, protocol).is_err());
9226 }
9227 }
9228 }
9229
9230 #[test]
9231 fn unsupported_decoded_multimedia_payloads_are_lossless_and_provenance_bound() {
9232 let mut words = [0; MULTIMEDIA_CAPABILITY_BYTES / 4];
9233 words[0] = 2_048;
9234 words[1] = 1;
9235 words[2] = VideoFormat::Cif.wire_value();
9236 words[3] = 2;
9237 words[12] = 7;
9238 words[13..].copy_from_slice(&[61, 62, 63, 64, 65, 66]);
9239 let capability = multimedia_capability_bytes(words);
9240 let payload = MultimediaPayload::from_wire(
9241 0,
9242 test_rtp_payload_number(97),
9243 capability,
9244 Codec::H265,
9245 MultimediaPayloadDirection::Receive,
9246 ProtocolVersion::V17,
9247 );
9248 assert_eq!(payload.codec(), Codec::H265);
9249 assert_eq!(payload.video_capability(), None);
9250 let open = ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
9251 conference_id: 42.into(),
9252 passthrough_party_id: 9.into(),
9253 line_instance: 1,
9254 call_reference: 7.into(),
9255 payload: payload.clone(),
9256 conference_creator: false,
9257 encryption: None,
9258 stream_passthrough_id: 10,
9259 associated_stream_id: 0,
9260 source: MediaEndpointAddress {
9261 address: "192.0.2.31".parse().unwrap(),
9262 port: 16_000,
9263 },
9264 requested_address_type: IpAddressType::Ipv4,
9265 });
9266 let encoded = open.encode(ProtocolVersion::V17).unwrap();
9267 let decoded = ServerMessage::decode(
9268 FrameDecoder::new().push(&encoded).unwrap().remove(0),
9269 ProtocolVersion::V17,
9270 )
9271 .unwrap();
9272 assert_eq!(decoded.encode(ProtocolVersion::V17).unwrap(), encoded);
9273 assert!(matches!(
9274 open.encode(ProtocolVersion::V16),
9275 Err(CodecError::InvalidValue {
9276 message_id: wire_id::OPEN_MULTIMEDIA_CHANNEL,
9277 field: "multimedia payload provenance",
9278 ..
9279 })
9280 ));
9281
9282 let start = ServerMessage::StartMultimediaTransmission(StartMultimediaTransmission {
9283 conference_id: 42.into(),
9284 passthrough_party_id: 9.into(),
9285 endpoint: MediaEndpointAddress {
9286 address: "192.0.2.32".parse().unwrap(),
9287 port: 16_002,
9288 },
9289 call_reference: 7.into(),
9290 payload,
9291 traffic_class: MediaTrafficClass::from_wire(136),
9292 encryption: None,
9293 stream_passthrough_id: 11,
9294 associated_stream_id: 0,
9295 });
9296 assert!(matches!(
9297 start.encode(ProtocolVersion::V17),
9298 Err(CodecError::InvalidValue {
9299 message_id: wire_id::START_MULTIMEDIA_TRANSMISSION,
9300 field: "multimedia payload provenance",
9301 ..
9302 })
9303 ));
9304 }
9305
9306 #[test]
9307 fn capabilities_incompatible_with_outer_compression_remain_opaque_and_lossless() {
9308 let message = ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
9309 conference_id: 42.into(),
9310 passthrough_party_id: 9.into(),
9311 line_instance: 1,
9312 call_reference: 7.into(),
9313 payload: typed_video_payload(MultimediaVideoCapabilityArm::H264 {
9314 profile: 100,
9315 level: 42,
9316 custom_max_mbps: 40_500,
9317 custom_max_fs: 1_620,
9318 custom_max_dpb: 8_100,
9319 custom_max_br_and_cpb: 10_000,
9320 }),
9321 conference_creator: false,
9322 encryption: None,
9323 stream_passthrough_id: 10,
9324 associated_stream_id: 0,
9325 source: MediaEndpointAddress {
9326 address: "192.0.2.31".parse().unwrap(),
9327 port: 16_000,
9328 },
9329 requested_address_type: IpAddressType::Ipv4,
9330 });
9331 let mut mismatched = message.encode(ProtocolVersion::V17).unwrap();
9332 let compression_offset = super::wire::HEADER_SIZE + 8;
9333 mismatched[compression_offset..compression_offset + 4]
9334 .copy_from_slice(&Codec::H263.wire_value().to_le_bytes());
9335
9336 let decoded = ServerMessage::decode(
9337 FrameDecoder::new().push(&mismatched).unwrap().remove(0),
9338 ProtocolVersion::V17,
9339 )
9340 .unwrap();
9341 let ServerMessage::OpenMultimediaChannel(open) = &decoded else {
9342 panic!("multimedia message decoded as a different command");
9343 };
9344 assert_eq!(open.payload.codec(), Codec::H263);
9345 assert_eq!(open.payload.compression_codec(), Codec::H263);
9346 assert_eq!(open.payload.video_capability(), None);
9347 assert_eq!(decoded.encode(ProtocolVersion::V17).unwrap(), mismatched);
9348 assert_ne!(decoded, message);
9349
9350 let mut invalid_payload_number = mismatched;
9351 let descriptor_offset = super::wire::HEADER_SIZE + 20;
9352 invalid_payload_number[descriptor_offset + 4..descriptor_offset + 8]
9353 .copy_from_slice(&128_u32.to_le_bytes());
9354 assert!(matches!(
9355 ServerMessage::decode(
9356 FrameDecoder::new()
9357 .push(&invalid_payload_number)
9358 .unwrap()
9359 .remove(0),
9360 ProtocolVersion::V17,
9361 ),
9362 Err(CodecError::InvalidValue {
9363 field: "RTP payload number",
9364 value: 128,
9365 ..
9366 })
9367 ));
9368 }
9369
9370 #[test]
9371 fn typed_multimedia_video_arms_encode_at_the_evidenced_offsets() {
9372 let arms = [
9373 (
9374 MultimediaVideoCapabilityArm::H261 {
9375 temporal_spatial_trade_off_capability: 11,
9376 still_image_transmission: 12,
9377 },
9378 [11, 12, 0, 0, 0, 0],
9379 ),
9380 (
9381 MultimediaVideoCapabilityArm::H263 {
9382 capability_bitfield: 21,
9383 annex_n_and_w_future_use: 22,
9384 },
9385 [21, 22, 0, 0, 0, 0],
9386 ),
9387 (
9388 MultimediaVideoCapabilityArm::H263Plus {
9389 model_number: 31,
9390 bandwidth: 32,
9391 },
9392 [31, 32, 0, 0, 0, 0],
9393 ),
9394 (
9395 MultimediaVideoCapabilityArm::H264 {
9396 profile: 41,
9397 level: 42,
9398 custom_max_mbps: 43,
9399 custom_max_fs: 44,
9400 custom_max_dpb: 45,
9401 custom_max_br_and_cpb: 46,
9402 },
9403 [41, 42, 43, 44, 45, 46],
9404 ),
9405 ];
9406
9407 for (arm, expected_arm) in arms {
9408 let payload = typed_video_payload(arm);
9409 let bytes = multimedia_capability_to_wire(&payload);
9410 let words = multimedia_capability_words(bytes);
9411 assert_eq!(words[0], 1_024);
9412 assert_eq!(words[1], 2);
9413 assert_eq!(
9414 &words[2..6],
9415 &[
9416 VideoFormat::Cif4.wire_value(),
9417 1,
9418 VideoFormat::Cif.wire_value(),
9419 2,
9420 ]
9421 );
9422 assert_eq!(&words[6..12], &[0; 6]);
9423 assert_eq!(words[12], 7);
9424 assert_eq!(&words[13..], &expected_arm);
9425
9426 let decoded = decoded_multimedia_capability(bytes, arm.codec());
9427 let MultimediaCapabilityState::Video(decoded) = decoded else {
9428 panic!("typed codec arm was not decoded");
9429 };
9430 assert_eq!(decoded.arm(), arm);
9431 assert_eq!(decoded.picture_formats().len(), 2);
9432 let decoded_payload = MultimediaPayload::from_decoded(
9433 payload.descriptor(),
9434 MultimediaCapabilityState::Video(decoded),
9435 MultimediaPayloadDirection::Transmit,
9436 ProtocolVersion::V17,
9437 arm.codec(),
9438 );
9439 assert_eq!(multimedia_capability_to_wire(&decoded_payload), bytes);
9440 }
9441 }
9442
9443 #[test]
9444 fn multimedia_descriptor_carries_the_negotiated_rtp_mapping() {
9445 for (arm, expected_payload_number) in [
9446 (
9447 MultimediaVideoCapabilityArm::H261 {
9448 temporal_spatial_trade_off_capability: 0,
9449 still_image_transmission: 0,
9450 },
9451 31,
9452 ),
9453 (
9454 MultimediaVideoCapabilityArm::H263 {
9455 capability_bitfield: 0,
9456 annex_n_and_w_future_use: 0,
9457 },
9458 34,
9459 ),
9460 (
9461 MultimediaVideoCapabilityArm::H263Plus {
9462 model_number: 0,
9463 bandwidth: 0,
9464 },
9465 96,
9466 ),
9467 (
9468 MultimediaVideoCapabilityArm::H264 {
9469 profile: 0,
9470 level: 0,
9471 custom_max_mbps: 0,
9472 custom_max_fs: 0,
9473 custom_max_dpb: 0,
9474 custom_max_br_and_cpb: 0,
9475 },
9476 97,
9477 ),
9478 ] {
9479 let payload = typed_video_payload(arm);
9480 let descriptor = payload.descriptor();
9481 assert_eq!(descriptor.rfc_number(), 0);
9482 assert_eq!(descriptor.payload_number().get(), expected_payload_number);
9483 assert_eq!(payload.codec(), arm.codec());
9484 assert_eq!(
9485 encode(
9486 wire_id::OPEN_MULTIMEDIA_CHANNEL,
9487 &WireMultimediaPayloadDescriptor::from(descriptor)
9488 )
9489 .unwrap(),
9490 [0, 0, 0, 0, expected_payload_number, 0, 0, 0,]
9491 );
9492 }
9493
9494 let payload = typed_video_payload(MultimediaVideoCapabilityArm::H263 {
9495 capability_bitfield: 0,
9496 annex_n_and_w_future_use: 0,
9497 });
9498 let descriptor = MultimediaPayloadDescriptor::new(4, payload.payload_number());
9499 assert_eq!(
9500 encode(
9501 wire_id::OPEN_MULTIMEDIA_CHANNEL,
9502 &WireMultimediaPayloadDescriptor::from(descriptor),
9503 )
9504 .unwrap(),
9505 [4, 0, 0, 0, 34, 0, 0, 0]
9506 );
9507 }
9508
9509 #[test]
9510 fn multimedia_acknowledgements_use_distinct_versioned_layouts() {
9511 for (protocol, address, open_size, start_size) in [
9512 (ProtocolVersion::V16, "192.0.2.33".parse().unwrap(), 20, 24),
9513 (
9514 ProtocolVersion::V17,
9515 "2001:db8::33".parse().unwrap(),
9516 36,
9517 40,
9518 ),
9519 ] {
9520 let open =
9521 ClientMessage::OpenMultimediaReceiveChannelAck(OpenMultimediaReceiveChannelAck {
9522 status: MediaStatus::Ok,
9523 endpoint: MediaEndpointAddress {
9524 address,
9525 port: 16_000,
9526 },
9527 passthrough_party_id: 9.into(),
9528 call_reference: 7.into(),
9529 });
9530 let open_bytes = open.encode(protocol).unwrap();
9531 assert_eq!(open_bytes.len() - 12, open_size);
9532 assert_eq!(
9533 ClientMessage::decode_with_version(
9534 FrameDecoder::new().push(&open_bytes).unwrap().remove(0),
9535 protocol,
9536 )
9537 .unwrap(),
9538 open
9539 );
9540
9541 let start =
9542 ClientMessage::StartMultimediaTransmissionAck(StartMultimediaTransmissionAck {
9543 conference_id: 42.into(),
9544 passthrough_party_id: 9.into(),
9545 call_reference: 7.into(),
9546 endpoint: MediaEndpointAddress {
9547 address,
9548 port: 16_002,
9549 },
9550 status: MediaStatus::Ok,
9551 });
9552 let start_bytes = start.encode(protocol).unwrap();
9553 assert_eq!(start_bytes.len() - 12, start_size);
9554 assert_eq!(
9555 ClientMessage::decode_with_version(
9556 FrameDecoder::new().push(&start_bytes).unwrap().remove(0),
9557 protocol,
9558 )
9559 .unwrap(),
9560 start
9561 );
9562 }
9563 }
9564
9565 #[test]
9566 fn port_messages_switch_layouts_at_protocol_twenty() {
9567 for (protocol, request_size, close_size, response_size) in [
9568 (ProtocolVersion::V19, 16, 12, 24),
9569 (ProtocolVersion::V20, 24, 16, 44),
9570 ] {
9571 let extended = protocol.wire() >= 20;
9572 let request = ServerMessage::PortRequest(PortRequest {
9573 conference_id: 42.into(),
9574 call_reference: 7.into(),
9575 passthrough_party_id: 9.into(),
9576 transport: MediaTransport::Rtp,
9577 address_type: extended.then_some(IpAddressType::Ipv4AndIpv6),
9578 media_type: extended.then_some(MediaType::Audio),
9579 });
9580 let request_bytes = request.encode(protocol).unwrap();
9581 assert_eq!(request_bytes.len() - 12, request_size);
9582 assert_eq!(
9583 ServerMessage::decode(
9584 FrameDecoder::new().push(&request_bytes).unwrap().remove(0),
9585 protocol,
9586 )
9587 .unwrap(),
9588 request
9589 );
9590
9591 let close = ServerMessage::PortClose(PortClose {
9592 conference_id: 42.into(),
9593 call_reference: 7.into(),
9594 passthrough_party_id: 9.into(),
9595 media_type: extended.then_some(MediaType::Audio),
9596 });
9597 let close_bytes = close.encode(protocol).unwrap();
9598 assert_eq!(close_bytes.len() - 12, close_size);
9599 assert_eq!(
9600 ServerMessage::decode(
9601 FrameDecoder::new().push(&close_bytes).unwrap().remove(0),
9602 protocol,
9603 )
9604 .unwrap(),
9605 close
9606 );
9607
9608 let response = ControlMessage::PortResponse(PortEndpoint {
9609 conference_id: 42,
9610 call_reference: 7,
9611 passthrough_party_id: 9,
9612 address: if extended {
9613 "2001:db8::34".parse().unwrap()
9614 } else {
9615 "192.0.2.34".parse().unwrap()
9616 },
9617 rtp_port: 16_000,
9618 rtcp_port: 16_001,
9619 media_type: extended.then_some(MediaType::Audio),
9620 });
9621 let response_bytes = response.encode(protocol).unwrap();
9622 assert_eq!(response_bytes.len() - 12, response_size);
9623 assert_eq!(
9624 ControlMessage::decode(
9625 FrameDecoder::new().push(&response_bytes).unwrap().remove(0),
9626 protocol,
9627 )
9628 .unwrap(),
9629 response
9630 );
9631 }
9632 }
9633
9634 #[test]
9635 fn wire_encryption_rejects_invalid_lengths_without_debugging_secrets() {
9636 let encryption = WireEncryptionInfo {
9637 algorithm: EncryptionMethod::Aes128HmacSha1_80.wire_value(),
9638 key_length: 17,
9639 salt_length: 16,
9640 key: [0xa5; 16],
9641 salt: [0x5a; 16],
9642 mki_present: 1,
9643 key_derivation_rate: 64,
9644 };
9645 let debug = format!("{encryption:?}");
9646 assert!(debug.contains("<redacted>"));
9647 assert!(!debug.contains("165"));
9648 assert!(!debug.contains("90"));
9649
9650 let error = encryption
9651 .to_public(wire_id::OPEN_MULTIMEDIA_CHANNEL)
9652 .unwrap_err();
9653 assert!(matches!(
9654 error,
9655 CodecError::SecretTooLong {
9656 field: "media encryption key",
9657 actual: 17,
9658 maximum: 16,
9659 }
9660 ));
9661 assert!(!error.to_string().contains("165"));
9662 }
9663
9664 #[test]
9665 fn wire_encryption_preserves_bytes_after_declared_lengths() {
9666 let wire = WireEncryptionInfo {
9667 algorithm: EncryptionMethod::Aes128HmacSha1_80.wire_value(),
9668 key_length: 1,
9669 salt_length: 1,
9670 key: [0xa5, 0x7f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
9671 salt: [0x5a, 0x6f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
9672 mki_present: 0,
9673 key_derivation_rate: 0,
9674 };
9675 let encryption = wire.to_public(wire_id::OPEN_MULTIMEDIA_CHANNEL).unwrap();
9676
9677 assert_eq!(WireEncryptionInfo::from_public(encryption.as_ref()), wire);
9678 assert_eq!(encryption.as_ref().unwrap().key(), &[0xa5]);
9679 assert_eq!(encryption.as_ref().unwrap().salt(), &[0x5a]);
9680 }
9681
9682 #[test]
9683 fn announcement_messages_use_bounded_fixed_wire_layouts() {
9684 let message = ControlMessage::StartAnnouncement {
9685 announcements: vec![AnnouncementEntry {
9686 locale: 1,
9687 country: 46,
9688 tone: Tone::Zip,
9689 }],
9690 end_of_ack: EndOfAnnouncementAck::Required,
9691 conference_id: 42,
9692 matrix_conference_party_ids: vec![7, 9],
9693 hearing_conference_party_mask: 0b11,
9694 play_mode: AnnouncementPlayMode::Continuous,
9695 };
9696 let bytes = message.encode(ProtocolVersion::V22).unwrap();
9697 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
9698 assert_eq!(frame.message_id, wire_id::START_ANNOUNCEMENT);
9699 assert_eq!(frame.payload.len(), 464);
9700 assert_eq!(
9701 ControlMessage::decode(frame.clone(), ProtocolVersion::V22).unwrap(),
9702 message
9703 );
9704
9705 let mut truncated = frame;
9706 truncated.payload.pop();
9707 assert!(matches!(
9708 ControlMessage::decode(truncated, ProtocolVersion::V22),
9709 Err(CodecError::Truncated {
9710 message_id: wire_id::START_ANNOUNCEMENT,
9711 needed: 464,
9712 actual: 463,
9713 })
9714 ));
9715
9716 for (message, expected_payload_len) in [
9717 (ControlMessage::StopAnnouncement { conference_id: 42 }, 4),
9718 (
9719 ControlMessage::AnnouncementFinish {
9720 conference_id: 42,
9721 play_status: AnnouncementPlayStatus::Unknown(3),
9722 },
9723 8,
9724 ),
9725 ] {
9726 let bytes = message.encode(ProtocolVersion::V22).unwrap();
9727 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
9728 assert_eq!(frame.payload.len(), expected_payload_len);
9729 assert_eq!(
9730 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9731 message
9732 );
9733 }
9734 }
9735
9736 #[test]
9737 fn conference_lifecycle_messages_use_documented_wire_sizes() {
9738 let server_messages = [
9739 (
9740 ControlMessage::ClearConference {
9741 conference_id: 42.into(),
9742 service_number: 3,
9743 },
9744 wire_id::CLEAR_CONFERENCE,
9745 8,
9746 ),
9747 (
9748 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
9749 conference_id: 42.into(),
9750 reserved_participants: 8,
9751 resource_type: ConferenceResourceType::Conference,
9752 application_id: 7.into(),
9753 application_conference_id: "festival-42".into(),
9754 application_data: "main-stage".into(),
9755 passthrough_data: vec![1, 2, 3],
9756 }),
9757 wire_id::CREATE_CONFERENCE_REQ,
9758 80,
9759 ),
9760 (
9761 ControlMessage::DeleteConferenceRequest {
9762 conference_id: 42.into(),
9763 },
9764 wire_id::DELETE_CONFERENCE_REQ,
9765 4,
9766 ),
9767 (
9768 ControlMessage::ModifyConferenceRequest(ModifyConferenceRequest {
9769 conference_id: 42.into(),
9770 reserved_participants: 12,
9771 application_id: 7.into(),
9772 application_conference_id: "festival-42".into(),
9773 application_data: "main-stage".into(),
9774 passthrough_data: vec![4, 5],
9775 }),
9776 wire_id::MODIFY_CONFERENCE_REQ,
9777 76,
9778 ),
9779 (
9780 ControlMessage::AuditConferenceRequest,
9781 wire_id::AUDIT_CONFERENCE_REQ,
9782 0,
9783 ),
9784 ];
9785 for (message, expected_id, expected_payload_len) in server_messages {
9786 let frame = FrameDecoder::new()
9787 .push(&message.encode(ProtocolVersion::V22).unwrap())
9788 .unwrap()
9789 .remove(0);
9790 assert_eq!(frame.message_id, expected_id);
9791 assert_eq!(frame.payload.len(), expected_payload_len);
9792 assert_eq!(
9793 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9794 message
9795 );
9796 }
9797
9798 let audit = AuditConferenceResponse {
9799 last: 1,
9800 entries: vec![AuditConferenceEntry {
9801 conference_id: 42.into(),
9802 resource_type: ConferenceResourceType::Conference,
9803 reserved_participants: 8,
9804 active_participants: 3,
9805 application_id: 7.into(),
9806 application_conference_id: "festival-42".into(),
9807 application_data: "main-stage".into(),
9808 }],
9809 };
9810 let client_messages = [
9811 (
9812 ControlMessage::CreateConferenceResponse(CreateConferenceResponse {
9813 conference_id: 42.into(),
9814 result: CreateConferenceResult::Ok,
9815 passthrough_data: vec![1, 2, 3],
9816 }),
9817 wire_id::CREATE_CONFERENCE_RES,
9818 16,
9819 ),
9820 (
9821 ControlMessage::DeleteConferenceResponse {
9822 conference_id: 42.into(),
9823 result: DeleteConferenceResult::Ok,
9824 },
9825 wire_id::DELETE_CONFERENCE_RES,
9826 8,
9827 ),
9828 (
9829 ControlMessage::ModifyConferenceResponse(ModifyConferenceResponse {
9830 conference_id: 42.into(),
9831 result: ModifyConferenceResult::Ok,
9832 passthrough_data: vec![4, 5],
9833 }),
9834 wire_id::MODIFY_CONFERENCE_RES,
9835 16,
9836 ),
9837 (
9838 ControlMessage::AuditConferenceResponse(audit),
9839 wire_id::AUDIT_CONFERENCE_RES,
9840 84,
9841 ),
9842 ];
9843 for (message, expected_id, expected_payload_len) in client_messages {
9844 let frame = FrameDecoder::new()
9845 .push(&message.encode(ProtocolVersion::V22).unwrap())
9846 .unwrap()
9847 .remove(0);
9848 assert_eq!(frame.message_id, expected_id);
9849 assert_eq!(frame.payload.len(), expected_payload_len);
9850 assert_eq!(
9851 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9852 message
9853 );
9854 }
9855 }
9856
9857 #[test]
9858 fn conference_participant_messages_and_application_changes_round_trip() {
9859 let participant = ConferenceParticipant {
9860 call_reference: 100.into(),
9861 presentation_restrictions: PartyInformationRestrictions::CALLING_NUMBER
9862 | PartyInformationRestrictions::LAST_REDIRECT_NAME,
9863 name: "Festival Caller".into(),
9864 number: "1001".into(),
9865 conference_name: "Main Stage".into(),
9866 };
9867 let server_messages = [
9868 (
9869 ControlMessage::AddParticipantRequest(AddParticipantRequest {
9870 conference_id: 42.into(),
9871 participant: participant.clone(),
9872 }),
9873 wire_id::ADD_PARTICIPANT_REQ,
9874 108,
9875 ),
9876 (
9877 ControlMessage::DropParticipantRequest {
9878 conference_id: 42.into(),
9879 call_reference: 100.into(),
9880 },
9881 wire_id::DROP_PARTICIPANT_REQ,
9882 8,
9883 ),
9884 (
9885 ControlMessage::AuditParticipantRequest {
9886 conference_id: 42.into(),
9887 },
9888 wire_id::AUDIT_PARTICIPANT_REQ,
9889 4,
9890 ),
9891 ];
9892 for (message, expected_id, expected_payload_len) in server_messages {
9893 let frame = FrameDecoder::new()
9894 .push(&message.encode(ProtocolVersion::V22).unwrap())
9895 .unwrap()
9896 .remove(0);
9897 assert_eq!(frame.message_id, expected_id);
9898 assert_eq!(frame.payload.len(), expected_payload_len);
9899 assert_eq!(
9900 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9901 message
9902 );
9903 }
9904
9905 let client_messages = [
9906 (
9907 ControlMessage::AddParticipantResponse(AddParticipantResponse {
9908 conference_id: 42.into(),
9909 call_reference: 100.into(),
9910 result: AddParticipantResult::Ok,
9911 bridge_participant_id: BoundedBytes::try_from(vec![3; 257]).unwrap(),
9912 }),
9913 wire_id::ADD_PARTICIPANT_RES,
9914 272,
9915 ),
9916 (
9917 ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
9918 result: AuditParticipantResult::Ok,
9919 last: 1,
9920 conference_id: 42.into(),
9921 number_of_entries: 2,
9922 participant_entries: vec![1, 2, 3, 4],
9923 }),
9924 wire_id::AUDIT_PARTICIPANT_RES,
9925 20,
9926 ),
9927 ];
9928 for (message, expected_id, expected_payload_len) in client_messages {
9929 let frame = FrameDecoder::new()
9930 .push(&message.encode(ProtocolVersion::V22).unwrap())
9931 .unwrap()
9932 .remove(0);
9933 assert_eq!(frame.message_id, expected_id);
9934 assert_eq!(frame.payload.len(), expected_payload_len);
9935 assert_eq!(
9936 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9937 message
9938 );
9939 }
9940
9941 let change = ConferenceParticipantChange {
9942 conference_id: 42.into(),
9943 participant,
9944 };
9945 let routing = ParticipantChangeRouting {
9946 application_id: 7.into(),
9947 line_instance: 1,
9948 transaction_id: 9.into(),
9949 sequence_flag: 1,
9950 display_priority: 2,
9951 application_instance_id: 3.into(),
9952 routing: 4,
9953 };
9954 let envelope = change.to_user_data_v1(routing).unwrap();
9955 assert_eq!(envelope.data.len(), 108);
9956 assert_eq!(
9957 ConferenceParticipantChange::from_user_data_v1(&envelope).unwrap(),
9958 change
9959 );
9960
9961 let mut mismatched = envelope;
9962 mismatched.conference_id += 1;
9963 assert!(matches!(
9964 ConferenceParticipantChange::from_user_data_v1(&mismatched),
9965 Err(CodecError::InvalidValue {
9966 field: "participant change conference ID",
9967 ..
9968 })
9969 ));
9970 }
9971
9972 #[test]
9973 fn participant_messages_enforce_text_and_audit_bounds() {
9974 let oversized = ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
9975 result: AuditParticipantResult::Ok,
9976 last: 1,
9977 conference_id: 42.into(),
9978 number_of_entries: 1,
9979 participant_entries: vec![0; 257],
9980 });
9981 assert!(matches!(
9982 oversized.encode(ProtocolVersion::V22),
9983 Err(CodecError::CountTooLarge {
9984 field: "participant audit data",
9985 count: 257,
9986 maximum: 256,
9987 ..
9988 })
9989 ));
9990
9991 let mut oversized_payload = vec![0; 16 + 257];
9992 oversized_payload[8..12].copy_from_slice(&42_u32.to_le_bytes());
9993 assert!(matches!(
9994 ControlMessage::decode(
9995 Frame::new(22, wire_id::AUDIT_PARTICIPANT_RES, oversized_payload),
9996 ProtocolVersion::V22,
9997 ),
9998 Err(CodecError::CountTooLarge {
9999 field: "participant audit data",
10000 count: 257,
10001 maximum: 256,
10002 ..
10003 })
10004 ));
10005
10006 let long_name = ControlMessage::AddParticipantRequest(AddParticipantRequest {
10007 conference_id: 42.into(),
10008 participant: ConferenceParticipant {
10009 call_reference: 100.into(),
10010 presentation_restrictions: PartyInformationRestrictions::empty(),
10011 name: "x".repeat(40),
10012 number: "1001".into(),
10013 conference_name: "Main Stage".into(),
10014 },
10015 });
10016 assert!(matches!(
10017 long_name.encode(ProtocolVersion::V22),
10018 Err(CodecError::TextTooLong {
10019 field: "participant name",
10020 actual: 40,
10021 maximum: 39,
10022 ..
10023 })
10024 ));
10025 }
10026
10027 #[test]
10028 fn multicast_media_layouts_cover_legacy_and_extended_addresses() {
10029 let acknowledgement = ClientMessage::MulticastMediaReceptionAck {
10030 status: MediaStatus::Ok,
10031 passthrough_party_id: 9.into(),
10032 call_reference: 7.into(),
10033 };
10034 let frame = FrameDecoder::new()
10035 .push(&acknowledgement.encode(ProtocolVersion::V3).unwrap())
10036 .unwrap()
10037 .remove(0);
10038 assert_eq!(frame.message_id, wire_id::MULTICAST_MEDIA_RECEPTION_ACK);
10039 assert_eq!(frame.payload.len(), 12);
10040 assert_eq!(ClientMessage::decode(frame).unwrap(), acknowledgement);
10041
10042 let reception_v3 = ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
10043 conference_id: 42.into(),
10044 passthrough_party_id: 9.into(),
10045 call_reference: 7.into(),
10046 address: "239.1.2.3".parse().unwrap(),
10047 port: 16_000,
10048 packet_millis: 20,
10049 codec: Codec::Pcmu,
10050 echo_cancellation: EchoCancellation::On,
10051 g723_bitrate: G723BitRate::Rate6_3,
10052 });
10053 let transmission_v3 =
10054 ServerMessage::StartMulticastMediaTransmission(MulticastMediaTransmission {
10055 conference_id: 42.into(),
10056 passthrough_party_id: 9.into(),
10057 call_reference: 7.into(),
10058 address: "239.1.2.3".parse().unwrap(),
10059 port: 16_002,
10060 packet_millis: 20,
10061 codec: Codec::Pcmu,
10062 precedence: 5,
10063 silence_suppression: 1,
10064 max_frames_per_packet: 2,
10065 g723_bitrate: G723BitRate::Rate5_3,
10066 });
10067 for (message, expected_id, expected_payload_len) in [
10068 (reception_v3, wire_id::START_MULTICAST_MEDIA_RECEPTION, 36),
10069 (
10070 transmission_v3,
10071 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
10072 44,
10073 ),
10074 ] {
10075 let frame = FrameDecoder::new()
10076 .push(&message.encode(ProtocolVersion::V3).unwrap())
10077 .unwrap()
10078 .remove(0);
10079 assert_eq!(frame.message_id, expected_id);
10080 assert_eq!(frame.payload.len(), expected_payload_len);
10081 assert_eq!(
10082 ServerMessage::decode(frame, ProtocolVersion::V3).unwrap(),
10083 message
10084 );
10085 }
10086
10087 let reception_v17 = ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
10088 conference_id: 43.into(),
10089 passthrough_party_id: 10.into(),
10090 call_reference: 8.into(),
10091 address: "ff3e::1234".parse().unwrap(),
10092 port: 17_000,
10093 packet_millis: 30,
10094 codec: Codec::Pcma,
10095 echo_cancellation: EchoCancellation::Unknown(7),
10096 g723_bitrate: G723BitRate::Unknown(9),
10097 });
10098 let transmission_v17 =
10099 ServerMessage::StartMulticastMediaTransmission(MulticastMediaTransmission {
10100 conference_id: 43.into(),
10101 passthrough_party_id: 10.into(),
10102 call_reference: 8.into(),
10103 address: "ff3e::1234".parse().unwrap(),
10104 port: 17_002,
10105 packet_millis: 30,
10106 codec: Codec::Pcma,
10107 precedence: 6,
10108 silence_suppression: 2,
10109 max_frames_per_packet: 3,
10110 g723_bitrate: G723BitRate::Unknown(9),
10111 });
10112 for (message, expected_id, expected_payload_len) in [
10113 (reception_v17, wire_id::START_MULTICAST_MEDIA_RECEPTION, 52),
10114 (
10115 transmission_v17,
10116 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
10117 60,
10118 ),
10119 ] {
10120 let frame = FrameDecoder::new()
10121 .push(&message.encode(ProtocolVersion::V17).unwrap())
10122 .unwrap()
10123 .remove(0);
10124 assert_eq!(frame.message_id, expected_id);
10125 assert_eq!(frame.payload.len(), expected_payload_len);
10126 assert_eq!(
10127 ServerMessage::decode(frame, ProtocolVersion::V17).unwrap(),
10128 message
10129 );
10130 }
10131
10132 for (message, expected_id) in [
10133 (
10134 ServerMessage::StopMulticastMediaReception {
10135 conference_id: 42.into(),
10136 passthrough_party_id: 9.into(),
10137 call_reference: 100.into(),
10138 },
10139 wire_id::STOP_MULTICAST_MEDIA_RECEPTION,
10140 ),
10141 (
10142 ServerMessage::StopMulticastMediaTransmission {
10143 conference_id: 42.into(),
10144 passthrough_party_id: 9.into(),
10145 call_reference: 100.into(),
10146 },
10147 wire_id::STOP_MULTICAST_MEDIA_TRANSMISSION,
10148 ),
10149 ] {
10150 let frame = FrameDecoder::new()
10151 .push(&message.encode(ProtocolVersion::V22).unwrap())
10152 .unwrap()
10153 .remove(0);
10154 assert_eq!(frame.message_id, expected_id);
10155 assert_eq!(frame.payload.len(), 12);
10156 assert_eq!(
10157 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10158 message
10159 );
10160 }
10161 }
10162
10163 #[test]
10164 fn legacy_multicast_rejects_ipv6_and_invalid_ports() {
10165 let ipv6 = ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
10166 conference_id: 42.into(),
10167 passthrough_party_id: 9.into(),
10168 call_reference: 7.into(),
10169 address: "ff3e::1234".parse().unwrap(),
10170 port: 16_000,
10171 packet_millis: 20,
10172 codec: Codec::Pcmu,
10173 echo_cancellation: EchoCancellation::On,
10174 g723_bitrate: G723BitRate::Rate6_3,
10175 });
10176 assert!(matches!(
10177 ipv6.encode(ProtocolVersion::V15),
10178 Err(CodecError::InvalidValue {
10179 field: "IP address family for pre-v17 protocol",
10180 ..
10181 })
10182 ));
10183
10184 let mut payload = vec![0; 52];
10185 payload[8..12].copy_from_slice(&1_u32.to_le_bytes());
10186 payload[28..32].copy_from_slice(&70_000_u32.to_le_bytes());
10187 assert!(matches!(
10188 ServerMessage::decode(
10189 Frame::new(17, wire_id::START_MULTICAST_MEDIA_RECEPTION, payload),
10190 ProtocolVersion::V17,
10191 ),
10192 Err(CodecError::InvalidValue {
10193 field: "multicast port",
10194 value: 70_000,
10195 ..
10196 })
10197 ));
10198 }
10199
10200 #[test]
10201 fn qos_control_messages_use_field_typed_service_layouts() {
10202 let flow = QosFlow {
10203 conference_id: 42.into(),
10204 call_reference: 7.into(),
10205 passthrough_party_id: 9.into(),
10206 address: "192.0.2.20".parse().unwrap(),
10207 port: 16_000,
10208 };
10209 let traffic = QosTrafficSpecification {
10210 codec: Codec::Pcmu,
10211 average_bit_rate: 64_000,
10212 burst_size: 1_200,
10213 peak_rate: 128_000,
10214 };
10215 let application = QosApplicationIdentifier {
10216 vendor_id: "Cisco".into(),
10217 version: "1".into(),
10218 application_name: "SCCP audio".into(),
10219 sub_application_id: "primary".into(),
10220 };
10221 let messages = [
10222 ControlMessage::QosReservationNotify {
10223 flow,
10224 direction: QosDirection::Send,
10225 },
10226 ControlMessage::QosErrorNotify {
10227 flow,
10228 direction: QosDirection::Send,
10229 error_code: QosErrorCode::ListenFailed,
10230 failure_node: "198.51.100.9".parse().unwrap(),
10231 rsvp_error_code: RsvpErrorCode::NoSenderInformation,
10232 rsvp_error_subcode: 5,
10233 rsvp_error_flags: 6,
10234 },
10235 ControlMessage::QosListen {
10236 flow,
10237 reservation_style: QosReservationStyle::SharedExplicit,
10238 maximum_retries: 3,
10239 retry_timer: 4,
10240 confirmation_required: true,
10241 preemption_priority: 5,
10242 defending_priority: 6,
10243 traffic,
10244 application: application.clone(),
10245 },
10246 ControlMessage::QosPath {
10247 flow,
10248 reservation_style: QosReservationStyle::SharedExplicit,
10249 maximum_retries: 3,
10250 retry_timer: 4,
10251 preemption_priority: 5,
10252 defending_priority: 6,
10253 traffic,
10254 application: application.clone(),
10255 },
10256 ControlMessage::QosTeardown {
10257 flow,
10258 direction: QosDirection::Send,
10259 },
10260 ControlMessage::UpdateDscp { flow, dscp: 46 },
10261 ControlMessage::QosModify {
10262 flow,
10263 direction: QosDirection::Send,
10264 traffic,
10265 application,
10266 },
10267 ];
10268 for (message, expected_size) in messages.into_iter().zip([24, 44, 172, 168, 24, 24, 152]) {
10269 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10270 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10271 assert_eq!(frame.payload.len(), expected_size);
10272 assert_eq!(
10273 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10274 message
10275 );
10276 }
10277 }
10278
10279 #[test]
10280 fn fixed_layout_alignment_bytes_must_be_zero() {
10281 let register_ack = WireRegisterAck {
10282 keepalive_seconds: 30,
10283 date_template: *b"D/M/Y\0",
10284 alignment: [1, 0],
10285 secondary_keepalive_seconds: 30,
10286 protocol_features: [22, 0, 0, 0],
10287 };
10288 assert!(
10289 ServerMessage::decode(
10290 Frame::new(
10291 ProtocolVersion::V22.wire(),
10292 wire_id::REGISTER_ACK,
10293 encode(wire_id::REGISTER_ACK, ®ister_ack).unwrap(),
10294 ),
10295 ProtocolVersion::V22,
10296 )
10297 .is_err()
10298 );
10299
10300 let notification = WireMessageWaitingNotification {
10301 target_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "target", "1001").unwrap(),
10302 control_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "control", "5000")
10303 .unwrap(),
10304 alignment: [1, 0],
10305 messages_waiting: 1,
10306 total_voicemail_new: 0,
10307 total_voicemail_old: 0,
10308 priority_voicemail_new: 0,
10309 priority_voicemail_old: 0,
10310 total_fax_new: 0,
10311 total_fax_old: 0,
10312 priority_fax_new: 0,
10313 priority_fax_old: 0,
10314 };
10315 let notification = encode(wire_id::MWI_NOTIFICATION, ¬ification).unwrap();
10316 assert_eq!(notification.len(), 88);
10317 assert!(
10318 ControlMessage::decode(
10319 Frame::new(
10320 ProtocolVersion::V22.wire(),
10321 wire_id::MWI_NOTIFICATION,
10322 notification,
10323 ),
10324 ProtocolVersion::V22,
10325 )
10326 .is_err()
10327 );
10328
10329 let response = WireMessageWaitingResponse {
10330 target_number: WireFixedText::new(wire_id::MWI_RESPONSE, "target", "1001").unwrap(),
10331 alignment: [0, 1, 0],
10332 result: MessageWaitingResult::Ok.wire_value(),
10333 };
10334 let response = encode(wire_id::MWI_RESPONSE, &response).unwrap();
10335 assert_eq!(response.len(), 32);
10336 assert!(
10337 ControlMessage::decode(
10338 Frame::new(ProtocolVersion::V22.wire(), wire_id::MWI_RESPONSE, response,),
10339 ProtocolVersion::V22,
10340 )
10341 .is_err()
10342 );
10343
10344 let connection_statistics = WireConnectionStatisticsV19 {
10345 directory_number: WireAlignedText {
10346 value: WireFixedText::new(
10347 wire_id::CONNECTION_STATISTICS_RES,
10348 "directory number",
10349 "2002",
10350 )
10351 .unwrap(),
10352 alignment: [1, 0, 0],
10353 },
10354 call_reference: 42,
10355 processing: StatisticsProcessing::Clear.wire_value(),
10356 statistics: WireConnectionStatisticsTail {
10357 counters: WireConnectionStatisticsCounters {
10358 packets_sent: 0,
10359 octets_sent: 0,
10360 packets_received: 0,
10361 octets_received: 0,
10362 packets_lost: 0,
10363 jitter_millis: 0,
10364 latency_millis: 0,
10365 },
10366 quality_size: 0,
10367 },
10368 quality: Vec::new(),
10369 };
10370 assert!(
10371 ClientMessage::decode_with_version(
10372 Frame::new(
10373 ProtocolVersion::V20.wire(),
10374 wire_id::CONNECTION_STATISTICS_RES,
10375 encode(wire_id::CONNECTION_STATISTICS_RES, &connection_statistics).unwrap(),
10376 ),
10377 ProtocolVersion::V20,
10378 )
10379 .is_err()
10380 );
10381
10382 let mut enbloc = encode(
10383 wire_id::ENBLOC_CALL,
10384 &WireEnblocWithLine::<25, 0> {
10385 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
10386 .unwrap(),
10387 line_instance: 2,
10388 },
10389 )
10390 .unwrap();
10391 enbloc.extend_from_slice(&[1, 0, 0]);
10392 assert!(
10393 ClientMessage::decode_with_version(
10394 Frame::new(ProtocolVersion::V19.wire(), wire_id::ENBLOC_CALL, enbloc),
10395 ProtocolVersion::V19,
10396 )
10397 .is_err()
10398 );
10399
10400 let mut off_hook = FrameDecoder::new()
10401 .push(
10402 &ClientMessage::OffHookWithCallingParty {
10403 calling_party_number: "2001".into(),
10404 voice_mailbox: "5000".into(),
10405 line_instance: 2,
10406 }
10407 .encode(ProtocolVersion::V19)
10408 .unwrap(),
10409 )
10410 .unwrap()
10411 .remove(0);
10412 off_hook.payload[50] = 1;
10413 assert!(ClientMessage::decode_with_version(off_hook, ProtocolVersion::V19).is_err());
10414
10415 let mut dialed = FrameDecoder::new()
10416 .push(
10417 &ServerMessage::DialedNumber {
10418 number: "2001".into(),
10419 line_instance: 2,
10420 call_reference: 42,
10421 }
10422 .encode(ProtocolVersion::V19)
10423 .unwrap(),
10424 )
10425 .unwrap()
10426 .remove(0);
10427 dialed.payload[25] = 1;
10428 assert!(ServerMessage::decode(dialed, ProtocolVersion::V19).is_err());
10429
10430 let mut forwarding = FrameDecoder::new()
10431 .push(
10432 &ServerMessage::ForwardStatus {
10433 line_instance: 2,
10434 forward_all: Some("2001".into()),
10435 forward_busy: None,
10436 forward_no_answer: None,
10437 }
10438 .encode(ProtocolVersion::V19)
10439 .unwrap(),
10440 )
10441 .unwrap()
10442 .remove(0);
10443 forwarding.payload[37] = 1;
10444 assert!(ServerMessage::decode(forwarding, ProtocolVersion::V19).is_err());
10445 }
10446
10447 #[test]
10448 fn boolean_control_words_reject_non_boolean_values() {
10449 let recording = encode(
10450 wire_id::RECORDING_STATUS,
10451 &WireRecordingStatus {
10452 call_reference: 7,
10453 active: 2,
10454 },
10455 )
10456 .unwrap();
10457 assert!(matches!(
10458 ServerMessage::decode(
10459 Frame::new(
10460 ProtocolVersion::V22.wire(),
10461 wire_id::RECORDING_STATUS,
10462 recording
10463 ),
10464 ProtocolVersion::V22,
10465 ),
10466 Err(CodecError::InvalidValue {
10467 field: "recording active",
10468 value: 2,
10469 ..
10470 })
10471 ));
10472
10473 let notification = WireMessageWaitingNotification {
10474 target_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "target", "1001").unwrap(),
10475 control_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "control", "5000")
10476 .unwrap(),
10477 alignment: [0; 2],
10478 messages_waiting: 2,
10479 total_voicemail_new: 0,
10480 total_voicemail_old: 0,
10481 priority_voicemail_new: 0,
10482 priority_voicemail_old: 0,
10483 total_fax_new: 0,
10484 total_fax_old: 0,
10485 priority_fax_new: 0,
10486 priority_fax_old: 0,
10487 };
10488 let payload = encode(wire_id::MWI_NOTIFICATION, ¬ification).unwrap();
10489 assert!(matches!(
10490 ControlMessage::decode(
10491 Frame::new(
10492 ProtocolVersion::V22.wire(),
10493 wire_id::MWI_NOTIFICATION,
10494 payload
10495 ),
10496 ProtocolVersion::V22,
10497 ),
10498 Err(CodecError::InvalidValue {
10499 field: "messages waiting",
10500 value: 2,
10501 ..
10502 })
10503 ));
10504
10505 let flow = QosFlow {
10506 conference_id: 1.into(),
10507 call_reference: 2.into(),
10508 passthrough_party_id: 3.into(),
10509 address: "192.0.2.1".parse().unwrap(),
10510 port: 16_000,
10511 };
10512 let traffic = QosTrafficSpecification {
10513 codec: Codec::Pcmu,
10514 average_bit_rate: 64_000,
10515 burst_size: 1_200,
10516 peak_rate: 128_000,
10517 };
10518 let application = QosApplicationIdentifier {
10519 vendor_id: "Cisco".into(),
10520 version: "1".into(),
10521 application_name: "SCCP audio".into(),
10522 sub_application_id: "primary".into(),
10523 };
10524 let mut qos_listen = FrameDecoder::new()
10525 .push(
10526 &ControlMessage::QosListen {
10527 flow,
10528 reservation_style: QosReservationStyle::SharedExplicit,
10529 maximum_retries: 3,
10530 retry_timer: 4,
10531 confirmation_required: true,
10532 preemption_priority: 5,
10533 defending_priority: 6,
10534 traffic,
10535 application,
10536 }
10537 .encode(ProtocolVersion::V22)
10538 .unwrap(),
10539 )
10540 .unwrap()
10541 .remove(0);
10542 qos_listen.payload[32..36].copy_from_slice(&2_u32.to_le_bytes());
10543 assert!(matches!(
10544 ControlMessage::decode(qos_listen, ProtocolVersion::V22),
10545 Err(CodecError::InvalidValue {
10546 field: "QoS confirmation required",
10547 value: 2,
10548 ..
10549 })
10550 ));
10551
10552 assert!(matches!(
10553 ControlMessage::UpdateDscp { flow, dscp: 64 }.encode(ProtocolVersion::V22),
10554 Err(CodecError::InvalidValue {
10555 field: "DSCP",
10556 value: 64,
10557 ..
10558 })
10559 ));
10560
10561 let invalid_dscp = encode(
10562 wire_id::UPDATE_DSCP,
10563 &WireUpdateDscp {
10564 flow: qos_flow_to_wire(flow),
10565 dscp: 64,
10566 },
10567 )
10568 .unwrap();
10569 assert!(matches!(
10570 ControlMessage::decode(
10571 Frame::new(
10572 ProtocolVersion::V22.wire(),
10573 wire_id::UPDATE_DSCP,
10574 invalid_dscp
10575 ),
10576 ProtocolVersion::V22,
10577 ),
10578 Err(CodecError::InvalidValue {
10579 field: "DSCP",
10580 value: 64,
10581 ..
10582 })
10583 ));
10584 }
10585
10586 #[test]
10587 fn compact_multimedia_dtmf_and_addon_layouts_round_trip_exactly() {
10588 let open_ack = OpenMultimediaReceiveChannelAck {
10589 status: MediaStatus::Ok,
10590 endpoint: MediaEndpointAddress {
10591 address: "2001:db8::20".parse().unwrap(),
10592 port: 16_000,
10593 },
10594 passthrough_party_id: 9.into(),
10595 call_reference: 7.into(),
10596 };
10597 let start_ack = StartMultimediaTransmissionAck {
10598 conference_id: 42.into(),
10599 passthrough_party_id: 9.into(),
10600 call_reference: 7.into(),
10601 endpoint: MediaEndpointAddress {
10602 address: "2001:db8::20".parse().unwrap(),
10603 port: 16_000,
10604 },
10605 status: MediaStatus::Ok,
10606 };
10607 for (message, expected_len) in [
10608 (ClientMessage::OpenMultimediaReceiveChannelAck(open_ack), 48),
10609 (ClientMessage::StartMultimediaTransmissionAck(start_ack), 52),
10610 ] {
10611 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10612 assert_eq!(bytes.len(), expected_len);
10613 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10614 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
10615 }
10616
10617 let addon = ClientMessage::ExtensionDeviceCapabilities(ExtensionDeviceCapabilities {
10618 unknown_1: 1,
10619 unknown_2: 2,
10620 unknown_3: 3,
10621 description: "7914 sidecar".into(),
10622 });
10623 let bytes = addon.encode(ProtocolVersion::V22).unwrap();
10624 assert_eq!(bytes.len(), 176);
10625 assert_eq!(
10626 ClientMessage::decode(FrameDecoder::new().push(&bytes).unwrap().remove(0)).unwrap(),
10627 addon
10628 );
10629
10630 let dtmf = DtmfToneControl {
10631 tone: Tone::Dtmf5,
10632 conference_id: 42.into(),
10633 passthrough_party_id: 9,
10634 };
10635 for message in [
10636 ServerMessage::NotifyDtmfTone(dtmf),
10637 ServerMessage::SendDtmfTone(dtmf),
10638 ] {
10639 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10640 assert_eq!(bytes.len(), 24);
10641 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10642 assert_eq!(
10643 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10644 message
10645 );
10646 }
10647
10648 let lifecycle = MultimediaStreamControl {
10649 conference_id: 42.into(),
10650 passthrough_party_id: 9.into(),
10651 call_reference: 7.into(),
10652 port_handling_flag: 1,
10653 };
10654 let flow = VideoFlowControl {
10655 conference_id: 42.into(),
10656 passthrough_party_id: 9.into(),
10657 call_reference: 7.into(),
10658 maximum_bit_rate: 512_000,
10659 };
10660 for message in [
10661 ServerMessage::StopMultimediaTransmission(lifecycle),
10662 ServerMessage::CloseMultimediaReceiveChannel(lifecycle),
10663 ServerMessage::FlowControlCommand(flow),
10664 ServerMessage::FlowControlNotify(flow),
10665 ] {
10666 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10667 assert_eq!(bytes.len(), 28);
10668 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10669 assert_eq!(
10670 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10671 message
10672 );
10673 }
10674 let display = ServerMessage::VideoDisplayCommand {
10675 conference_id: 42.into(),
10676 call_reference: 7.into(),
10677 layout_id: 2,
10678 };
10679 let bytes = display.encode(ProtocolVersion::V22).unwrap();
10680 assert_eq!(bytes.len(), 24);
10681 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10682 assert_eq!(
10683 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10684 display
10685 );
10686
10687 let failure_detection = ServerMessage::StartMediaFailureDetection(MediaFailureDetection {
10688 conference_id: 42.into(),
10689 passthrough_party_id: 9,
10690 packet_millis: 20,
10691 codec: Codec::Pcmu,
10692 echo_cancellation: EchoCancellation::On,
10693 codec_qualifier: [1, 2, 3, 4],
10694 call_reference: 7.into(),
10695 });
10696 let bytes = failure_detection.encode(ProtocolVersion::V22).unwrap();
10697 assert_eq!(bytes.len(), 40);
10698 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10699 assert_eq!(
10700 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10701 failure_detection
10702 );
10703
10704 let dynamic = ServerMessage::ConfigStatus(ConfigurationStatus {
10705 device_name: "SEP001122334455".into(),
10706 station_user_id: 0xfeed,
10707 station_instance: 2,
10708 line_count: 6,
10709 speed_dial_count: 12,
10710 user_name: "festival".into(),
10711 server_name: "sccp.example.test".into(),
10712 });
10713 let bytes = dynamic.encode(ProtocolVersion::V22).unwrap();
10714 assert_eq!(bytes.len() % 4, 0);
10715 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10716 assert_eq!(
10717 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10718 dynamic
10719 );
10720 }
10721
10722 #[test]
10723 fn soft_key_template_keeps_cisco_event_positions() {
10724 let bytes = ServerMessage::SoftKeyTemplate {
10725 actions: SoftKeyProfile::default().template_actions(),
10726 }
10727 .encode(ProtocolVersion::V22)
10728 .unwrap();
10729 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10730 let payload: WireSoftKeyTemplate = decode(frame.message_id, &frame.payload).unwrap();
10731 assert_eq!(payload.count, 32);
10732 assert_eq!(payload.definitions[31].event, 32);
10733 assert_eq!(
10734 payload
10735 .definitions
10736 .iter()
10737 .map(|definition| definition.event)
10738 .collect::<Vec<_>>(),
10739 (1..=32).collect::<Vec<_>>()
10740 );
10741 }
10742
10743 #[test]
10744 fn line_only_button_template_preserves_the_fixed_wire_layout() {
10745 let message = ServerMessage::ButtonTemplate {
10746 offset: 0,
10747 total: 1,
10748 buttons: vec![ButtonTemplateEntry {
10749 instance: 1,
10750 button_type: ButtonType::Line,
10751 }],
10752 };
10753 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10754 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10755 let payload: WireButtonTemplate = decode(frame.message_id, &frame.payload).unwrap();
10756
10757 assert_eq!(payload.offset, 0);
10758 assert_eq!(payload.count, 1);
10759 assert_eq!(payload.total, 1);
10760 assert_eq!(
10761 payload.definitions[0],
10762 WireButtonDefinition {
10763 instance: 1,
10764 button_type: ButtonType::Line.wire_value() as u8,
10765 }
10766 );
10767 assert!(
10768 payload.definitions[1..]
10769 .iter()
10770 .all(|definition| *definition == WireButtonDefinition::default())
10771 );
10772 assert_eq!(
10773 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10774 message
10775 );
10776 }
10777
10778 #[test]
10779 fn mixed_button_template_round_trips_ordered_semantic_entries() {
10780 let message = ServerMessage::ButtonTemplate {
10781 offset: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10782 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 + 6,
10783 buttons: vec![
10784 ButtonTemplateEntry {
10785 instance: 1,
10786 button_type: ButtonType::Line,
10787 },
10788 ButtonTemplateEntry {
10789 instance: 2,
10790 button_type: ButtonType::SpeedDial,
10791 },
10792 ButtonTemplateEntry {
10793 instance: 3,
10794 button_type: ButtonType::DoNotDisturb,
10795 },
10796 ButtonTemplateEntry {
10797 instance: 4,
10798 button_type: ButtonType::ServiceUrl,
10799 },
10800 ButtonTemplateEntry {
10801 instance: 0,
10802 button_type: ButtonType::Unused,
10803 },
10804 ButtonTemplateEntry {
10805 instance: 5,
10806 button_type: ButtonType::BlfSpeedDial,
10807 },
10808 ],
10809 };
10810
10811 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10812 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10813 assert_eq!(
10814 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10815 message
10816 );
10817 }
10818
10819 #[test]
10820 fn button_template_rejects_unrepresentable_entries_and_counts() {
10821 let too_many = ServerMessage::ButtonTemplate {
10822 offset: 0,
10823 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 + 1,
10824 buttons: vec![
10825 ButtonTemplateEntry {
10826 instance: 1,
10827 button_type: ButtonType::Line,
10828 };
10829 BUTTON_TEMPLATE_ENTRIES_PER_CHUNK + 1
10830 ],
10831 };
10832 assert!(matches!(
10833 too_many.encode(ProtocolVersion::V22),
10834 Err(CodecError::CountTooLarge { .. })
10835 ));
10836
10837 let large_instance = ServerMessage::ButtonTemplate {
10838 offset: 0,
10839 total: 1,
10840 buttons: vec![ButtonTemplateEntry {
10841 instance: 256,
10842 button_type: ButtonType::Line,
10843 }],
10844 };
10845 assert!(matches!(
10846 large_instance.encode(ProtocolVersion::V22),
10847 Err(CodecError::InvalidValue {
10848 field: "button instance",
10849 ..
10850 })
10851 ));
10852
10853 let payload = WireButtonTemplate {
10854 offset: 0,
10855 count: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 + 1,
10856 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10857 definitions: [WireButtonDefinition::default(); BUTTON_TEMPLATE_ENTRIES_PER_CHUNK],
10858 };
10859 let frame = Frame::new(
10860 ProtocolVersion::V22.wire(),
10861 wire_id::BUTTON_TEMPLATE,
10862 encode(wire_id::BUTTON_TEMPLATE, &payload).unwrap(),
10863 );
10864 assert!(matches!(
10865 ServerMessage::decode(frame, ProtocolVersion::V22),
10866 Err(CodecError::CountTooLarge {
10867 field: "button definitions in message",
10868 ..
10869 })
10870 ));
10871
10872 let payload = WireButtonTemplate {
10873 offset: 1,
10874 count: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10875 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10876 definitions: [WireButtonDefinition::default(); BUTTON_TEMPLATE_ENTRIES_PER_CHUNK],
10877 };
10878 let frame = Frame::new(
10879 ProtocolVersion::V22.wire(),
10880 wire_id::BUTTON_TEMPLATE,
10881 encode(wire_id::BUTTON_TEMPLATE, &payload).unwrap(),
10882 );
10883 assert!(matches!(
10884 ServerMessage::decode(frame, ProtocolVersion::V22),
10885 Err(CodecError::InvalidValue {
10886 field: "button template range",
10887 ..
10888 })
10889 ));
10890 }
10891
10892 #[test]
10893 fn station_statuses_select_and_round_trip_dynamic_layouts() {
10894 let cases = [
10895 (
10896 ServerMessage::LineStatus {
10897 instance: 3,
10898 directory_number: "1003".into(),
10899 fully_qualified_display_name: "Desk 1003".into(),
10900 display_label: "A dynamic line label".into(),
10901 },
10902 ProtocolVersion::V8,
10903 wire_id::LINE_STAT,
10904 ),
10905 (
10906 ServerMessage::LineStatus {
10907 instance: 3,
10908 directory_number: "1003".into(),
10909 fully_qualified_display_name: "Desk 1003".into(),
10910 display_label: "A dynamic line label".into(),
10911 },
10912 ProtocolVersion::V9,
10913 wire_id::LINE_STAT_DYNAMIC,
10914 ),
10915 (
10916 ServerMessage::SpeedDialStatus {
10917 instance: 4,
10918 number: "2004".into(),
10919 display_name: "Warehouse".into(),
10920 },
10921 ProtocolVersion::V8,
10922 wire_id::SPEED_DIAL_STAT,
10923 ),
10924 (
10925 ServerMessage::SpeedDialStatus {
10926 instance: 4,
10927 number: "2004".into(),
10928 display_name: "Warehouse".into(),
10929 },
10930 ProtocolVersion::V9,
10931 wire_id::SPEED_DIAL_STAT_DYNAMIC,
10932 ),
10933 (
10934 ServerMessage::FeatureStatus {
10935 instance: 5,
10936 button_type: ButtonType::DoNotDisturb,
10937 label: "Do not disturb".into(),
10938 state: 0x0002_0101,
10939 },
10940 ProtocolVersion::V22,
10941 wire_id::FEATURE_STAT,
10942 ),
10943 (
10944 ServerMessage::ServiceUrlStatus {
10945 index: 6,
10946 url: "http://services.invalid/directory".into(),
10947 label: "Directory".into(),
10948 extension_text: String::new(),
10949 },
10950 ProtocolVersion::V8,
10951 wire_id::SERVICE_URL_STAT,
10952 ),
10953 (
10954 ServerMessage::ServiceUrlStatus {
10955 index: 6,
10956 url: "http://services.invalid/directory".into(),
10957 label: "Directory".into(),
10958 extension_text: String::new(),
10959 },
10960 ProtocolVersion::V9,
10961 wire_id::SERVICE_URL_STAT_DYNAMIC,
10962 ),
10963 ];
10964
10965 for (message, protocol, expected_id) in cases {
10966 let bytes = message.encode(protocol).unwrap();
10967 assert_eq!(bytes.len() % 4, 0, "message 0x{expected_id:04x}");
10968 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10969 assert_eq!(frame.message_id, expected_id);
10970 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
10971 }
10972
10973 let message = ServerMessage::FeatureStatus {
10974 instance: 5,
10975 button_type: ButtonType::DoNotDisturb,
10976 label: "Do not disturb".into(),
10977 state: 0x0002_0101,
10978 };
10979 let session =
10980 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES);
10981 let bytes = message.encode_for_session(session).unwrap();
10982 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10983 assert_eq!(frame.message_id, wire_id::FEATURE_STAT_DYNAMIC);
10984 assert_eq!(
10985 ServerMessage::decode(frame, ProtocolVersion::V8).unwrap(),
10986 message
10987 );
10988 }
10989
10990 #[test]
10991 fn line_status_keeps_display_identity_separate_from_button_label() {
10992 let message = ServerMessage::LineStatus {
10993 instance: 1,
10994 directory_number: "coral".into(),
10995 fully_qualified_display_name: "Coral's phone".into(),
10996 display_label: "ATP".into(),
10997 };
10998
10999 let fixed = message.encode(ProtocolVersion::V8).unwrap();
11000 let fixed = FrameDecoder::new().push(&fixed).unwrap().remove(0);
11001 let fixed: WireLineStatus = decode(fixed.message_id, &fixed.payload).unwrap();
11002 assert_eq!(fixed.directory_number.text().unwrap(), "coral");
11003 assert_eq!(fixed.display_name.text().unwrap(), "Coral's phone");
11004 assert_eq!(fixed.display_label.text().unwrap(), "ATP");
11005
11006 let dynamic = message.encode(ProtocolVersion::V9).unwrap();
11007 let dynamic = FrameDecoder::new().push(&dynamic).unwrap().remove(0);
11008 assert_eq!(
11009 decode_dynamic_texts(dynamic.message_id, &dynamic.payload, 8, 3).unwrap(),
11010 ["coral", "Coral's phone", "ATP"]
11011 );
11012 }
11013
11014 #[test]
11015 fn configuration_status_is_lossless_across_session_selected_layouts() {
11016 let message = ServerMessage::ConfigStatus(ConfigurationStatus {
11017 device_name: "SEP001122334455".into(),
11018 station_user_id: 17,
11019 station_instance: 2,
11020 line_count: 6,
11021 speed_dial_count: 12,
11022 user_name: "festival".into(),
11023 server_name: "sccp.example.test".into(),
11024 });
11025 for (session, expected_id) in [
11026 (
11027 StationSessionContext::from(ProtocolVersion::V8),
11028 wire_id::CONFIG_STAT,
11029 ),
11030 (
11031 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES),
11032 wire_id::CONFIG_STAT_DYNAMIC,
11033 ),
11034 (
11035 StationSessionContext::from(ProtocolVersion::V9),
11036 wire_id::CONFIG_STAT_DYNAMIC,
11037 ),
11038 ] {
11039 let bytes = message.encode_for_session(session).unwrap();
11040 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11041 assert_eq!(frame.message_id, expected_id);
11042 assert_eq!(
11043 ServerMessage::decode(frame, session.protocol).unwrap(),
11044 message
11045 );
11046 }
11047 }
11048
11049 #[test]
11050 fn speed_dial_status_uses_session_selection_and_variable_wire_layout() {
11051 let message = ServerMessage::SpeedDialStatus {
11052 instance: 4,
11053 number: "2004".into(),
11054 display_name: "Warehouse".into(),
11055 };
11056 for (session, expected_id) in [
11057 (
11058 StationSessionContext::from(ProtocolVersion::V8),
11059 wire_id::SPEED_DIAL_STAT,
11060 ),
11061 (
11062 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES),
11063 wire_id::SPEED_DIAL_STAT_DYNAMIC,
11064 ),
11065 (
11066 StationSessionContext::from(ProtocolVersion::V9),
11067 wire_id::SPEED_DIAL_STAT_DYNAMIC,
11068 ),
11069 ] {
11070 let bytes = message.encode_for_session(session).unwrap();
11071 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11072 assert_eq!(frame.message_id, expected_id);
11073 assert_eq!(
11074 ServerMessage::decode(frame, session.protocol).unwrap(),
11075 message
11076 );
11077 }
11078
11079 let bytes = ServerMessage::SpeedDialStatus {
11080 instance: 2,
11081 number: "2001".into(),
11082 display_name: "Reception".into(),
11083 }
11084 .encode(ProtocolVersion::V9)
11085 .unwrap();
11086 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11087 assert_eq!(frame.message_id, wire_id::SPEED_DIAL_STAT_DYNAMIC);
11088 assert_eq!(
11089 frame.payload,
11090 [
11091 0x02, 0x00, 0x00, 0x00, b'2', b'0', b'0', b'1', 0x00, b'R', b'e', b'c', b'e', b'p',
11092 b't', b'i', b'o', b'n', 0x00, 0x00,
11093 ]
11094 );
11095 }
11096
11097 #[test]
11098 fn dynamic_call_information_uses_the_versioned_string_count() {
11099 let message = ServerMessage::CallInfo {
11100 info: CallInfo {
11101 direction: crate::types::CallDirection::Inbound,
11102 calling_name: "Alice".into(),
11103 calling_number: "1001".into(),
11104 called_name: "Bob".into(),
11105 called_number: "2001".into(),
11106 original_called_name: "Carol".into(),
11107 original_called_number: "3001".into(),
11108 last_redirecting_name: "Dave".into(),
11109 last_redirecting_number: "4001".into(),
11110 original_redirect_reason: 2,
11111 last_redirect_reason: 4,
11112 party_restrictions: 0,
11113 },
11114 line_instance: 2,
11115 call_reference: 42,
11116 };
11117
11118 for (protocol, count) in [
11119 (ProtocolVersion::V15, 12),
11120 (ProtocolVersion::V16, 13),
11121 (ProtocolVersion::V18, 13),
11122 (ProtocolVersion::V19, 15),
11123 ] {
11124 let bytes = message.encode(protocol).unwrap();
11125 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11126 assert_eq!(frame.message_id, wire_id::CALL_INFO_DYNAMIC);
11127 assert_eq!(
11128 decode_dynamic_texts(frame.message_id, &frame.payload, 32, count)
11129 .unwrap()
11130 .len(),
11131 count
11132 );
11133 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
11134 }
11135 }
11136
11137 #[test]
11138 fn dynamic_service_status_adds_the_extension_field_from_version_nineteen() {
11139 let unsupported = ServerMessage::ServiceUrlStatus {
11140 index: 3,
11141 url: "http://services.invalid/directory".into(),
11142 label: "Directory".into(),
11143 extension_text: "extension".into(),
11144 };
11145 assert!(matches!(
11146 unsupported.encode(ProtocolVersion::V18),
11147 Err(CodecError::InvalidValue {
11148 field: "service URL extension for this protocol version",
11149 ..
11150 })
11151 ));
11152
11153 let before = ServerMessage::ServiceUrlStatus {
11154 index: 3,
11155 url: "http://services.invalid/directory".into(),
11156 label: "Directory".into(),
11157 extension_text: String::new(),
11158 };
11159 let bytes = before.encode(ProtocolVersion::V18).unwrap();
11160 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11161 assert_eq!(
11162 decode_dynamic_texts(frame.message_id, &frame.payload, 4, 2).unwrap(),
11163 ["http://services.invalid/directory", "Directory"]
11164 );
11165 assert_eq!(
11166 ServerMessage::decode(frame, ProtocolVersion::V18).unwrap(),
11167 before
11168 );
11169
11170 let from = ServerMessage::ServiceUrlStatus {
11171 index: 3,
11172 url: "http://services.invalid/directory".into(),
11173 label: "Directory".into(),
11174 extension_text: "extension".into(),
11175 };
11176 let bytes = from.encode(ProtocolVersion::V19).unwrap();
11177 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11178 assert_eq!(
11179 decode_dynamic_texts(frame.message_id, &frame.payload, 4, 3).unwrap(),
11180 [
11181 "http://services.invalid/directory",
11182 "Directory",
11183 "extension"
11184 ]
11185 );
11186 assert_eq!(
11187 ServerMessage::decode(frame, ProtocolVersion::V19).unwrap(),
11188 from
11189 );
11190 }
11191
11192 #[test]
11193 fn dynamic_7961_line_status_has_cisco_word_padding() {
11194 let bytes = ServerMessage::LineStatus {
11195 instance: 1,
11196 directory_number: "1006".into(),
11197 fully_qualified_display_name: "1006".into(),
11198 display_label: "1006".into(),
11199 }
11200 .encode(ProtocolVersion::V22)
11201 .unwrap();
11202 assert_eq!(bytes.len(), 36);
11203 assert_eq!(&bytes[..4], &28_u32.to_le_bytes());
11204 assert_eq!(
11205 &bytes[12..],
11206 b"\x01\0\0\0\x0f\0\0\x001006\x001006\x001006\0\0"
11207 );
11208 }
11209
11210 #[test]
11211 fn dynamic_station_decoders_reject_missing_or_nonzero_word_padding() {
11212 let bytes = ServerMessage::LineStatus {
11213 instance: 1,
11214 directory_number: "1006".into(),
11215 fully_qualified_display_name: "1006".into(),
11216 display_label: "1006".into(),
11217 }
11218 .encode(ProtocolVersion::V22)
11219 .unwrap();
11220 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11221
11222 let mut missing_padding = frame.clone();
11223 missing_padding.payload.pop();
11224 assert!(matches!(
11225 ServerMessage::decode(missing_padding, ProtocolVersion::V22),
11226 Err(CodecError::InvalidAlignment { actual: 23, .. })
11227 ));
11228
11229 let mut nonzero_padding = frame.clone();
11230 *nonzero_padding.payload.last_mut().unwrap() = 0x7f;
11231 assert!(matches!(
11232 ServerMessage::decode(nonzero_padding, ProtocolVersion::V22),
11233 Err(CodecError::TrailingBytes { count: 1, .. })
11234 ));
11235
11236 let mut extension = frame;
11237 extension.payload.extend_from_slice(&[0; 4]);
11238 assert!(matches!(
11239 ServerMessage::decode(extension, ProtocolVersion::V22),
11240 Err(CodecError::TrailingBytes { count: 5, .. })
11241 ));
11242 }
11243
11244 #[test]
11245 fn dynamic_display_decoders_validate_the_same_padding_contract() {
11246 let bytes = ServerMessage::DisplayNotify {
11247 timeout_seconds: 4,
11248 text: "status".into(),
11249 }
11250 .encode(ProtocolVersion::V22)
11251 .unwrap();
11252 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11253 assert_eq!(frame.payload.len() % 4, 0);
11254
11255 let mut bad = frame;
11256 bad.payload.extend_from_slice(&[0, 0, 0, 1]);
11257 assert!(matches!(
11258 ServerMessage::decode(bad, ProtocolVersion::V22),
11259 Err(CodecError::TrailingBytes { .. })
11260 ));
11261 }
11262
11263 #[test]
11264 fn legacy_station_labels_use_the_configured_single_byte_code_page() {
11265 let message = ServerMessage::LineStatus {
11266 instance: 1,
11267 directory_number: "1001".into(),
11268 fully_qualified_display_name: "Desk 1001".into(),
11269 display_label: "Räksmörgås".into(),
11270 };
11271 let latin1 = message
11272 .encode_for_legacy_station(ProtocolVersion::V3, LegacyCodePage::Iso8859_1)
11273 .unwrap();
11274 let latin1 = FrameDecoder::new().push(&latin1).unwrap().remove(0);
11275 let expected = b"R\xe4ksm\xf6rg\xe5s";
11276 assert!(
11277 latin1
11278 .payload
11279 .windows(expected.len())
11280 .any(|bytes| bytes == expected)
11281 );
11282
11283 let ascii = message
11284 .encode_for_legacy_station(ProtocolVersion::V3, LegacyCodePage::Ascii)
11285 .unwrap();
11286 let ascii = FrameDecoder::new().push(&ascii).unwrap().remove(0);
11287 assert!(
11288 ascii
11289 .payload
11290 .windows(10)
11291 .any(|bytes| bytes == b"R?ksm?rg?s")
11292 );
11293
11294 let utf8 = message.encode(ProtocolVersion::V3).unwrap();
11295 let utf8 = FrameDecoder::new().push(&utf8).unwrap().remove(0);
11296 assert!(
11297 utf8.payload
11298 .windows(13)
11299 .any(|bytes| bytes == "Räksmörgås".as_bytes())
11300 );
11301 }
11302
11303 #[test]
11304 fn dynamic_station_statuses_support_extended_labels_and_require_terminators() {
11305 let label = "A label that is intentionally longer than the static forty-byte field";
11306 for message in [
11307 ServerMessage::LineStatus {
11308 instance: 1,
11309 directory_number: "1001".into(),
11310 fully_qualified_display_name: "Desk 1001".into(),
11311 display_label: label.into(),
11312 },
11313 ServerMessage::ServiceUrlStatus {
11314 index: 3,
11315 url: "http://services.invalid/directory".into(),
11316 label: label.into(),
11317 extension_text: String::new(),
11318 },
11319 ] {
11320 let bytes = message.encode(ProtocolVersion::V17).unwrap();
11321 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11322 assert_eq!(
11323 ServerMessage::decode(frame, ProtocolVersion::V17).unwrap(),
11324 message
11325 );
11326 }
11327
11328 let feature = ServerMessage::FeatureStatus {
11329 instance: 2,
11330 button_type: ButtonType::DoNotDisturb,
11331 label: label.into(),
11332 state: 1,
11333 };
11334 let session =
11335 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES);
11336 let bytes = feature.encode_for_session(session).unwrap();
11337 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11338 assert_eq!(frame.message_id, wire_id::FEATURE_STAT_DYNAMIC);
11339 assert_eq!(
11340 ServerMessage::decode(frame, ProtocolVersion::V8).unwrap(),
11341 feature
11342 );
11343
11344 let unterminated_service = Frame::new(
11345 ProtocolVersion::V17.wire(),
11346 wire_id::SERVICE_URL_STAT_DYNAMIC,
11347 [3_u32.to_le_bytes().as_slice(), b"x\0YZ"].concat(),
11348 );
11349 assert!(matches!(
11350 ServerMessage::decode(unterminated_service, ProtocolVersion::V17),
11351 Err(CodecError::Truncated { .. })
11352 ));
11353 }
11354
11355 #[test]
11356 fn call_info_layouts_preserve_redirecting_and_presentation_fields() {
11357 let info = CallInfo {
11358 direction: crate::types::CallDirection::Inbound,
11359 calling_name: "Festival Caller".into(),
11360 calling_number: "1001".into(),
11361 called_name: "Festival Phone".into(),
11362 called_number: "1006".into(),
11363 original_called_name: "Reception".into(),
11364 original_called_number: "1000".into(),
11365 last_redirecting_name: "Front Desk".into(),
11366 last_redirecting_number: "1002".into(),
11367 original_redirect_reason: 4,
11368 last_redirect_reason: 2,
11369 party_restrictions: 0xf,
11370 };
11371 for (protocol, expected_id) in [
11372 (ProtocolVersion::V3, wire_id::CALL_INFO),
11373 (ProtocolVersion::V8, wire_id::CALL_INFO),
11374 (ProtocolVersion::V16, wire_id::CALL_INFO_DYNAMIC),
11375 (ProtocolVersion::V22, wire_id::CALL_INFO_DYNAMIC),
11376 ] {
11377 let message = ServerMessage::CallInfo {
11378 info: info.clone(),
11379 line_instance: 1,
11380 call_reference: 42,
11381 };
11382 let bytes = message.encode(protocol).unwrap();
11383 assert_eq!(bytes.len() % 4, 0, "message 0x{expected_id:04x}");
11384 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11385 assert_eq!(frame.message_id, expected_id);
11386 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
11387 }
11388
11389 let bytes = ServerMessage::DisplayPrompt {
11390 timeout_seconds: 0,
11391 text: "From Festival Caller (1001)".into(),
11392 line_instance: 1,
11393 call_reference: 42,
11394 }
11395 .encode(ProtocolVersion::V22)
11396 .unwrap();
11397 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11398 assert_eq!(frame.message_id, wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS);
11399 assert_eq!(
11400 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11401 ServerMessage::DisplayPrompt {
11402 timeout_seconds: 0,
11403 text: "From Festival Caller (1001)".into(),
11404 line_instance: 1,
11405 call_reference: 42,
11406 }
11407 );
11408 }
11409
11410 #[test]
11411 fn notification_frames_select_static_or_dynamic_layout_and_keep_priority_six() {
11412 for (protocol, expected_id) in [
11413 (ProtocolVersion::V3, wire_id::DISPLAY_PRIORITY_NOTIFY),
11414 (
11415 ProtocolVersion::V22,
11416 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY,
11417 ),
11418 ] {
11419 let message = ServerMessage::DisplayPriorityNotify {
11420 timeout_seconds: 10,
11421 priority: NotificationPriority::Timed,
11422 text: "Status line".into(),
11423 };
11424 let bytes = message.encode(protocol).unwrap();
11425 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11426 assert_eq!(frame.message_id, expected_id);
11427 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
11428 }
11429
11430 let message = ServerMessage::DisplayNotify {
11431 timeout_seconds: 3,
11432 text: "Dynamic notification text longer than thirty-one bytes".into(),
11433 };
11434 let bytes = message.encode(ProtocolVersion::V22).unwrap();
11435 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11436 assert_eq!(frame.message_id, wire_id::DISPLAY_DYNAMIC_NOTIFY);
11437 assert_eq!(
11438 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11439 message
11440 );
11441 }
11442
11443 #[test]
11444 fn call_state_uses_cisco_visibility_then_precedence_layout() {
11445 let bytes = ServerMessage::CallState {
11446 state: CallState::RingIn,
11447 line_instance: 1,
11448 call_reference: 42,
11449 }
11450 .encode(ProtocolVersion::V22)
11451 .unwrap();
11452 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11453 let words = (0..frame.payload.len() / 4)
11454 .map(|index| {
11455 let offset = index * 4;
11456 u32::from_le_bytes([
11457 frame.payload[offset],
11458 frame.payload[offset + 1],
11459 frame.payload[offset + 2],
11460 frame.payload[offset + 3],
11461 ])
11462 })
11463 .collect::<Vec<_>>();
11464
11465 assert_eq!(
11466 words,
11467 vec![CallState::RingIn.wire_value(), 1, 42, 0, 2, 0],
11468 "CallState is state, line, call, visibility, priority, domain"
11469 );
11470
11471 for (state, expected) in [
11472 (CallState::OffHook, 3),
11473 (CallState::Proceed, 3),
11474 (CallState::Connected, 3),
11475 (CallState::RingOut, 4),
11476 ] {
11477 let bytes = ServerMessage::CallState {
11478 state,
11479 line_instance: 1,
11480 call_reference: 42,
11481 }
11482 .encode(ProtocolVersion::V22)
11483 .unwrap();
11484 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11485 assert_eq!(
11486 u32::from_le_bytes(frame.payload[16..20].try_into().unwrap()),
11487 expected,
11488 "wrong precedence for {state:?}"
11489 );
11490 }
11491 }
11492
11493 #[test]
11494 fn soft_key_sets_and_masks_only_advertise_implemented_actions() {
11495 let profile = SoftKeyProfile::default();
11496 let bytes = ServerMessage::SoftKeySet {
11497 profile: profile.clone(),
11498 }
11499 .encode(ProtocolVersion::V22)
11500 .unwrap();
11501 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11502 let payload: WireSoftKeySet = decode(frame.message_id, &frame.payload).unwrap();
11503
11504 assert_eq!(
11505 &payload.sets[KeyMode::RingIn.wire_value() as usize].template_indexes[..2],
11506 &[
11507 SoftKey::Answer.wire_value() as u8,
11508 SoftKey::EndCall.wire_value() as u8
11509 ]
11510 );
11511 assert_eq!(profile.valid_mask(KeyMode::RingIn), 0b11);
11512 assert_eq!(profile.valid_mask(KeyMode::Connected), 0b111);
11513 assert_eq!(profile.valid_mask(KeyMode::Empty), 0);
11514 }
11515
11516 #[test]
11517 fn configured_soft_key_set_round_trips_order_and_empty_modes() {
11518 let profile = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
11519 let actions = match mode {
11520 KeyMode::OnHook => vec![SoftKey::Redial, SoftKey::NewCall],
11521 KeyMode::Connected => vec![SoftKey::EndCall, SoftKey::Hold],
11522 _ => Vec::new(),
11523 };
11524 (mode, actions)
11525 }))
11526 .unwrap();
11527 let message = ServerMessage::SoftKeySet {
11528 profile: profile.clone(),
11529 };
11530 let bytes = message.encode(ProtocolVersion::V22).unwrap();
11531 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11532 let payload: WireSoftKeySet = decode(frame.message_id, &frame.payload).unwrap();
11533
11534 assert_eq!(
11535 &payload.sets[KeyMode::OnHook.wire_value() as usize].template_indexes[..3],
11536 &[
11537 SoftKey::Redial.wire_value() as u8,
11538 SoftKey::NewCall.wire_value() as u8,
11539 0,
11540 ]
11541 );
11542 assert_eq!(
11543 &payload.sets[KeyMode::Connected.wire_value() as usize].template_indexes[..3],
11544 &[
11545 SoftKey::EndCall.wire_value() as u8,
11546 SoftKey::Hold.wire_value() as u8,
11547 0,
11548 ]
11549 );
11550 assert_eq!(
11551 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11552 message
11553 );
11554 assert_eq!(profile.valid_mask(KeyMode::OnHook), 0b11);
11555 assert_eq!(profile.valid_mask(KeyMode::RingIn), 0);
11556
11557 let template = ServerMessage::SoftKeyTemplate {
11558 actions: profile.template_actions(),
11559 };
11560 let bytes = template.encode(ProtocolVersion::V22).unwrap();
11561 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11562 let payload: WireSoftKeyTemplate = decode(frame.message_id, &frame.payload).unwrap();
11563 assert_eq!(payload.definitions[0].event, SoftKey::Redial.wire_value());
11564 assert_eq!(payload.definitions[2].event, SoftKey::Hold.wire_value());
11565 assert_eq!(payload.definitions[3].event, 0);
11566 assert_eq!(
11567 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11568 template
11569 );
11570 }
11571
11572 #[test]
11573 fn nominally_empty_requests_accept_bounded_extensions() {
11574 for message_id in [
11575 wire_id::CONFIG_STAT_REQ,
11576 wire_id::TIME_DATE_REQ,
11577 wire_id::VERSION_REQ,
11578 wire_id::SERVER_REQ,
11579 wire_id::SOFT_KEY_SET_REQ,
11580 wire_id::SOFT_KEY_TEMPLATE_REQ,
11581 ] {
11582 ClientMessage::decode(Frame::new(22, message_id, 34_u32.to_le_bytes().to_vec()))
11583 .unwrap();
11584 }
11585 }
11586
11587 #[test]
11588 fn dtmf_payload_messages_use_their_structural_word_layouts() {
11589 let identity = DtmfPayloadIdentity {
11590 payload_type: 101,
11591 conference_id: 0x1122_3344,
11592 passthrough_party_id: 0x5566_7788,
11593 };
11594 let request = DtmfPayloadRequest {
11595 payload_type: identity.payload_type,
11596 conference_id: identity.conference_id,
11597 passthrough_party_id: identity.passthrough_party_id,
11598 dtmf_type: 2,
11599 };
11600 let identity_payload = [
11601 identity.payload_type.to_le_bytes(),
11602 identity.conference_id.to_le_bytes(),
11603 identity.passthrough_party_id.to_le_bytes(),
11604 ]
11605 .concat();
11606 let request_payload = [
11607 request.payload_type.to_le_bytes(),
11608 request.conference_id.to_le_bytes(),
11609 request.passthrough_party_id.to_le_bytes(),
11610 request.dtmf_type.to_le_bytes(),
11611 ]
11612 .concat();
11613
11614 for message in [
11615 ClientMessage::SubscribeDtmfPayloadResponse(identity),
11616 ClientMessage::UnsubscribeDtmfPayloadResponse(identity),
11617 ] {
11618 let frame = FrameDecoder::new()
11619 .push(&message.encode(ProtocolVersion::V22).unwrap())
11620 .unwrap()
11621 .remove(0);
11622 assert_eq!(frame.payload, identity_payload);
11623 assert_eq!(
11624 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
11625 message
11626 );
11627 }
11628
11629 for (message, expected_payload) in [
11630 (
11631 ServerMessage::SubscribeDtmfPayloadRequest(request),
11632 request_payload.as_slice(),
11633 ),
11634 (
11635 ServerMessage::SubscribeDtmfPayloadError(identity),
11636 identity_payload.as_slice(),
11637 ),
11638 (
11639 ServerMessage::UnsubscribeDtmfPayloadRequest(request),
11640 request_payload.as_slice(),
11641 ),
11642 (
11643 ServerMessage::UnsubscribeDtmfPayloadError(identity),
11644 identity_payload.as_slice(),
11645 ),
11646 ] {
11647 let frame = FrameDecoder::new()
11648 .push(&message.encode(ProtocolVersion::V22).unwrap())
11649 .unwrap()
11650 .remove(0);
11651 assert_eq!(frame.payload.as_slice(), expected_payload);
11652 assert_eq!(
11653 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11654 message
11655 );
11656 }
11657
11658 assert!(
11659 ClientMessage::decode_with_version(
11660 Frame::new(22, wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES, vec![0; 13]),
11661 ProtocolVersion::V22,
11662 )
11663 .is_err()
11664 );
11665 assert!(
11666 ServerMessage::decode(
11667 Frame::new(22, wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ, vec![0; 17]),
11668 ProtocolVersion::V22,
11669 )
11670 .is_err()
11671 );
11672 }
11673
11674 #[test]
11675 fn add_participant_response_preserves_progressive_identifier_bytes() {
11676 for identifier_len in [0, 1, 64, 256] {
11677 let identifier = (0..identifier_len)
11678 .map(|index| (index as u8).wrapping_mul(17).wrapping_add(3))
11679 .collect::<Vec<_>>();
11680 let mut payload = [
11681 42_u32.to_le_bytes(),
11682 100_u32.to_le_bytes(),
11683 0_u32.to_le_bytes(),
11684 ]
11685 .concat();
11686 payload.extend_from_slice(&identifier);
11687 let decoded = ControlMessage::decode(
11688 Frame::new(22, wire_id::ADD_PARTICIPANT_RES, payload),
11689 ProtocolVersion::V22,
11690 )
11691 .unwrap();
11692 let ControlMessage::AddParticipantResponse(response) = &decoded else {
11693 panic!("expected add-participant response");
11694 };
11695 assert_eq!(response.bridge_participant_id.as_bytes(), identifier);
11696 let frame = FrameDecoder::new()
11697 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
11698 .unwrap()
11699 .remove(0);
11700 assert_eq!(frame.payload.len(), 272);
11701 assert_eq!(&frame.payload[12..12 + identifier_len], identifier);
11702 assert!(
11703 frame.payload[12 + identifier_len..]
11704 .iter()
11705 .all(|byte| *byte == 0)
11706 );
11707 }
11708
11709 let identifier = (0..257)
11710 .map(|index| (index as u8).wrapping_mul(17).wrapping_add(3))
11711 .collect::<Vec<_>>();
11712 let canonical = ControlMessage::AddParticipantResponse(AddParticipantResponse {
11713 conference_id: 42.into(),
11714 call_reference: 100.into(),
11715 result: AddParticipantResult::Ok,
11716 bridge_participant_id: BoundedBytes::try_from(identifier).unwrap(),
11717 });
11718 let frame = FrameDecoder::new()
11719 .push(&canonical.encode(ProtocolVersion::V22).unwrap())
11720 .unwrap()
11721 .remove(0);
11722 assert_eq!(frame.payload.len(), 272);
11723 assert_eq!(
11724 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11725 canonical
11726 );
11727
11728 for invalid_len in [270, 271, 273] {
11729 assert!(
11730 ControlMessage::decode(
11731 Frame::new(22, wire_id::ADD_PARTICIPANT_RES, vec![0; invalid_len]),
11732 ProtocolVersion::V22,
11733 )
11734 .is_err()
11735 );
11736 }
11737 let mut invalid_alignment = vec![0; 272];
11738 invalid_alignment[271] = 1;
11739 assert!(
11740 ControlMessage::decode(
11741 Frame::new(22, wire_id::ADD_PARTICIPANT_RES, invalid_alignment),
11742 ProtocolVersion::V22,
11743 )
11744 .is_err()
11745 );
11746 }
11747
11748 #[test]
11749 fn xml_alarm_accepts_and_preserves_every_bounded_frame_form() {
11750 for payload_len in [0, 1, 2_000, 2_004, 2_048] {
11751 let payload = (0..payload_len)
11752 .map(|index| (index as u8).wrapping_mul(29).wrapping_add(1))
11753 .collect::<Vec<_>>();
11754 let decoded = ClientMessage::decode_with_version(
11755 Frame::new(22, wire_id::XML_ALARM, payload.clone()),
11756 ProtocolVersion::V22,
11757 )
11758 .unwrap();
11759 let ClientMessage::XmlAlarm(message) = &decoded else {
11760 panic!("expected XML alarm");
11761 };
11762 assert_eq!(message.wire_payload(), payload.as_slice());
11763 let frame = FrameDecoder::new()
11764 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
11765 .unwrap()
11766 .remove(0);
11767 assert_eq!(frame.payload, payload);
11768 }
11769
11770 assert!(matches!(
11771 ClientMessage::decode_with_version(
11772 Frame::new(22, wire_id::XML_ALARM, vec![0; 2_049]),
11773 ProtocolVersion::V22,
11774 ),
11775 Err(CodecError::CountTooLarge {
11776 message_id: wire_id::XML_ALARM,
11777 count: 2_049,
11778 maximum: 2_048,
11779 ..
11780 })
11781 ));
11782
11783 let with_suffix =
11784 XmlAlarmMessage::from_wire_payload(b"<alarm/>\0ignored".to_vec()).unwrap();
11785 assert_eq!(with_suffix.xml_bytes(), b"<alarm/>");
11786 assert_eq!(with_suffix.wire_payload(), b"<alarm/>\0ignored");
11787
11788 let canonical = XmlAlarmMessage::from_xml(vec![b'x'; 2_000]).unwrap();
11789 assert_eq!(canonical.xml_bytes().len(), 2_000);
11790 assert_eq!(canonical.wire_payload().len(), 2_004);
11791 assert!(XmlAlarmMessage::from_xml(vec![b'x'; 2_001]).is_err());
11792 }
11793
11794 #[test]
11795 fn xml_alarm_preserves_bounded_wire_payload() {
11796 let xml = "<?xml version=\"1.0\"?><x-cisco-alarm></x-cisco-alarm>";
11797 let mut payload = vec![0; 2_000];
11798 payload[..xml.len()].copy_from_slice(xml.as_bytes());
11799
11800 let decoded =
11801 ClientMessage::decode(Frame::new(0, wire_id::XML_ALARM, payload.clone())).unwrap();
11802 let ClientMessage::XmlAlarm(message) = &decoded else {
11803 panic!("expected XML alarm");
11804 };
11805 assert_eq!(message.xml_bytes(), xml.as_bytes());
11806 assert_eq!(message.wire_payload(), payload);
11807 let frame = FrameDecoder::new()
11808 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
11809 .unwrap()
11810 .remove(0);
11811 assert_eq!(frame.payload, payload);
11812 }
11813
11814 #[test]
11815 fn location_information_uses_text_storage_followed_by_zero_alignment() {
11816 let maximum = "x".repeat(2_400);
11817 let encoded = ClientMessage::LocationInfo {
11818 xml: maximum.clone(),
11819 }
11820 .encode(ProtocolVersion::V22)
11821 .unwrap();
11822 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
11823 assert_eq!(frame.payload.len(), 2_404);
11824 assert_eq!(&frame.payload[2_400..], &[0, 0, 0, 0]);
11825 assert_eq!(
11826 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
11827 ClientMessage::LocationInfo { xml: maximum }
11828 );
11829
11830 assert!(matches!(
11831 ClientMessage::LocationInfo {
11832 xml: "x".repeat(2_401),
11833 }
11834 .encode(ProtocolVersion::V22),
11835 Err(CodecError::TextTooLong {
11836 message_id: wire_id::LOCATION_INFO,
11837 maximum: 2_400,
11838 ..
11839 })
11840 ));
11841
11842 let mut nonzero_alignment = vec![0; 2_404];
11843 nonzero_alignment[2_401] = 1;
11844 assert!(matches!(
11845 ClientMessage::decode_with_version(
11846 Frame::new(22, wire_id::LOCATION_INFO, nonzero_alignment),
11847 ProtocolVersion::V22,
11848 ),
11849 Err(CodecError::InvalidValue {
11850 message_id: wire_id::LOCATION_INFO,
11851 field: "reserved payload byte",
11852 ..
11853 })
11854 ));
11855 }
11856
11857 #[test]
11858 fn decodes_7961_button_template_request_with_payload() {
11859 assert_eq!(
11860 ClientMessage::decode(Frame::new(
11861 22,
11862 wire_id::BUTTON_TEMPLATE_REQ,
11863 34_u32.to_le_bytes().to_vec(),
11864 ))
11865 .unwrap(),
11866 ClientMessage::ButtonTemplateRequest
11867 );
11868 }
11869}