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 number: value.directory_number.text()?,
3799 display_name: value.display_name.text()?,
3800 })
3801 }
3802 wire_id::LINE_STAT_DYNAMIC => decode_dynamic_line_status(p),
3803 wire_id::BUTTON_TEMPLATE => {
3804 let value: WireButtonTemplate = decode(frame.message_id, p)?;
3805 if value.count > BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 {
3806 return Err(CodecError::CountTooLarge {
3807 message_id: frame.message_id,
3808 field: "button definitions in message",
3809 count: usize_from_wire(
3810 frame.message_id,
3811 "button definitions in message",
3812 value.count,
3813 )?,
3814 maximum: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK,
3815 });
3816 }
3817 let total = usize_from_wire(frame.message_id, "button definitions", value.total)?;
3818 let offset = usize_from_wire(frame.message_id, "button offset", value.offset)?;
3819 let count = usize_from_wire(frame.message_id, "button definitions", value.count)?;
3820 if offset.checked_add(count).is_none_or(|end| end > total) {
3821 return Err(CodecError::InvalidValue {
3822 message_id: frame.message_id,
3823 field: "button template range",
3824 value: u64::from(value.offset) + u64::from(value.count),
3825 });
3826 }
3827 let buttons = value.definitions[..count]
3828 .iter()
3829 .map(|definition| ButtonTemplateEntry {
3830 instance: u32::from(definition.instance),
3831 button_type: ButtonType::from(u32::from(definition.button_type)),
3832 })
3833 .collect::<Vec<_>>();
3834 Ok(Self::ButtonTemplate {
3835 offset: value.offset,
3836 total: value.total,
3837 buttons,
3838 })
3839 }
3840 wire_id::VERSION => {
3841 let value: WireFixedText<16> = decode(frame.message_id, p)?;
3842 Ok(Self::Version {
3843 firmware: value.text()?,
3844 })
3845 }
3846 wire_id::SERVER_RES => {
3847 let servers = match protocol.wire() {
3848 17.. => {
3849 let value: WireServerResponse<WireExtendedAddress> =
3850 decode(frame.message_id, p)?;
3851 decode_server_endpoints(
3852 frame.message_id,
3853 value.names,
3854 value.ports,
3855 value
3856 .addresses
3857 .map(|address| address.to_ip(frame.message_id))
3858 .into_iter()
3859 .collect::<Result<Vec<_>, _>>()?,
3860 )?
3861 }
3862 _ => {
3863 let value: WireServerResponse<WireIpv4Address> =
3864 decode(frame.message_id, p)?;
3865 decode_server_endpoints(
3866 frame.message_id,
3867 value.names,
3868 value.ports,
3869 value
3870 .addresses
3871 .map(|address| address.to_ip(frame.message_id))
3872 .into_iter()
3873 .collect::<Result<Vec<_>, _>>()?,
3874 )?
3875 }
3876 };
3877 Ok(Self::ServerResponse { servers })
3878 }
3879 wire_id::DEFINE_TIME_DATE => {
3880 let value: WireTimeDate = decode(frame.message_id, p)?;
3881 Ok(Self::TimeDate {
3882 year: value.year,
3883 month: value.month,
3884 weekday: value.weekday,
3885 day: value.day,
3886 hour: value.hour,
3887 minute: value.minute,
3888 second: value.second,
3889 milliseconds: value.milliseconds,
3890 unix_seconds: value.unix_seconds,
3891 })
3892 }
3893 wire_id::SOFT_KEY_TEMPLATE_RES => {
3894 let value: WireSoftKeyTemplate = decode(frame.message_id, p)?;
3895 let actions = value
3896 .definitions
3897 .iter()
3898 .filter(|definition| definition.event != 0)
3899 .map(|definition| SoftKey::from(definition.event))
3900 .collect();
3901 Ok(Self::SoftKeyTemplate { actions })
3902 }
3903 wire_id::SOFT_KEY_SET_RES => {
3904 let value: WireSoftKeySet = decode(frame.message_id, p)?;
3905 let profile =
3906 SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
3907 let actions = value
3908 .sets
3909 .get(mode.wire_value() as usize)
3910 .map(|set| {
3911 set.template_indexes
3912 .iter()
3913 .copied()
3914 .take_while(|index| *index != 0)
3915 .map(|index| SoftKey::from(u32::from(index)))
3916 .collect()
3917 })
3918 .unwrap_or_default();
3919 (mode, actions)
3920 }))?;
3921 Ok(Self::SoftKeySet { profile })
3922 }
3923 wire_id::SELECT_SOFT_KEYS => {
3924 let value: WireSelectSoftKeys = decode(frame.message_id, p)?;
3925 Ok(Self::SelectSoftKeys {
3926 line_instance: value.line_instance,
3927 call_reference: value.call_reference,
3928 set: KeyMode::from(value.set),
3929 valid_mask: value.valid_mask,
3930 })
3931 }
3932 wire_id::CALL_STATE => {
3933 let value: WireCallState = decode(frame.message_id, p)?;
3934 Ok(Self::CallState {
3935 state: CallState::from(value.state),
3936 line_instance: value.line_instance,
3937 call_reference: value.call_reference,
3938 })
3939 }
3940 wire_id::CALL_INFO => {
3941 let value: WireCallInfo = decode(frame.message_id, p)?;
3942 let call_type = super::values::CallType::from(value.call_type);
3943 Ok(Self::CallInfo {
3944 info: CallInfo {
3945 direction: match call_type {
3946 super::values::CallType::Inbound => {
3947 crate::types::CallDirection::Inbound
3948 }
3949 _ => crate::types::CallDirection::Outbound,
3950 },
3951 calling_name: value.calling_name.text()?,
3952 calling_number: value.calling_number.text()?,
3953 called_name: value.called_name.text()?,
3954 called_number: value.called_number.text()?,
3955 original_called_name: value.original_called_name.text()?,
3956 original_called_number: value.original_called_number.text()?,
3957 last_redirecting_name: value.last_redirecting_name.text()?,
3958 last_redirecting_number: value.last_redirecting_number.text()?,
3959 original_redirect_reason: value.original_redirect_reason,
3960 last_redirect_reason: value.last_redirect_reason,
3961 party_restrictions: value.party_restrictions,
3962 },
3963 line_instance: value.line_instance,
3964 call_reference: value.call_reference,
3965 })
3966 }
3967 wire_id::CALL_INFO_DYNAMIC => decode_dynamic_call_info(p, protocol),
3968 wire_id::DISPLAY_PROMPT_STATUS => {
3969 let value: WirePromptStatus = decode(frame.message_id, p)?;
3970 Ok(Self::DisplayPrompt {
3971 timeout_seconds: value.timeout_seconds,
3972 text: value.text.text()?,
3973 line_instance: value.line_instance,
3974 call_reference: value.call_reference,
3975 })
3976 }
3977 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS => {
3978 const HEADER_SIZE: usize = 12;
3979 if p.len() < HEADER_SIZE {
3980 return Err(CodecError::Truncated {
3981 message_id: frame.message_id,
3982 needed: HEADER_SIZE,
3983 actual: p.len(),
3984 });
3985 }
3986 let value: WireDynamicPromptHeader = decode(frame.message_id, &p[..HEADER_SIZE])?;
3987 Ok(Self::DisplayPrompt {
3988 timeout_seconds: value.timeout_seconds,
3989 text: decode_dynamic_text(frame.message_id, p, HEADER_SIZE)?,
3990 line_instance: value.line_instance,
3991 call_reference: value.call_reference,
3992 })
3993 }
3994 wire_id::CLEAR_PROMPT_STATUS => {
3995 let value: WireLineCall = decode(frame.message_id, p)?;
3996 Ok(Self::ClearPrompt {
3997 line_instance: value.line_instance,
3998 call_reference: value.call_reference,
3999 })
4000 }
4001 wire_id::DISPLAY_NOTIFY => {
4002 let value: WireNotify = decode(frame.message_id, p)?;
4003 Ok(Self::DisplayNotify {
4004 timeout_seconds: value.timeout_seconds,
4005 text: value.text.text()?,
4006 })
4007 }
4008 wire_id::DISPLAY_DYNAMIC_NOTIFY => {
4009 const HEADER_SIZE: usize = 4;
4010 if p.len() < HEADER_SIZE {
4011 return Err(CodecError::Truncated {
4012 message_id: frame.message_id,
4013 needed: HEADER_SIZE,
4014 actual: p.len(),
4015 });
4016 }
4017 let value: WireDynamicNotifyHeader = decode(frame.message_id, &p[..HEADER_SIZE])?;
4018 Ok(Self::DisplayNotify {
4019 timeout_seconds: value.timeout_seconds,
4020 text: decode_dynamic_text(frame.message_id, p, HEADER_SIZE)?,
4021 })
4022 }
4023 wire_id::CLEAR_NOTIFY => Ok(Self::ClearNotify),
4024 wire_id::DISPLAY_PRIORITY_NOTIFY => {
4025 let value: WirePriorityNotify = decode(frame.message_id, p)?;
4026 Ok(Self::DisplayPriorityNotify {
4027 timeout_seconds: value.timeout_seconds,
4028 priority: NotificationPriority::from(value.priority),
4029 text: value.text.text()?,
4030 })
4031 }
4032 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY => {
4033 const HEADER_SIZE: usize = 8;
4034 if p.len() < HEADER_SIZE {
4035 return Err(CodecError::Truncated {
4036 message_id: frame.message_id,
4037 needed: HEADER_SIZE,
4038 actual: p.len(),
4039 });
4040 }
4041 let value: WireDynamicPriorityNotifyHeader =
4042 decode(frame.message_id, &p[..HEADER_SIZE])?;
4043 Ok(Self::DisplayPriorityNotify {
4044 timeout_seconds: value.timeout_seconds,
4045 priority: NotificationPriority::from(value.priority),
4046 text: decode_dynamic_text(frame.message_id, p, HEADER_SIZE)?,
4047 })
4048 }
4049 wire_id::CLEAR_PRIORITY_NOTIFY => {
4050 let value: WireOneWord = decode(frame.message_id, p)?;
4051 Ok(Self::ClearPriorityNotify {
4052 priority: NotificationPriority::from(value.value),
4053 })
4054 }
4055 wire_id::NOTIFY_DTMF_TONE | wire_id::SEND_DTMF_TONE => {
4056 let value: WireDtmfToneControl = decode(frame.message_id, p)?;
4057 let message = DtmfToneControl {
4058 tone: Tone::from(value.tone),
4059 conference_id: value.conference_id.into(),
4060 passthrough_party_id: value.passthrough_party_id,
4061 };
4062 if frame.message_id == wire_id::NOTIFY_DTMF_TONE {
4063 Ok(Self::NotifyDtmfTone(message))
4064 } else {
4065 Ok(Self::SendDtmfTone(message))
4066 }
4067 }
4068 wire_id::START_ANNOUNCEMENT => {
4069 const PAYLOAD_SIZE: usize = 464;
4070 validate_exact_payload(p, frame.message_id, PAYLOAD_SIZE)?;
4071 let value: WireStartAnnouncement = decode(frame.message_id, p)?;
4072 let mut announcements = value
4073 .announcements
4074 .into_iter()
4075 .map(|entry| AnnouncementEntry {
4076 locale: entry.locale,
4077 country: entry.country,
4078 tone: Tone::from(entry.tone),
4079 })
4080 .collect::<Vec<_>>();
4081 while announcements.last().is_some_and(|entry| {
4082 entry.locale == 0 && entry.country == 0 && entry.tone.wire_value() == 0
4083 }) {
4084 announcements.pop();
4085 }
4086 let mut matrix_conference_party_ids = value.matrix_conference_party_ids.to_vec();
4087 while matrix_conference_party_ids.last() == Some(&0) {
4088 matrix_conference_party_ids.pop();
4089 }
4090 Ok(Self::StartAnnouncement {
4091 announcements,
4092 end_of_ack: value.end_of_ack,
4093 conference_id: value.conference_id,
4094 matrix_conference_party_ids,
4095 hearing_conference_party_mask: value.hearing_conference_party_mask,
4096 play_mode: value.play_mode,
4097 })
4098 }
4099 wire_id::STOP_ANNOUNCEMENT => {
4100 validate_exact_payload(p, frame.message_id, 4)?;
4101 let value: WireOneWord = decode(frame.message_id, p)?;
4102 Ok(Self::StopAnnouncement {
4103 conference_id: value.value,
4104 })
4105 }
4106 wire_id::ANNOUNCEMENT_FINISH => {
4107 validate_exact_payload(p, frame.message_id, 8)?;
4108 let value: WireAnnouncementFinish = decode(frame.message_id, p)?;
4109 Ok(Self::AnnouncementFinish {
4110 conference_id: value.conference_id,
4111 play_status: value.play_status,
4112 })
4113 }
4114 wire_id::CLEAR_CONFERENCE => {
4115 validate_exact_payload(p, frame.message_id, 8)?;
4116 let value: WireCallParty = decode(frame.message_id, p)?;
4117 Ok(Self::ClearConference {
4118 conference_id: value.call_reference.into(),
4119 service_number: value.passthrough_party_id,
4120 })
4121 }
4122 wire_id::CREATE_CONFERENCE_REQ => {
4123 validate_conference_data_length(p, frame.message_id, 76, 72)?;
4124 let value: WireCreateConferenceRequest = decode_zero_padded(frame.message_id, p)?;
4125 Ok(Self::CreateConferenceRequest(CreateConferenceRequest {
4126 conference_id: value.conference_id.into(),
4127 reserved_participants: value.reserved_participants,
4128 resource_type: ConferenceResourceType::from(value.resource_type),
4129 application_id: value.application_id.into(),
4130 application_conference_id: value.application_conference_id.text()?,
4131 application_data: value.application_data.text()?,
4132 passthrough_data: value.passthrough_data,
4133 }))
4134 }
4135 wire_id::DELETE_CONFERENCE_REQ => {
4136 validate_exact_payload(p, frame.message_id, 4)?;
4137 let value: WireOneWord = decode(frame.message_id, p)?;
4138 Ok(Self::DeleteConferenceRequest {
4139 conference_id: value.value.into(),
4140 })
4141 }
4142 wire_id::MODIFY_CONFERENCE_REQ => {
4143 validate_conference_data_length(p, frame.message_id, 72, 68)?;
4144 let value: WireModifyConferenceRequest = decode_zero_padded(frame.message_id, p)?;
4145 Ok(Self::ModifyConferenceRequest(ModifyConferenceRequest {
4146 conference_id: value.conference_id.into(),
4147 reserved_participants: value.reserved_participants,
4148 application_id: value.application_id.into(),
4149 application_conference_id: value.application_conference_id.text()?,
4150 application_data: value.application_data.text()?,
4151 passthrough_data: value.passthrough_data,
4152 }))
4153 }
4154 wire_id::AUDIT_CONFERENCE_REQ => {
4155 validate_exact_payload(p, frame.message_id, 0)?;
4156 Ok(Self::AuditConferenceRequest)
4157 }
4158 wire_id::ADD_PARTICIPANT_REQ => {
4159 let (conference_id, participant) = decode_participant_request(p, frame.message_id)?;
4160 Ok(Self::AddParticipantRequest(AddParticipantRequest {
4161 conference_id,
4162 participant,
4163 }))
4164 }
4165 wire_id::DROP_PARTICIPANT_REQ => {
4166 validate_exact_payload(p, frame.message_id, 8)?;
4167 let value: WireCallParty = decode(frame.message_id, p)?;
4168 Ok(Self::DropParticipantRequest {
4169 conference_id: value.call_reference.into(),
4170 call_reference: value.passthrough_party_id.into(),
4171 })
4172 }
4173 wire_id::AUDIT_PARTICIPANT_REQ => {
4174 validate_exact_payload(p, frame.message_id, 4)?;
4175 let value: WireOneWord = decode(frame.message_id, p)?;
4176 Ok(Self::AuditParticipantRequest {
4177 conference_id: value.value.into(),
4178 })
4179 }
4180 wire_id::CHANGE_PARTICIPANT_REQ => {
4181 let (conference_id, participant) = decode_participant_request(p, frame.message_id)?;
4182 Ok(Self::ChangeParticipantRequest(ChangeParticipantRequest {
4183 conference_id,
4184 participant,
4185 }))
4186 }
4187 wire_id::STOP_MULTIMEDIA_TRANSMISSION | wire_id::CLOSE_MULTIMEDIA_RECEIVE_CHANNEL => {
4188 let value: WireMultimediaStreamControl = decode(frame.message_id, p)?;
4189 let message = MultimediaStreamControl {
4190 conference_id: value.conference_id.into(),
4191 passthrough_party_id: value.passthrough_party_id.into(),
4192 call_reference: value.call_reference.into(),
4193 port_handling_flag: value.port_handling_flag,
4194 };
4195 if frame.message_id == wire_id::STOP_MULTIMEDIA_TRANSMISSION {
4196 Ok(Self::StopMultimediaTransmission(message))
4197 } else {
4198 Ok(Self::CloseMultimediaReceiveChannel(message))
4199 }
4200 }
4201 wire_id::FLOW_CONTROL_COMMAND | wire_id::FLOW_CONTROL_NOTIFY => {
4202 let value: WireVideoFlowControl = decode(frame.message_id, p)?;
4203 let message = VideoFlowControl {
4204 conference_id: value.conference_id.into(),
4205 passthrough_party_id: value.passthrough_party_id.into(),
4206 call_reference: value.call_reference.into(),
4207 maximum_bit_rate: value.maximum_bit_rate,
4208 };
4209 if frame.message_id == wire_id::FLOW_CONTROL_COMMAND {
4210 Ok(Self::FlowControlCommand(message))
4211 } else {
4212 Ok(Self::FlowControlNotify(message))
4213 }
4214 }
4215 wire_id::VIDEO_DISPLAY_COMMAND => {
4216 let value: WireVideoDisplayCommand = decode(frame.message_id, p)?;
4217 Ok(Self::VideoDisplayCommand {
4218 conference_id: value.conference_id.into(),
4219 call_reference: value.call_reference.into(),
4220 layout_id: value.layout_id,
4221 })
4222 }
4223 wire_id::ACTIVATE_CALL_PLANE => {
4224 let value: WireOneWord = decode(frame.message_id, p)?;
4225 Ok(Self::ActivateCallPlane {
4226 line_instance: value.value,
4227 })
4228 }
4229 wire_id::DEACTIVATE_CALL_PLANE => Ok(Self::DeactivateCallPlane),
4230 wire_id::BACKSPACE_RESPONSE => {
4231 let value: WireLineCall = decode(frame.message_id, p)?;
4232 Ok(Self::BackspaceResponse {
4233 line_instance: value.line_instance,
4234 call_reference: value.call_reference,
4235 })
4236 }
4237 wire_id::REGISTER_TOKEN_ACK => Ok(Self::RegisterTokenAck),
4238 wire_id::REGISTER_TOKEN_REJECT => {
4239 let value: WireOneWord = decode(frame.message_id, p)?;
4240 Ok(Self::RegisterTokenReject {
4241 backoff_seconds: value.value,
4242 })
4243 }
4244 wire_id::SPCP_REGISTER_TOKEN_ACK => {
4245 let value: WireOneWord = decode(frame.message_id, p)?;
4246 Ok(Self::SpcpRegisterTokenAck {
4247 features: value.value,
4248 })
4249 }
4250 wire_id::SPCP_REGISTER_TOKEN_REJECT => {
4251 let value: WireOneWord = decode(frame.message_id, p)?;
4252 Ok(Self::SpcpRegisterTokenReject {
4253 backoff_seconds: value.value,
4254 })
4255 }
4256 wire_id::SET_RINGER => {
4257 let value: WireModeLineCall = decode(frame.message_id, p)?;
4258 Ok(Self::SetRinger {
4259 mode: RingerMode::from(value.mode),
4260 duration: RingDuration::from(value.duration),
4261 line_instance: value.line_instance,
4262 call_reference: value.call_reference,
4263 })
4264 }
4265 wire_id::SET_LAMP => {
4266 let value: WireLampState = decode(frame.message_id, p)?;
4267 Ok(Self::SetLamp {
4268 stimulus: ButtonType::from(value.stimulus),
4269 instance: value.instance,
4270 mode: LampMode::from(value.mode),
4271 })
4272 }
4273 wire_id::SET_HOOK_FLASH_DETECT => {
4274 validate_exact_payload(p, frame.message_id, 0)?;
4275 Ok(Self::SetHookFlashDetect)
4276 }
4277 wire_id::START_TONE => {
4278 let value: WireToneLineCall = decode(frame.message_id, p)?;
4279 Ok(Self::StartTone {
4280 tone: Tone::from(value.tone),
4281 direction: ToneDirection::from(value.direction),
4282 line_instance: value.line_instance,
4283 call_reference: value.call_reference,
4284 })
4285 }
4286 wire_id::STOP_TONE => {
4287 let (line_instance, call_reference) = match protocol.wire() {
4288 12.. => {
4289 let value: WireStopToneV12 = decode(frame.message_id, p)?;
4290 (value.line_instance, value.call_reference)
4291 }
4292 _ => {
4293 let value: WireLineCall = decode(frame.message_id, p)?;
4294 (value.line_instance, value.call_reference)
4295 }
4296 };
4297 Ok(Self::StopTone {
4298 line_instance,
4299 call_reference,
4300 })
4301 }
4302 wire_id::START_MULTICAST_MEDIA_RECEPTION => {
4303 decode_start_multicast_reception(p, protocol, frame.message_id)
4304 }
4305 wire_id::START_MULTICAST_MEDIA_TRANSMISSION => {
4306 decode_start_multicast_transmission(p, protocol, frame.message_id)
4307 }
4308 wire_id::STOP_MULTICAST_MEDIA_RECEPTION
4309 | wire_id::STOP_MULTICAST_MEDIA_TRANSMISSION => {
4310 validate_exact_payload(p, frame.message_id, 12)?;
4311 let value: WireStopMulticast = decode(frame.message_id, p)?;
4312 if frame.message_id == wire_id::STOP_MULTICAST_MEDIA_RECEPTION {
4313 Ok(Self::StopMulticastMediaReception {
4314 conference_id: value.conference_id.into(),
4315 passthrough_party_id: value.passthrough_party_id.into(),
4316 call_reference: value.call_reference.into(),
4317 })
4318 } else {
4319 Ok(Self::StopMulticastMediaTransmission {
4320 conference_id: value.conference_id.into(),
4321 passthrough_party_id: value.passthrough_party_id.into(),
4322 call_reference: value.call_reference.into(),
4323 })
4324 }
4325 }
4326 wire_id::OPEN_RECEIVE_CHANNEL => decode_open_receive(p, protocol, frame.message_id),
4327 wire_id::CLOSE_RECEIVE_CHANNEL => {
4328 validate_exact_payload(p, frame.message_id, 16)?;
4329 let value: WireAudioStreamControl = decode(frame.message_id, p)?;
4330 Ok(Self::CloseReceiveChannel(AudioStreamControl {
4331 conference_id: value.conference_id.into(),
4332 passthrough_party_id: value.passthrough_party_id.into(),
4333 call_reference: value.call_reference.into(),
4334 port_handling_flag: value.port_handling_flag,
4335 }))
4336 }
4337 wire_id::CONNECTION_STATISTICS_REQ => match protocol.wire() {
4338 19.. => decode_connection_statistics_request::<25, 3>(p, frame.message_id),
4339 _ => decode_connection_statistics_request::<24, 0>(p, frame.message_id),
4340 },
4341 wire_id::START_MEDIA_TRANSMISSION => decode_start_media(p, protocol, frame.message_id),
4342 wire_id::STOP_MEDIA_TRANSMISSION => {
4343 validate_exact_payload(p, frame.message_id, 16)?;
4344 let value: WireAudioStreamControl = decode(frame.message_id, p)?;
4345 Ok(Self::StopMediaTransmission(AudioStreamControl {
4346 conference_id: value.conference_id.into(),
4347 passthrough_party_id: value.passthrough_party_id.into(),
4348 call_reference: value.call_reference.into(),
4349 port_handling_flag: value.port_handling_flag,
4350 }))
4351 }
4352 wire_id::START_MEDIA_RECEPTION => {
4353 validate_exact_payload(p, frame.message_id, 0)?;
4354 Ok(Self::StartMediaReception)
4355 }
4356 wire_id::STOP_MEDIA_RECEPTION => {
4357 let value: WireStopMediaReception = decode(frame.message_id, p)?;
4358 Ok(Self::StopMediaReception {
4359 conference_id: value.conference_id.into(),
4360 passthrough_party_id: value.passthrough_party_id.into(),
4361 })
4362 }
4363 wire_id::SET_SPEAKER_MODE => {
4364 let value: WireOneWord = decode(frame.message_id, p)?;
4365 Ok(Self::SetSpeakerMode(SpeakerMode::from(value.value)))
4366 }
4367 wire_id::SET_MICROPHONE_MODE => {
4368 let value: WireOneWord = decode(frame.message_id, p)?;
4369 Ok(Self::SetMicrophoneMode(MicrophoneMode::from(value.value)))
4370 }
4371 wire_id::RESET => {
4372 let value: WireOneWord = decode(frame.message_id, p)?;
4373 Ok(Self::Reset(ResetType::from(value.value)))
4374 }
4375 wire_id::DISPLAY_TEXT => {
4376 let value: WireFixedText<32> = decode(frame.message_id, p)?;
4377 Ok(Self::DisplayText {
4378 text: value.text()?,
4379 })
4380 }
4381 wire_id::CLEAR_DISPLAY => Ok(Self::ClearDisplay),
4382 wire_id::FORWARD_STAT => match protocol.wire() {
4383 19.. => decode_forward_status::<25, 3>(p, frame.message_id),
4384 _ => decode_forward_status::<24, 0>(p, frame.message_id),
4385 },
4386 wire_id::SPEED_DIAL_STAT => {
4387 let value: WireSpeedDialStatus = decode(frame.message_id, p)?;
4388 Ok(Self::SpeedDialStatus {
4389 instance: value.instance,
4390 number: value.number.text()?,
4391 display_name: value.display_name.text()?,
4392 })
4393 }
4394 wire_id::SPEED_DIAL_STAT_DYNAMIC => decode_dynamic_speed_dial_status(p),
4395 wire_id::START_MEDIA_FAILURE_DETECTION => {
4396 let value: WireMediaFailureDetection = decode(frame.message_id, p)?;
4397 Ok(Self::StartMediaFailureDetection(MediaFailureDetection {
4398 conference_id: value.conference_id.into(),
4399 passthrough_party_id: value.passthrough_party_id,
4400 packet_millis: value.packet_millis,
4401 codec: Codec::from(value.codec),
4402 echo_cancellation: EchoCancellation::from(value.echo_cancellation),
4403 codec_qualifier: value.codec_qualifier,
4404 call_reference: value.call_reference.into(),
4405 }))
4406 }
4407 wire_id::OPEN_MULTIMEDIA_CHANNEL => {
4408 decode_open_multimedia(p, protocol, frame.message_id)
4409 .map(Self::OpenMultimediaChannel)
4410 }
4411 wire_id::START_MULTIMEDIA_TRANSMISSION => {
4412 decode_start_multimedia(p, protocol, frame.message_id)
4413 .map(Self::StartMultimediaTransmission)
4414 }
4415 wire_id::MISCELLANEOUS_COMMAND => {
4416 decode_miscellaneous_command(p, frame.message_id).map(Self::MiscellaneousCommand)
4417 }
4418 wire_id::DIALED_NUMBER => match protocol.wire() {
4419 19.. => decode_dialed_number::<25, 3>(p, frame.message_id),
4420 _ => decode_dialed_number::<24, 0>(p, frame.message_id),
4421 },
4422 wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ => {
4423 let value: WireDtmfPayloadRequest = decode(frame.message_id, p)?;
4424 Ok(Self::SubscribeDtmfPayloadRequest(
4425 dtmf_payload_request_from_wire(value),
4426 ))
4427 }
4428 wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR => {
4429 let value: WireDtmfPayloadIdentity = decode(frame.message_id, p)?;
4430 Ok(Self::SubscribeDtmfPayloadError(
4431 dtmf_payload_identity_from_wire(value),
4432 ))
4433 }
4434 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ => {
4435 let value: WireDtmfPayloadRequest = decode(frame.message_id, p)?;
4436 Ok(Self::UnsubscribeDtmfPayloadRequest(
4437 dtmf_payload_request_from_wire(value),
4438 ))
4439 }
4440 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR => {
4441 let value: WireDtmfPayloadIdentity = decode(frame.message_id, p)?;
4442 Ok(Self::UnsubscribeDtmfPayloadError(
4443 dtmf_payload_identity_from_wire(value),
4444 ))
4445 }
4446 wire_id::USER_TO_DEVICE_DATA => {
4447 decode_user_data(p, frame.message_id).map(Self::UserToDeviceData)
4448 }
4449 wire_id::USER_TO_DEVICE_DATA_V1 => {
4450 decode_user_data_v1(p, frame.message_id).map(Self::UserToDeviceDataV1)
4451 }
4452 wire_id::FEATURE_STAT => {
4453 let value: WireFeatureStatus = decode(frame.message_id, p)?;
4454 Ok(Self::FeatureStatus {
4455 instance: value.instance,
4456 button_type: ButtonType::from(value.button_type),
4457 label: value.label.text()?,
4458 state: value.state,
4459 })
4460 }
4461 wire_id::FEATURE_STAT_DYNAMIC => {
4462 let value: WireFeatureStatusDynamic = decode(frame.message_id, p)?;
4463 Ok(Self::FeatureStatus {
4464 instance: value.instance,
4465 button_type: ButtonType::from(value.button_type),
4466 label: value.label.text()?,
4467 state: value.state,
4468 })
4469 }
4470 wire_id::SERVICE_URL_STAT => {
4471 let value: WireServiceUrlStatus = decode(frame.message_id, p)?;
4472 Ok(Self::ServiceUrlStatus {
4473 index: value.index,
4474 url: value.url.text()?,
4475 label: value.label.text()?,
4476 extension_text: String::new(),
4477 })
4478 }
4479 wire_id::SERVICE_URL_STAT_DYNAMIC => decode_dynamic_service_url_status(p, protocol),
4480 wire_id::CALL_SELECT_STAT => {
4481 let value: WireCallSelectStatus = decode(frame.message_id, p)?;
4482 Ok(Self::CallSelectStatus {
4483 status: value.status,
4484 call_reference: value.call_reference,
4485 line_instance: value.line_instance,
4486 })
4487 }
4488 wire_id::PORT_REQUEST => {
4489 let request = match protocol.wire() {
4490 20.. => {
4491 let value: WirePortRequestV20 = decode(frame.message_id, p)?;
4492 PortRequest {
4493 conference_id: value.base.conference_id.into(),
4494 call_reference: value.base.call_reference.into(),
4495 passthrough_party_id: value.base.passthrough_party_id.into(),
4496 transport: MediaTransport::from(value.base.transport),
4497 address_type: Some(IpAddressType::from(value.address_type)),
4498 media_type: Some(MediaType::from(value.media_type)),
4499 }
4500 }
4501 _ => {
4502 let value: WirePortRequest = decode(frame.message_id, p)?;
4503 PortRequest {
4504 conference_id: value.conference_id.into(),
4505 call_reference: value.call_reference.into(),
4506 passthrough_party_id: value.passthrough_party_id.into(),
4507 transport: MediaTransport::from(value.transport),
4508 address_type: None,
4509 media_type: None,
4510 }
4511 }
4512 };
4513 Ok(Self::PortRequest(request))
4514 }
4515 wire_id::PORT_CLOSE => {
4516 let close = match protocol.wire() {
4517 20.. => {
4518 let value: WirePortCloseV20 = decode(frame.message_id, p)?;
4519 PortClose {
4520 conference_id: value.base.conference_id.into(),
4521 call_reference: value.base.call_reference.into(),
4522 passthrough_party_id: value.base.passthrough_party_id.into(),
4523 media_type: Some(MediaType::from(value.media_type)),
4524 }
4525 }
4526 _ => {
4527 let value: WirePortClose = decode(frame.message_id, p)?;
4528 PortClose {
4529 conference_id: value.conference_id.into(),
4530 call_reference: value.call_reference.into(),
4531 passthrough_party_id: value.passthrough_party_id.into(),
4532 media_type: None,
4533 }
4534 }
4535 };
4536 Ok(Self::PortClose(close))
4537 }
4538 wire_id::SUBSCRIPTION_STAT => {
4539 let value: WireSubscriptionStatus = decode(frame.message_id, p)?;
4540 Ok(Self::SubscriptionStatus {
4541 transaction_id: value.transaction_id,
4542 feature_id: value.feature_id,
4543 timer_seconds: value.timer_seconds,
4544 cause: SubscriptionCause::from(value.cause),
4545 })
4546 }
4547 wire_id::NOTIFICATION => {
4548 let value: WireNotification = decode(frame.message_id, p)?;
4549 Ok(Self::Notification {
4550 transaction_id: value.transaction_id,
4551 feature_id: value.feature_id,
4552 status: BusyLampFieldState::from(value.status),
4553 text: value.text.text()?,
4554 })
4555 }
4556 wire_id::CALL_HISTORY_DISPOSITION => {
4557 let value: WireCallHistoryDisposition = decode(frame.message_id, p)?;
4558 Ok(Self::CallHistoryDisposition {
4559 disposition: CallHistoryDisposition::from(value.disposition),
4560 line_instance: value.line_instance,
4561 call_reference: value.call_reference,
4562 })
4563 }
4564 wire_id::CALL_COUNT_RES => {
4565 let value: WireCallCountResponse = decode(frame.message_id, p)?;
4566 let line_data_entries = usize_from_wire(
4567 frame.message_id,
4568 "call-count line data",
4569 value.line_data_entries,
4570 )?;
4571 if line_data_entries > CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES {
4572 return Err(CodecError::CountTooLarge {
4573 message_id: frame.message_id,
4574 field: "call-count line data",
4575 count: line_data_entries,
4576 maximum: CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES,
4577 });
4578 }
4579 Ok(Self::CallCountResponse(CallCountResponse {
4580 total_configured_lines: value.total_configured_lines,
4581 starting_line_instance: value.starting_line_instance,
4582 line_data: value
4583 .line_data
4584 .into_iter()
4585 .take(line_data_entries)
4586 .map(|entry| CallCountLineData {
4587 max_calls: entry.max_calls,
4588 busy_trigger: entry.busy_trigger,
4589 })
4590 .collect(),
4591 }))
4592 }
4593 wire_id::RECORDING_STATUS => {
4594 let value: WireRecordingStatus = decode(frame.message_id, p)?;
4595 Ok(Self::RecordingStatus {
4596 call_reference: value.call_reference,
4597 active: decode_bool_word(value.active, frame.message_id, "recording active")?,
4598 })
4599 }
4600 _ => {
4601 let message_type = frame.message_type();
4602 if message_type.is_known() {
4603 preserve_known_message(frame, message_type).map(Self::KnownOpaque)
4604 } else {
4605 Ok(Self::Unknown(RawMessage {
4606 message_id: frame.message_id,
4607 protocol_version: frame.protocol_version,
4608 payload: frame.payload,
4609 }))
4610 }
4611 }
4612 }
4613 }
4614
4615 pub fn encode(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
4620 self.encode_for_session(protocol.into())
4621 }
4622
4623 pub fn encode_for_session(
4625 &self,
4626 session: StationSessionContext,
4627 ) -> Result<Vec<u8>, CodecError> {
4628 let (message_id, payload, header_protocol) = self.payload(session, None)?;
4629 reject_non_station_route(
4630 message_id,
4631 MessageRoute::ControlToStation,
4632 "control-to-station",
4633 )?;
4634 Frame::new(header_protocol, message_id, payload).encode()
4635 }
4636
4637 fn encode_unchecked(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
4638 let (message_id, payload, header_protocol) = self.payload(protocol.into(), None)?;
4639 Frame::new(header_protocol, message_id, payload).encode()
4640 }
4641
4642 pub fn encode_for_legacy_station(
4647 &self,
4648 protocol: ProtocolVersion,
4649 code_page: LegacyCodePage,
4650 ) -> Result<Vec<u8>, CodecError> {
4651 self.encode_for_legacy_session(protocol.into(), code_page)
4652 }
4653
4654 pub fn encode_for_legacy_session(
4657 &self,
4658 session: StationSessionContext,
4659 code_page: LegacyCodePage,
4660 ) -> Result<Vec<u8>, CodecError> {
4661 let (message_id, payload, header_protocol) = self.payload(session, Some(code_page))?;
4662 reject_non_station_route(
4663 message_id,
4664 MessageRoute::ControlToStation,
4665 "control-to-station",
4666 )?;
4667 Frame::new(header_protocol, message_id, payload).encode()
4668 }
4669
4670 fn payload(
4671 &self,
4672 session: StationSessionContext,
4673 legacy_code_page: Option<LegacyCodePage>,
4674 ) -> Result<(u32, Vec<u8>, u32), CodecError> {
4675 let protocol = session.protocol;
4676 let mut p = Vec::new();
4677 let id = match self {
4678 Self::RegisterAck {
4679 keepalive_seconds,
4680 secondary_keepalive_seconds,
4681 protocol,
4682 features,
4683 date_template,
4684 } => {
4685 if date_template.as_str().len() > 6 {
4686 return Err(CodecError::TextTooLong {
4687 message_id: wire_id::REGISTER_ACK,
4688 field: "date template",
4689 actual: date_template.as_str().len(),
4690 maximum: 6,
4691 });
4692 }
4693 let mut wire_date_template = [0_u8; 6];
4694 wire_date_template[..date_template.as_str().len()]
4695 .copy_from_slice(date_template.as_str().as_bytes());
4696 p = encode(
4697 wire_id::REGISTER_ACK,
4698 &WireRegisterAck {
4699 keepalive_seconds: *keepalive_seconds,
4700 date_template: wire_date_template,
4701 alignment: [0; 2],
4702 secondary_keepalive_seconds: *secondary_keepalive_seconds,
4703 protocol_features: {
4704 let mut bytes = features.bits().to_le_bytes();
4705 bytes[0] = protocol.wire() as u8;
4706 bytes
4707 },
4708 },
4709 )?;
4710 return Ok((wire_id::REGISTER_ACK, p, 0));
4711 }
4712 Self::RegisterReject { reason } => {
4713 p = encode(
4714 wire_id::REGISTER_REJECT,
4715 &WireFixedText::<33>::new(wire_id::REGISTER_REJECT, "reject reason", reason)?,
4716 )?;
4717 pad_dynamic_payload(&mut p);
4718 wire_id::REGISTER_REJECT
4719 }
4720 Self::KeepAliveAck => return Ok((wire_id::KEEP_ALIVE_ACK, p, 0)),
4721 Self::UnregisterAck => {
4722 p = encode(wire_id::UNREGISTER_ACK, &WireOneWord { value: 0 })?;
4723 return Ok((wire_id::UNREGISTER_ACK, p, 0));
4724 }
4725 Self::CapabilitiesRequest => wire_id::CAPABILITIES_REQ,
4726 Self::EnunciatorCommand => wire_id::ENUNCIATOR_COMMAND,
4727 Self::ConfigStatus(status) => {
4728 if session.uses_dynamic_general_ui() {
4729 p = encode_dynamic_config_status(status)?;
4730 wire_id::CONFIG_STAT_DYNAMIC
4731 } else {
4732 p = encode(
4733 wire_id::CONFIG_STAT,
4734 &WireConfigStatus {
4735 device_id: WireFixedText::new(
4736 wire_id::CONFIG_STAT,
4737 "device ID",
4738 &status.device_name,
4739 )?,
4740 station_user_id: status.station_user_id,
4741 station_instance: status.station_instance,
4742 user_name: WireFixedText::new_station(
4743 wire_id::CONFIG_STAT,
4744 "user name",
4745 &status.user_name,
4746 legacy_code_page,
4747 )?,
4748 server_name: WireFixedText::new_station(
4749 wire_id::CONFIG_STAT,
4750 "server name",
4751 &status.server_name,
4752 legacy_code_page,
4753 )?,
4754 line_count: status.line_count,
4755 speed_dial_count: status.speed_dial_count,
4756 },
4757 )?;
4758 wire_id::CONFIG_STAT
4759 }
4760 }
4761 Self::LineStatus {
4762 instance,
4763 number,
4764 display_name,
4765 } => {
4766 if session.uses_dynamic_general_ui() {
4767 p = encode_dynamic_line_status(
4768 *instance,
4769 number,
4770 display_name,
4771 legacy_code_page,
4772 )?;
4773 wire_id::LINE_STAT_DYNAMIC
4774 } else {
4775 p = encode(
4776 wire_id::LINE_STAT,
4777 &WireLineStatus {
4778 line_instance: *instance,
4779 directory_number: WireFixedText::new(
4780 wire_id::LINE_STAT,
4781 "line number",
4782 number,
4783 )?,
4784 display_name: WireFixedText::new_station(
4785 wire_id::LINE_STAT,
4786 "display name",
4787 display_name,
4788 legacy_code_page,
4789 )?,
4790 display_label: WireFixedText::new_station(
4791 wire_id::LINE_STAT,
4792 "line label",
4793 display_name,
4794 legacy_code_page,
4795 )?,
4796 reserved: 0,
4797 },
4798 )?;
4799 wire_id::LINE_STAT
4800 }
4801 }
4802 Self::ButtonTemplate {
4803 offset,
4804 total,
4805 buttons,
4806 } => {
4807 if buttons.len() > BUTTON_TEMPLATE_ENTRIES_PER_CHUNK {
4808 return Err(CodecError::CountTooLarge {
4809 message_id: wire_id::BUTTON_TEMPLATE,
4810 field: "button definitions",
4811 count: buttons.len(),
4812 maximum: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK,
4813 });
4814 }
4815 let count = u32::try_from(buttons.len()).map_err(|_| CodecError::InvalidValue {
4816 message_id: wire_id::BUTTON_TEMPLATE,
4817 field: "button definitions in message",
4818 value: buttons.len() as u64,
4819 })?;
4820 if offset.checked_add(count).is_none_or(|end| end > *total) {
4821 return Err(CodecError::InvalidValue {
4822 message_id: wire_id::BUTTON_TEMPLATE,
4823 field: "button template range",
4824 value: u64::from(*offset) + u64::from(count),
4825 });
4826 }
4827 let mut definitions =
4828 [WireButtonDefinition::default(); BUTTON_TEMPLATE_ENTRIES_PER_CHUNK];
4829 for (index, button) in buttons.iter().enumerate() {
4830 definitions[index] = WireButtonDefinition {
4831 instance: u8::try_from(button.instance).map_err(|_| {
4832 CodecError::InvalidValue {
4833 message_id: wire_id::BUTTON_TEMPLATE,
4834 field: "button instance",
4835 value: u64::from(button.instance),
4836 }
4837 })?,
4838 button_type: u8::try_from(button.button_type.wire_value()).map_err(
4839 |_| CodecError::InvalidValue {
4840 message_id: wire_id::BUTTON_TEMPLATE,
4841 field: "button type",
4842 value: u64::from(button.button_type.wire_value()),
4843 },
4844 )?,
4845 };
4846 }
4847 p = encode(
4848 wire_id::BUTTON_TEMPLATE,
4849 &WireButtonTemplate {
4850 offset: *offset,
4851 count,
4852 total: *total,
4853 definitions,
4854 },
4855 )?;
4856 wire_id::BUTTON_TEMPLATE
4857 }
4858 Self::Version { firmware } => {
4859 p = encode(
4860 wire_id::VERSION,
4861 &WireFixedText::<16>::new(wire_id::VERSION, "firmware", firmware)?,
4862 )?;
4863 wire_id::VERSION
4864 }
4865 Self::ServerResponse { servers } => {
4866 if servers.is_empty() {
4867 return Err(CodecError::InvalidValue {
4868 message_id: wire_id::SERVER_RES,
4869 field: "server endpoints",
4870 value: 0,
4871 });
4872 }
4873 if servers.len() > MAX_SIGNALING_SERVERS {
4874 return Err(CodecError::CountTooLarge {
4875 message_id: wire_id::SERVER_RES,
4876 field: "server endpoints",
4877 count: servers.len(),
4878 maximum: MAX_SIGNALING_SERVERS,
4879 });
4880 }
4881 if servers
4882 .iter()
4883 .any(|server| server.address.is_unspecified() || server.address.is_multicast())
4884 {
4885 return Err(CodecError::InvalidValue {
4886 message_id: wire_id::SERVER_RES,
4887 field: "server address",
4888 value: 0,
4889 });
4890 }
4891 let names: [WireFixedText<48>; MAX_SIGNALING_SERVERS] = (0..MAX_SIGNALING_SERVERS)
4892 .map(|index| {
4893 WireFixedText::new(
4894 wire_id::SERVER_RES,
4895 "server name",
4896 servers.get(index).map_or("", |server| server.name.as_str()),
4897 )
4898 })
4899 .collect::<Result<Vec<_>, _>>()?
4900 .try_into()
4901 .map_err(|_| CodecError::InvalidValue {
4902 message_id: wire_id::SERVER_RES,
4903 field: "server endpoint array",
4904 value: servers.len() as u64,
4905 })?;
4906 let ports = std::array::from_fn(|index| {
4907 servers
4908 .get(index)
4909 .map_or(0, |server| u32::from(server.port.get()))
4910 });
4911 p = match protocol.wire() {
4912 17.. => encode(
4913 wire_id::SERVER_RES,
4914 &WireServerResponse::<WireExtendedAddress> {
4915 names,
4916 ports,
4917 addresses: std::array::from_fn(|index| {
4918 WireExtendedAddress::from_ip(
4919 servers
4920 .get(index)
4921 .map_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED), |server| {
4922 server.address
4923 }),
4924 )
4925 }),
4926 },
4927 ),
4928 _ => {
4929 let addresses: [WireIpv4Address; MAX_SIGNALING_SERVERS] = (0
4930 ..MAX_SIGNALING_SERVERS)
4931 .map(|index| {
4932 WireIpv4Address::from_ip(
4933 servers
4934 .get(index)
4935 .map_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED), |server| {
4936 server.address
4937 }),
4938 wire_id::SERVER_RES,
4939 "IP address family for pre-v17 protocol",
4940 )
4941 })
4942 .collect::<Result<Vec<_>, _>>()?
4943 .try_into()
4944 .map_err(|_| CodecError::InvalidValue {
4945 message_id: wire_id::SERVER_RES,
4946 field: "server address array",
4947 value: servers.len() as u64,
4948 })?;
4949 encode(
4950 wire_id::SERVER_RES,
4951 &WireServerResponse::<WireIpv4Address> {
4952 names,
4953 ports,
4954 addresses,
4955 },
4956 )
4957 }
4958 }?;
4959 wire_id::SERVER_RES
4960 }
4961 Self::TimeDate {
4962 year,
4963 month,
4964 weekday,
4965 day,
4966 hour,
4967 minute,
4968 second,
4969 milliseconds,
4970 unix_seconds,
4971 } => {
4972 p = encode(
4973 wire_id::DEFINE_TIME_DATE,
4974 &WireTimeDate {
4975 year: *year,
4976 month: *month,
4977 weekday: *weekday,
4978 day: *day,
4979 hour: *hour,
4980 minute: *minute,
4981 second: *second,
4982 milliseconds: *milliseconds,
4983 unix_seconds: *unix_seconds,
4984 },
4985 )?;
4986 wire_id::DEFINE_TIME_DATE
4987 }
4988 Self::SoftKeyTemplate { actions } => {
4989 const LABELS: [u16; 32] = [
4993 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 202, 65, 67, 63,
4994 79, 78, 54, 62, 77, 80, 88, 60, 0, 201,
4995 ];
4996 let mut available = [false; 32];
4997 for action in actions {
4998 let value = action.wire_value();
4999 if !action.is_known() || value == 0 || value > available.len() as u32 {
5000 return Err(CodecError::InvalidDefinition(format!(
5001 "soft-key template contains unknown action {value}"
5002 )));
5003 }
5004 let slot = value as usize - 1;
5005 if std::mem::replace(&mut available[slot], true) {
5006 return Err(CodecError::InvalidDefinition(format!(
5007 "soft-key template repeats action {value}"
5008 )));
5009 }
5010 }
5011 p = encode(
5012 wire_id::SOFT_KEY_TEMPLATE_RES,
5013 &WireSoftKeyTemplate {
5014 offset: 0,
5015 count: 32,
5016 total: 32,
5017 definitions: std::array::from_fn(|index| {
5018 if !available[index] {
5019 return WireSoftKeyDefinition {
5020 label: [0; 16],
5021 event: 0,
5022 };
5023 }
5024 let label = LABELS[index];
5025 let mut encoded = [0; 16];
5026 match label {
5027 201 => encoded[..4].copy_from_slice(b"Dial"),
5028 0 => {}
5029 _ => {
5030 encoded[0] = 0x80;
5031 encoded[1] = label as u8;
5032 }
5033 }
5034 WireSoftKeyDefinition {
5035 label: encoded,
5036 event: index as u32 + 1,
5037 }
5038 }),
5039 },
5040 )?;
5041 wire_id::SOFT_KEY_TEMPLATE_RES
5042 }
5043 Self::SoftKeySet { profile } => {
5044 let definitions = (0_u32..16)
5045 .map(KeyMode::from)
5046 .map(|mode| profile.actions(mode))
5047 .map(|actions| {
5048 let mut indexes = [0_u8; 16];
5049 let mut info = [0_u16; 16];
5050 for (slot, action) in actions.iter().copied().enumerate() {
5051 let template = action.wire_value() as u8;
5052 indexes[slot] = template;
5053 info[slot] = u16::from(template) + 300;
5054 }
5055 WireSoftKeySetDefinition {
5056 template_indexes: indexes,
5057 info,
5058 }
5059 })
5060 .collect();
5061 p = encode(
5062 wire_id::SOFT_KEY_SET_RES,
5063 &WireSoftKeySet {
5064 offset: 0,
5065 count: 16,
5066 total: 16,
5067 sets: definitions,
5068 },
5069 )?;
5070 wire_id::SOFT_KEY_SET_RES
5071 }
5072 Self::SelectSoftKeys {
5073 line_instance,
5074 call_reference,
5075 set,
5076 valid_mask,
5077 } => {
5078 p = encode(
5079 wire_id::SELECT_SOFT_KEYS,
5080 &WireSelectSoftKeys {
5081 line_instance: *line_instance,
5082 call_reference: *call_reference,
5083 set: set.wire_value(),
5084 valid_mask: *valid_mask,
5085 },
5086 )?;
5087 wire_id::SELECT_SOFT_KEYS
5088 }
5089 Self::CallState {
5090 state,
5091 line_instance,
5092 call_reference,
5093 } => {
5094 p = encode(
5095 wire_id::CALL_STATE,
5096 &WireCallState {
5097 state: state.wire_value(),
5098 line_instance: *line_instance,
5099 call_reference: *call_reference,
5100 visibility: 0,
5101 precedence: call_state_precedence(*state),
5102 domain: 0,
5103 },
5104 )?;
5105 wire_id::CALL_STATE
5106 }
5107 Self::CallInfo {
5108 info,
5109 line_instance,
5110 call_reference,
5111 } => {
5112 if session.uses_dynamic_general_ui() {
5113 p = encode_dynamic_call_info(info, *line_instance, *call_reference, protocol)?;
5114 wire_id::CALL_INFO_DYNAMIC
5115 } else {
5116 p = encode(
5117 wire_id::CALL_INFO,
5118 &WireCallInfo {
5119 calling_name: WireFixedText::new(
5120 wire_id::CALL_INFO,
5121 "calling name",
5122 &info.calling_name,
5123 )?,
5124 calling_number: WireFixedText::new(
5125 wire_id::CALL_INFO,
5126 "calling number",
5127 &info.calling_number,
5128 )?,
5129 called_name: WireFixedText::new(
5130 wire_id::CALL_INFO,
5131 "called name",
5132 &info.called_name,
5133 )?,
5134 called_number: WireFixedText::new(
5135 wire_id::CALL_INFO,
5136 "called number",
5137 &info.called_number,
5138 )?,
5139 line_instance: *line_instance,
5140 call_reference: *call_reference,
5141 call_type: match info.direction {
5142 crate::types::CallDirection::Inbound => 1,
5143 crate::types::CallDirection::Outbound => 2,
5144 },
5145 original_called_name: WireFixedText::new(
5146 wire_id::CALL_INFO,
5147 "original called name",
5148 &info.original_called_name,
5149 )?,
5150 original_called_number: WireFixedText::new(
5151 wire_id::CALL_INFO,
5152 "original called number",
5153 &info.original_called_number,
5154 )?,
5155 last_redirecting_name: WireFixedText::new(
5156 wire_id::CALL_INFO,
5157 "last redirecting name",
5158 &info.last_redirecting_name,
5159 )?,
5160 last_redirecting_number: WireFixedText::new(
5161 wire_id::CALL_INFO,
5162 "last redirecting number",
5163 &info.last_redirecting_number,
5164 )?,
5165 original_redirect_reason: info.original_redirect_reason,
5166 last_redirect_reason: info.last_redirect_reason,
5167 voice_mailboxes: std::array::from_fn(|_| {
5168 WireFixedText::new(wire_id::CALL_INFO, "voice mailbox", "").unwrap()
5169 }),
5170 call_instance: 1,
5171 security_status: 0,
5172 party_restrictions: info.party_restrictions,
5173 },
5174 )?;
5175 wire_id::CALL_INFO
5176 }
5177 }
5178 Self::DisplayPrompt {
5179 timeout_seconds,
5180 text,
5181 line_instance,
5182 call_reference,
5183 } => {
5184 if session.uses_dynamic_general_ui() {
5185 p = encode(
5186 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS,
5187 &WireDynamicPromptHeader {
5188 timeout_seconds: *timeout_seconds,
5189 line_instance: *line_instance,
5190 call_reference: *call_reference,
5191 },
5192 )?;
5193 push_dynamic_text(
5194 &mut p,
5195 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS,
5196 "prompt",
5197 text,
5198 96,
5199 )?;
5200 pad_dynamic_payload(&mut p);
5201 wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS
5202 } else {
5203 p = encode(
5204 wire_id::DISPLAY_PROMPT_STATUS,
5205 &WirePromptStatus {
5206 timeout_seconds: *timeout_seconds,
5207 text: WireFixedText::new(
5208 wire_id::DISPLAY_PROMPT_STATUS,
5209 "prompt",
5210 text,
5211 )?,
5212 line_instance: *line_instance,
5213 call_reference: *call_reference,
5214 },
5215 )?;
5216 wire_id::DISPLAY_PROMPT_STATUS
5217 }
5218 }
5219 Self::ClearPrompt {
5220 line_instance,
5221 call_reference,
5222 } => {
5223 p = encode(
5224 wire_id::CLEAR_PROMPT_STATUS,
5225 &WireLineCall {
5226 line_instance: *line_instance,
5227 call_reference: *call_reference,
5228 },
5229 )?;
5230 wire_id::CLEAR_PROMPT_STATUS
5231 }
5232 Self::DisplayNotify {
5233 timeout_seconds,
5234 text,
5235 } => {
5236 if session.uses_dynamic_general_ui() {
5237 p = encode(
5238 wire_id::DISPLAY_DYNAMIC_NOTIFY,
5239 &WireDynamicNotifyHeader {
5240 timeout_seconds: *timeout_seconds,
5241 },
5242 )?;
5243 push_dynamic_text(
5244 &mut p,
5245 wire_id::DISPLAY_DYNAMIC_NOTIFY,
5246 "notification",
5247 text,
5248 96,
5249 )?;
5250 pad_dynamic_payload(&mut p);
5251 wire_id::DISPLAY_DYNAMIC_NOTIFY
5252 } else {
5253 p = encode(
5254 wire_id::DISPLAY_NOTIFY,
5255 &WireNotify {
5256 timeout_seconds: *timeout_seconds,
5257 text: WireFixedText::new(
5258 wire_id::DISPLAY_NOTIFY,
5259 "notification",
5260 text,
5261 )?,
5262 },
5263 )?;
5264 wire_id::DISPLAY_NOTIFY
5265 }
5266 }
5267 Self::ClearNotify => wire_id::CLEAR_NOTIFY,
5268 Self::DisplayPriorityNotify {
5269 timeout_seconds,
5270 priority,
5271 text,
5272 } => {
5273 if session.uses_dynamic_general_ui() {
5274 p = encode(
5275 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY,
5276 &WireDynamicPriorityNotifyHeader {
5277 timeout_seconds: *timeout_seconds,
5278 priority: priority.wire_value(),
5279 },
5280 )?;
5281 push_dynamic_text(
5282 &mut p,
5283 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY,
5284 "notification",
5285 text,
5286 96,
5287 )?;
5288 pad_dynamic_payload(&mut p);
5289 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY
5290 } else {
5291 p = encode(
5292 wire_id::DISPLAY_PRIORITY_NOTIFY,
5293 &WirePriorityNotify {
5294 timeout_seconds: *timeout_seconds,
5295 priority: priority.wire_value(),
5296 text: WireFixedText::new(
5297 wire_id::DISPLAY_PRIORITY_NOTIFY,
5298 "notification",
5299 text,
5300 )?,
5301 },
5302 )?;
5303 wire_id::DISPLAY_PRIORITY_NOTIFY
5304 }
5305 }
5306 Self::ClearPriorityNotify { priority } => {
5307 p = encode(
5308 wire_id::CLEAR_PRIORITY_NOTIFY,
5309 &WireOneWord {
5310 value: priority.wire_value(),
5311 },
5312 )?;
5313 wire_id::CLEAR_PRIORITY_NOTIFY
5314 }
5315 Self::NotifyDtmfTone(message) | Self::SendDtmfTone(message) => {
5316 let message_id = if matches!(self, Self::NotifyDtmfTone(_)) {
5317 wire_id::NOTIFY_DTMF_TONE
5318 } else {
5319 wire_id::SEND_DTMF_TONE
5320 };
5321 p = encode(
5322 message_id,
5323 &WireDtmfToneControl {
5324 tone: message.tone.wire_value(),
5325 conference_id: message.conference_id.get(),
5326 passthrough_party_id: message.passthrough_party_id,
5327 },
5328 )?;
5329 message_id
5330 }
5331 Self::StartAnnouncement {
5332 announcements,
5333 end_of_ack,
5334 conference_id,
5335 matrix_conference_party_ids,
5336 hearing_conference_party_mask,
5337 play_mode,
5338 } => {
5339 if announcements.len() > 32 {
5340 return Err(CodecError::CountTooLarge {
5341 message_id: wire_id::START_ANNOUNCEMENT,
5342 field: "announcements",
5343 count: announcements.len(),
5344 maximum: 32,
5345 });
5346 }
5347 if matrix_conference_party_ids.len() > 16 {
5348 return Err(CodecError::CountTooLarge {
5349 message_id: wire_id::START_ANNOUNCEMENT,
5350 field: "matrix conference party identifiers",
5351 count: matrix_conference_party_ids.len(),
5352 maximum: 16,
5353 });
5354 }
5355 let mut wire_announcements = [WireAnnouncementEntry::default(); 32];
5356 for (wire, entry) in wire_announcements.iter_mut().zip(announcements) {
5357 *wire = WireAnnouncementEntry {
5358 locale: entry.locale,
5359 country: entry.country,
5360 tone: entry.tone.wire_value(),
5361 };
5362 }
5363 let mut wire_party_ids = [0; 16];
5364 wire_party_ids[..matrix_conference_party_ids.len()]
5365 .copy_from_slice(matrix_conference_party_ids);
5366 p = encode(
5367 wire_id::START_ANNOUNCEMENT,
5368 &WireStartAnnouncement {
5369 announcements: wire_announcements,
5370 end_of_ack: *end_of_ack,
5371 conference_id: *conference_id,
5372 matrix_conference_party_ids: wire_party_ids,
5373 hearing_conference_party_mask: *hearing_conference_party_mask,
5374 play_mode: *play_mode,
5375 },
5376 )?;
5377 wire_id::START_ANNOUNCEMENT
5378 }
5379 Self::StopAnnouncement { conference_id } => {
5380 p = encode(
5381 wire_id::STOP_ANNOUNCEMENT,
5382 &WireOneWord {
5383 value: *conference_id,
5384 },
5385 )?;
5386 wire_id::STOP_ANNOUNCEMENT
5387 }
5388 Self::AnnouncementFinish {
5389 conference_id,
5390 play_status,
5391 } => {
5392 p = encode(
5393 wire_id::ANNOUNCEMENT_FINISH,
5394 &WireAnnouncementFinish {
5395 conference_id: *conference_id,
5396 play_status: *play_status,
5397 },
5398 )?;
5399 wire_id::ANNOUNCEMENT_FINISH
5400 }
5401 Self::ClearConference {
5402 conference_id,
5403 service_number,
5404 } => {
5405 p = encode(
5406 wire_id::CLEAR_CONFERENCE,
5407 &WireCallParty {
5408 call_reference: conference_id.get(),
5409 passthrough_party_id: *service_number,
5410 },
5411 )?;
5412 wire_id::CLEAR_CONFERENCE
5413 }
5414 Self::CreateConferenceRequest(request) => {
5415 p = encode(
5416 wire_id::CREATE_CONFERENCE_REQ,
5417 &WireCreateConferenceRequest {
5418 conference_id: request.conference_id.get(),
5419 reserved_participants: request.reserved_participants,
5420 resource_type: request.resource_type.wire_value(),
5421 application_id: request.application_id.get(),
5422 application_conference_id: WireFixedText::new(
5423 wire_id::CREATE_CONFERENCE_REQ,
5424 "application conference ID",
5425 &request.application_conference_id,
5426 )?,
5427 application_data: WireFixedText::new(
5428 wire_id::CREATE_CONFERENCE_REQ,
5429 "application data",
5430 &request.application_data,
5431 )?,
5432 data_length: validate_conference_data_for_encode(
5433 wire_id::CREATE_CONFERENCE_REQ,
5434 &request.passthrough_data,
5435 )?,
5436 passthrough_data: request.passthrough_data.clone(),
5437 },
5438 )?;
5439 wire_id::CREATE_CONFERENCE_REQ
5440 }
5441 Self::DeleteConferenceRequest { conference_id } => {
5442 p = encode(
5443 wire_id::DELETE_CONFERENCE_REQ,
5444 &WireOneWord {
5445 value: conference_id.get(),
5446 },
5447 )?;
5448 wire_id::DELETE_CONFERENCE_REQ
5449 }
5450 Self::ModifyConferenceRequest(request) => {
5451 p = encode(
5452 wire_id::MODIFY_CONFERENCE_REQ,
5453 &WireModifyConferenceRequest {
5454 conference_id: request.conference_id.get(),
5455 reserved_participants: request.reserved_participants,
5456 application_id: request.application_id.get(),
5457 application_conference_id: WireFixedText::new(
5458 wire_id::MODIFY_CONFERENCE_REQ,
5459 "application conference ID",
5460 &request.application_conference_id,
5461 )?,
5462 application_data: WireFixedText::new(
5463 wire_id::MODIFY_CONFERENCE_REQ,
5464 "application data",
5465 &request.application_data,
5466 )?,
5467 data_length: validate_conference_data_for_encode(
5468 wire_id::MODIFY_CONFERENCE_REQ,
5469 &request.passthrough_data,
5470 )?,
5471 passthrough_data: request.passthrough_data.clone(),
5472 },
5473 )?;
5474 wire_id::MODIFY_CONFERENCE_REQ
5475 }
5476 Self::AuditConferenceRequest => wire_id::AUDIT_CONFERENCE_REQ,
5477 Self::AddParticipantRequest(request) => {
5478 p = encode(
5479 wire_id::ADD_PARTICIPANT_REQ,
5480 &encode_participant_request(
5481 wire_id::ADD_PARTICIPANT_REQ,
5482 request.conference_id,
5483 &request.participant,
5484 )?,
5485 )?;
5486 wire_id::ADD_PARTICIPANT_REQ
5487 }
5488 Self::DropParticipantRequest {
5489 conference_id,
5490 call_reference,
5491 } => {
5492 p = encode(
5493 wire_id::DROP_PARTICIPANT_REQ,
5494 &WireCallParty {
5495 call_reference: conference_id.get(),
5496 passthrough_party_id: call_reference.get(),
5497 },
5498 )?;
5499 wire_id::DROP_PARTICIPANT_REQ
5500 }
5501 Self::AuditParticipantRequest { conference_id } => {
5502 p = encode(
5503 wire_id::AUDIT_PARTICIPANT_REQ,
5504 &WireOneWord {
5505 value: conference_id.get(),
5506 },
5507 )?;
5508 wire_id::AUDIT_PARTICIPANT_REQ
5509 }
5510 Self::ChangeParticipantRequest(request) => {
5511 p = encode(
5512 wire_id::CHANGE_PARTICIPANT_REQ,
5513 &encode_participant_request(
5514 wire_id::CHANGE_PARTICIPANT_REQ,
5515 request.conference_id,
5516 &request.participant,
5517 )?,
5518 )?;
5519 wire_id::CHANGE_PARTICIPANT_REQ
5520 }
5521 Self::StopMultimediaTransmission(message)
5522 | Self::CloseMultimediaReceiveChannel(message) => {
5523 let message_id = if matches!(self, Self::StopMultimediaTransmission(_)) {
5524 wire_id::STOP_MULTIMEDIA_TRANSMISSION
5525 } else {
5526 wire_id::CLOSE_MULTIMEDIA_RECEIVE_CHANNEL
5527 };
5528 p = encode(
5529 message_id,
5530 &WireMultimediaStreamControl {
5531 conference_id: message.conference_id.get(),
5532 passthrough_party_id: message.passthrough_party_id.get(),
5533 call_reference: message.call_reference.get(),
5534 port_handling_flag: message.port_handling_flag,
5535 },
5536 )?;
5537 message_id
5538 }
5539 Self::FlowControlCommand(message) | Self::FlowControlNotify(message) => {
5540 let message_id = if matches!(self, Self::FlowControlCommand(_)) {
5541 wire_id::FLOW_CONTROL_COMMAND
5542 } else {
5543 wire_id::FLOW_CONTROL_NOTIFY
5544 };
5545 p = encode(
5546 message_id,
5547 &WireVideoFlowControl {
5548 conference_id: message.conference_id.get(),
5549 passthrough_party_id: message.passthrough_party_id.get(),
5550 call_reference: message.call_reference.get(),
5551 maximum_bit_rate: message.maximum_bit_rate,
5552 },
5553 )?;
5554 message_id
5555 }
5556 Self::VideoDisplayCommand {
5557 conference_id,
5558 call_reference,
5559 layout_id,
5560 } => {
5561 p = encode(
5562 wire_id::VIDEO_DISPLAY_COMMAND,
5563 &WireVideoDisplayCommand {
5564 conference_id: conference_id.get(),
5565 call_reference: call_reference.get(),
5566 layout_id: *layout_id,
5567 },
5568 )?;
5569 wire_id::VIDEO_DISPLAY_COMMAND
5570 }
5571 Self::ActivateCallPlane { line_instance } => {
5572 p = encode(
5573 wire_id::ACTIVATE_CALL_PLANE,
5574 &WireOneWord {
5575 value: *line_instance,
5576 },
5577 )?;
5578 wire_id::ACTIVATE_CALL_PLANE
5579 }
5580 Self::DeactivateCallPlane => wire_id::DEACTIVATE_CALL_PLANE,
5581 Self::BackspaceResponse {
5582 line_instance,
5583 call_reference,
5584 } => {
5585 p = encode(
5586 wire_id::BACKSPACE_RESPONSE,
5587 &WireLineCall {
5588 line_instance: *line_instance,
5589 call_reference: *call_reference,
5590 },
5591 )?;
5592 wire_id::BACKSPACE_RESPONSE
5593 }
5594 Self::RegisterTokenAck => wire_id::REGISTER_TOKEN_ACK,
5595 Self::RegisterTokenReject { backoff_seconds } => {
5596 p = encode(
5597 wire_id::REGISTER_TOKEN_REJECT,
5598 &WireOneWord {
5599 value: *backoff_seconds,
5600 },
5601 )?;
5602 wire_id::REGISTER_TOKEN_REJECT
5603 }
5604 Self::SpcpRegisterTokenAck { features } => {
5605 p = encode(
5606 wire_id::SPCP_REGISTER_TOKEN_ACK,
5607 &WireOneWord { value: *features },
5608 )?;
5609 wire_id::SPCP_REGISTER_TOKEN_ACK
5610 }
5611 Self::SpcpRegisterTokenReject { backoff_seconds } => {
5612 p = encode(
5613 wire_id::SPCP_REGISTER_TOKEN_REJECT,
5614 &WireOneWord {
5615 value: *backoff_seconds,
5616 },
5617 )?;
5618 wire_id::SPCP_REGISTER_TOKEN_REJECT
5619 }
5620 Self::SetRinger {
5621 mode,
5622 duration,
5623 line_instance,
5624 call_reference,
5625 } => {
5626 p = encode(
5627 wire_id::SET_RINGER,
5628 &WireModeLineCall {
5629 mode: mode.wire_value(),
5630 duration: duration.wire_value(),
5631 line_instance: *line_instance,
5632 call_reference: *call_reference,
5633 },
5634 )?;
5635 wire_id::SET_RINGER
5636 }
5637 Self::SetLamp {
5638 stimulus,
5639 instance,
5640 mode,
5641 } => {
5642 p = encode(
5643 wire_id::SET_LAMP,
5644 &WireLampState {
5645 stimulus: stimulus.wire_value(),
5646 instance: *instance,
5647 mode: mode.wire_value(),
5648 },
5649 )?;
5650 wire_id::SET_LAMP
5651 }
5652 Self::SetHookFlashDetect => wire_id::SET_HOOK_FLASH_DETECT,
5653 Self::StartTone {
5654 tone,
5655 direction,
5656 line_instance,
5657 call_reference,
5658 } => {
5659 p = encode(
5660 wire_id::START_TONE,
5661 &WireToneLineCall {
5662 tone: tone.wire_value(),
5663 direction: direction.wire_value(),
5664 line_instance: *line_instance,
5665 call_reference: *call_reference,
5666 },
5667 )?;
5668 wire_id::START_TONE
5669 }
5670 Self::StopTone {
5671 line_instance,
5672 call_reference,
5673 } => {
5674 p = match protocol.wire() {
5675 12.. => encode(
5676 wire_id::STOP_TONE,
5677 &WireStopToneV12 {
5678 line_instance: *line_instance,
5679 call_reference: *call_reference,
5680 tone: 0,
5681 },
5682 ),
5683 _ => encode(
5684 wire_id::STOP_TONE,
5685 &WireLineCall {
5686 line_instance: *line_instance,
5687 call_reference: *call_reference,
5688 },
5689 ),
5690 }?;
5691 wire_id::STOP_TONE
5692 }
5693 Self::StartMulticastMediaReception(message) => {
5694 p = encode_start_multicast_reception(message, protocol)?;
5695 wire_id::START_MULTICAST_MEDIA_RECEPTION
5696 }
5697 Self::StartMulticastMediaTransmission(message) => {
5698 p = encode_start_multicast_transmission(message, protocol)?;
5699 wire_id::START_MULTICAST_MEDIA_TRANSMISSION
5700 }
5701 Self::StopMulticastMediaReception {
5702 conference_id,
5703 passthrough_party_id,
5704 call_reference,
5705 }
5706 | Self::StopMulticastMediaTransmission {
5707 conference_id,
5708 passthrough_party_id,
5709 call_reference,
5710 } => {
5711 let message_id = if matches!(self, Self::StopMulticastMediaReception { .. }) {
5712 wire_id::STOP_MULTICAST_MEDIA_RECEPTION
5713 } else {
5714 wire_id::STOP_MULTICAST_MEDIA_TRANSMISSION
5715 };
5716 p = encode(
5717 message_id,
5718 &WireStopMulticast {
5719 conference_id: conference_id.get(),
5720 passthrough_party_id: passthrough_party_id.get(),
5721 call_reference: call_reference.get(),
5722 },
5723 )?;
5724 message_id
5725 }
5726 Self::OpenReceiveChannel {
5727 call_reference,
5728 passthrough_party_id,
5729 packet_ms,
5730 codec,
5731 echo_cancellation,
5732 telephone_event_payload,
5733 source_address,
5734 source_port,
5735 encryption,
5736 wire,
5737 } => {
5738 p = encode_open_receive(
5739 *call_reference,
5740 *passthrough_party_id,
5741 OpenReceiveParameters {
5742 packet_ms: *packet_ms,
5743 codec: *codec,
5744 echo_cancellation: *echo_cancellation,
5745 telephone_event_payload: *telephone_event_payload,
5746 source_address: *source_address,
5747 source_port: *source_port,
5748 },
5749 encryption.as_ref(),
5750 wire.as_ref(),
5751 protocol,
5752 )?;
5753 wire_id::OPEN_RECEIVE_CHANNEL
5754 }
5755 Self::CloseReceiveChannel(control) => {
5756 p = encode(
5757 wire_id::CLOSE_RECEIVE_CHANNEL,
5758 &WireAudioStreamControl {
5759 conference_id: control.conference_id.get(),
5760 passthrough_party_id: control.passthrough_party_id.get(),
5761 call_reference: control.call_reference.get(),
5762 port_handling_flag: control.port_handling_flag,
5763 },
5764 )?;
5765 wire_id::CLOSE_RECEIVE_CHANNEL
5766 }
5767 Self::ConnectionStatisticsRequest {
5768 directory_number,
5769 call_reference,
5770 processing,
5771 } => {
5772 p = match protocol.wire() {
5773 19.. => encode_connection_statistics_request::<25, 3>(
5774 directory_number,
5775 *call_reference,
5776 *processing,
5777 ),
5778 _ => encode_connection_statistics_request::<24, 0>(
5779 directory_number,
5780 *call_reference,
5781 *processing,
5782 ),
5783 }?;
5784 wire_id::CONNECTION_STATISTICS_REQ
5785 }
5786 Self::StartMediaTransmission {
5787 call_reference,
5788 passthrough_party_id,
5789 endpoint,
5790 silence_suppression,
5791 traffic_class,
5792 encryption,
5793 wire,
5794 } => {
5795 p = encode_start_media(
5796 *call_reference,
5797 *passthrough_party_id,
5798 StartMediaParameters {
5799 endpoint: *endpoint,
5800 silence_suppression: *silence_suppression,
5801 traffic_class: *traffic_class,
5802 },
5803 encryption.as_ref(),
5804 wire.as_ref(),
5805 protocol,
5806 )?;
5807 wire_id::START_MEDIA_TRANSMISSION
5808 }
5809 Self::StopMediaTransmission(control) => {
5810 p = encode(
5811 wire_id::STOP_MEDIA_TRANSMISSION,
5812 &WireAudioStreamControl {
5813 conference_id: control.conference_id.get(),
5814 passthrough_party_id: control.passthrough_party_id.get(),
5815 call_reference: control.call_reference.get(),
5816 port_handling_flag: control.port_handling_flag,
5817 },
5818 )?;
5819 wire_id::STOP_MEDIA_TRANSMISSION
5820 }
5821 Self::StartMediaReception => wire_id::START_MEDIA_RECEPTION,
5822 Self::StopMediaReception {
5823 conference_id,
5824 passthrough_party_id,
5825 } => {
5826 p = encode(
5827 wire_id::STOP_MEDIA_RECEPTION,
5828 &WireStopMediaReception {
5829 conference_id: conference_id.get(),
5830 passthrough_party_id: passthrough_party_id.get(),
5831 },
5832 )?;
5833 wire_id::STOP_MEDIA_RECEPTION
5834 }
5835 Self::SetSpeakerMode(mode) => {
5836 p = encode(
5837 wire_id::SET_SPEAKER_MODE,
5838 &WireOneWord {
5839 value: mode.wire_value(),
5840 },
5841 )?;
5842 wire_id::SET_SPEAKER_MODE
5843 }
5844 Self::SetMicrophoneMode(mode) => {
5845 p = encode(
5846 wire_id::SET_MICROPHONE_MODE,
5847 &WireOneWord {
5848 value: mode.wire_value(),
5849 },
5850 )?;
5851 wire_id::SET_MICROPHONE_MODE
5852 }
5853 Self::Reset(reset) => {
5854 p = encode(
5855 wire_id::RESET,
5856 &WireOneWord {
5857 value: reset.wire_value(),
5858 },
5859 )?;
5860 wire_id::RESET
5861 }
5862 Self::DisplayText { text } => {
5863 p = encode(
5864 wire_id::DISPLAY_TEXT,
5865 &WireFixedText::<32>::new(wire_id::DISPLAY_TEXT, "display text", text)?,
5866 )?;
5867 wire_id::DISPLAY_TEXT
5868 }
5869 Self::ClearDisplay => wire_id::CLEAR_DISPLAY,
5870 Self::ForwardStatus {
5871 line_instance,
5872 forward_all,
5873 forward_busy,
5874 forward_no_answer,
5875 } => {
5876 p = match protocol.wire() {
5877 19.. => encode_forward_status::<25, 3>(
5878 *line_instance,
5879 forward_all.as_deref(),
5880 forward_busy.as_deref(),
5881 forward_no_answer.as_deref(),
5882 ),
5883 _ => encode_forward_status::<24, 0>(
5884 *line_instance,
5885 forward_all.as_deref(),
5886 forward_busy.as_deref(),
5887 forward_no_answer.as_deref(),
5888 ),
5889 }?;
5890 wire_id::FORWARD_STAT
5891 }
5892 Self::SpeedDialStatus {
5893 instance,
5894 number,
5895 display_name,
5896 } => {
5897 if session.uses_dynamic_speed_dial_status() {
5898 p = encode_dynamic_speed_dial_status(
5899 *instance,
5900 number,
5901 display_name,
5902 legacy_code_page,
5903 )?;
5904 wire_id::SPEED_DIAL_STAT_DYNAMIC
5905 } else {
5906 p = encode(
5907 wire_id::SPEED_DIAL_STAT,
5908 &WireSpeedDialStatus {
5909 instance: *instance,
5910 number: WireFixedText::new(wire_id::SPEED_DIAL_STAT, "number", number)?,
5911 display_name: WireFixedText::new_station(
5912 wire_id::SPEED_DIAL_STAT,
5913 "display name",
5914 display_name,
5915 legacy_code_page,
5916 )?,
5917 },
5918 )?;
5919 wire_id::SPEED_DIAL_STAT
5920 }
5921 }
5922 Self::DialedNumber {
5923 number,
5924 line_instance,
5925 call_reference,
5926 } => {
5927 p = match protocol.wire() {
5928 19.. => encode_dialed_number::<25, 3>(number, *line_instance, *call_reference),
5929 _ => encode_dialed_number::<24, 0>(number, *line_instance, *call_reference),
5930 }?;
5931 wire_id::DIALED_NUMBER
5932 }
5933 Self::StartMediaFailureDetection(detection) => {
5934 p = encode(
5935 wire_id::START_MEDIA_FAILURE_DETECTION,
5936 &WireMediaFailureDetection {
5937 conference_id: detection.conference_id.get(),
5938 passthrough_party_id: detection.passthrough_party_id,
5939 packet_millis: detection.packet_millis,
5940 codec: detection.codec.wire_value(),
5941 echo_cancellation: detection.echo_cancellation.wire_value(),
5942 codec_qualifier: detection.codec_qualifier,
5943 call_reference: detection.call_reference.get(),
5944 },
5945 )?;
5946 wire_id::START_MEDIA_FAILURE_DETECTION
5947 }
5948 Self::OpenMultimediaChannel(message) => {
5949 p = encode_open_multimedia(message, protocol)?;
5950 wire_id::OPEN_MULTIMEDIA_CHANNEL
5951 }
5952 Self::StartMultimediaTransmission(message) => {
5953 p = encode_start_multimedia(message, protocol)?;
5954 wire_id::START_MULTIMEDIA_TRANSMISSION
5955 }
5956 Self::MiscellaneousCommand(message) => {
5957 p = encode_miscellaneous_command(message)?;
5958 wire_id::MISCELLANEOUS_COMMAND
5959 }
5960 Self::UserToDeviceData(data) => {
5961 p = encode_user_data(data, wire_id::USER_TO_DEVICE_DATA)?;
5962 wire_id::USER_TO_DEVICE_DATA
5963 }
5964 Self::UserToDeviceDataV1(data) => {
5965 p = encode_user_data_v1(data, wire_id::USER_TO_DEVICE_DATA_V1)?;
5966 wire_id::USER_TO_DEVICE_DATA_V1
5967 }
5968 Self::SubscribeDtmfPayloadRequest(request) => {
5969 p = encode(
5970 wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ,
5971 &dtmf_payload_request_to_wire(*request),
5972 )?;
5973 wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ
5974 }
5975 Self::SubscribeDtmfPayloadError(identity) => {
5976 p = encode(
5977 wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR,
5978 &dtmf_payload_identity_to_wire(*identity),
5979 )?;
5980 wire_id::SUBSCRIBE_DTMF_PAYLOAD_ERR
5981 }
5982 Self::UnsubscribeDtmfPayloadRequest(request) => {
5983 p = encode(
5984 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ,
5985 &dtmf_payload_request_to_wire(*request),
5986 )?;
5987 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ
5988 }
5989 Self::UnsubscribeDtmfPayloadError(identity) => {
5990 p = encode(
5991 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR,
5992 &dtmf_payload_identity_to_wire(*identity),
5993 )?;
5994 wire_id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR
5995 }
5996 Self::FeatureStatus {
5997 instance,
5998 button_type,
5999 label,
6000 state,
6001 } => {
6002 if session.uses_dynamic_feature_status() {
6003 p = encode(
6004 wire_id::FEATURE_STAT_DYNAMIC,
6005 &WireFeatureStatusDynamic {
6006 instance: *instance,
6007 button_type: button_type.wire_value(),
6008 state: *state,
6009 label: WireFixedText::new_station(
6010 wire_id::FEATURE_STAT_DYNAMIC,
6011 "feature label",
6012 label,
6013 legacy_code_page,
6014 )?,
6015 padding: [0; 3],
6016 },
6017 )?;
6018 wire_id::FEATURE_STAT_DYNAMIC
6019 } else {
6020 p = encode(
6021 wire_id::FEATURE_STAT,
6022 &WireFeatureStatus {
6023 instance: *instance,
6024 button_type: button_type.wire_value(),
6025 label: WireFixedText::new_station(
6026 wire_id::FEATURE_STAT,
6027 "feature label",
6028 label,
6029 legacy_code_page,
6030 )?,
6031 state: *state,
6032 },
6033 )?;
6034 wire_id::FEATURE_STAT
6035 }
6036 }
6037 Self::ServiceUrlStatus {
6038 index,
6039 url,
6040 label,
6041 extension_text,
6042 } => {
6043 match (protocol.wire(), extension_text.is_empty()) {
6044 (0..=18, false) => Err(CodecError::InvalidValue {
6045 message_id: wire_id::SERVICE_URL_STAT_DYNAMIC,
6046 field: "service URL extension for this protocol version",
6047 value: extension_text.len() as u64,
6048 }),
6049 _ => Ok(()),
6050 }?;
6051 if session.uses_dynamic_general_ui() {
6052 p = encode_dynamic_service_url_status(
6053 *index,
6054 url,
6055 label,
6056 extension_text,
6057 protocol,
6058 legacy_code_page,
6059 )?;
6060 wire_id::SERVICE_URL_STAT_DYNAMIC
6061 } else {
6062 p = encode(
6063 wire_id::SERVICE_URL_STAT,
6064 &WireServiceUrlStatus {
6065 index: *index,
6066 url: WireFixedText::new(wire_id::SERVICE_URL_STAT, "service URL", url)?,
6067 label: WireFixedText::new_station(
6068 wire_id::SERVICE_URL_STAT,
6069 "service label",
6070 label,
6071 legacy_code_page,
6072 )?,
6073 },
6074 )?;
6075 wire_id::SERVICE_URL_STAT
6076 }
6077 }
6078 Self::CallSelectStatus {
6079 status,
6080 call_reference,
6081 line_instance,
6082 } => {
6083 p = encode(
6084 wire_id::CALL_SELECT_STAT,
6085 &WireCallSelectStatus {
6086 status: *status,
6087 call_reference: *call_reference,
6088 line_instance: *line_instance,
6089 },
6090 )?;
6091 wire_id::CALL_SELECT_STAT
6092 }
6093 Self::PortRequest(request) => {
6094 let base = WirePortRequest {
6095 conference_id: request.conference_id.get(),
6096 call_reference: request.call_reference.get(),
6097 passthrough_party_id: request.passthrough_party_id.get(),
6098 transport: request.transport.wire_value(),
6099 };
6100 p = match protocol.wire() {
6101 20.. => encode(
6102 wire_id::PORT_REQUEST,
6103 &WirePortRequestV20 {
6104 base,
6105 address_type: request
6106 .address_type
6107 .ok_or(CodecError::InvalidValue {
6108 message_id: wire_id::PORT_REQUEST,
6109 field: "address type required from protocol 20",
6110 value: 0,
6111 })?
6112 .wire_value(),
6113 media_type: request
6114 .media_type
6115 .ok_or(CodecError::InvalidValue {
6116 message_id: wire_id::PORT_REQUEST,
6117 field: "media type required from protocol 20",
6118 value: 0,
6119 })?
6120 .wire_value(),
6121 },
6122 ),
6123 _ => encode(wire_id::PORT_REQUEST, &base),
6124 }?;
6125 wire_id::PORT_REQUEST
6126 }
6127 Self::PortClose(close) => {
6128 let base = WirePortClose {
6129 conference_id: close.conference_id.get(),
6130 call_reference: close.call_reference.get(),
6131 passthrough_party_id: close.passthrough_party_id.get(),
6132 };
6133 p = match protocol.wire() {
6134 20.. => encode(
6135 wire_id::PORT_CLOSE,
6136 &WirePortCloseV20 {
6137 base,
6138 media_type: close
6139 .media_type
6140 .ok_or(CodecError::InvalidValue {
6141 message_id: wire_id::PORT_CLOSE,
6142 field: "media type required from protocol 20",
6143 value: 0,
6144 })?
6145 .wire_value(),
6146 },
6147 ),
6148 _ => encode(wire_id::PORT_CLOSE, &base),
6149 }?;
6150 wire_id::PORT_CLOSE
6151 }
6152 Self::SubscriptionStatus {
6153 transaction_id,
6154 feature_id,
6155 timer_seconds,
6156 cause,
6157 } => {
6158 p = encode(
6159 wire_id::SUBSCRIPTION_STAT,
6160 &WireSubscriptionStatus {
6161 transaction_id: *transaction_id,
6162 feature_id: *feature_id,
6163 timer_seconds: *timer_seconds,
6164 cause: cause.wire_value(),
6165 },
6166 )?;
6167 wire_id::SUBSCRIPTION_STAT
6168 }
6169 Self::Notification {
6170 transaction_id,
6171 feature_id,
6172 status,
6173 text,
6174 } => {
6175 p = encode(
6176 wire_id::NOTIFICATION,
6177 &WireNotification {
6178 transaction_id: *transaction_id,
6179 feature_id: *feature_id,
6180 status: status.wire_value(),
6181 text: WireFixedText::new(wire_id::NOTIFICATION, "notification", text)?,
6182 },
6183 )?;
6184 wire_id::NOTIFICATION
6185 }
6186 Self::CallHistoryDisposition {
6187 disposition,
6188 line_instance,
6189 call_reference,
6190 } => {
6191 p = encode(
6192 wire_id::CALL_HISTORY_DISPOSITION,
6193 &WireCallHistoryDisposition {
6194 disposition: disposition.wire_value(),
6195 line_instance: *line_instance,
6196 call_reference: *call_reference,
6197 },
6198 )?;
6199 wire_id::CALL_HISTORY_DISPOSITION
6200 }
6201 Self::CallCountResponse(response) => {
6202 if response.line_data.len() > CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES {
6203 return Err(CodecError::CountTooLarge {
6204 message_id: wire_id::CALL_COUNT_RES,
6205 field: "call-count line data",
6206 count: response.line_data.len(),
6207 maximum: CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES,
6208 });
6209 }
6210 let mut line_data =
6211 [WireCallCountLineData::default(); CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES];
6212 for (wire, entry) in line_data.iter_mut().zip(&response.line_data) {
6213 *wire = WireCallCountLineData {
6214 max_calls: entry.max_calls,
6215 busy_trigger: entry.busy_trigger,
6216 };
6217 }
6218 p = encode(
6219 wire_id::CALL_COUNT_RES,
6220 &WireCallCountResponse {
6221 total_configured_lines: response.total_configured_lines,
6222 starting_line_instance: response.starting_line_instance,
6223 line_data_entries: wire_count(
6224 wire_id::CALL_COUNT_RES,
6225 "call-count line data",
6226 response.line_data.len(),
6227 )?,
6228 line_data,
6229 },
6230 )?;
6231 wire_id::CALL_COUNT_RES
6232 }
6233 Self::RecordingStatus {
6234 call_reference,
6235 active,
6236 } => {
6237 p = encode(
6238 wire_id::RECORDING_STATUS,
6239 &WireRecordingStatus {
6240 call_reference: *call_reference,
6241 active: u32::from(*active),
6242 },
6243 )?;
6244 wire_id::RECORDING_STATUS
6245 }
6246 Self::KnownOpaque(message) => {
6247 ensure_preserve_only(message.id)?;
6248 return Ok((
6249 message.id.wire_value(),
6250 message.payload.as_bytes().to_vec(),
6251 message.protocol_version,
6252 ));
6253 }
6254 Self::Unknown(message) => {
6255 return Ok((
6256 message.message_id,
6257 message.payload.clone(),
6258 message.protocol_version,
6259 ));
6260 }
6261 };
6262 pad_typed_payload(id, &mut p);
6263 Ok((id, p, protocol.wire()))
6264 }
6265}
6266
6267fn reject_non_station_route(
6268 message_id: u32,
6269 expected_route: MessageRoute,
6270 expected: &'static str,
6271) -> Result<(), CodecError> {
6272 if let Some(actual) = MessageId::from(message_id).route()
6273 && actual != expected_route
6274 {
6275 return Err(CodecError::UnexpectedRoute {
6276 message_id,
6277 actual,
6278 expected,
6279 });
6280 }
6281 Ok(())
6282}
6283
6284impl ControlMessage {
6285 pub fn decode(frame: Frame, protocol: ProtocolVersion) -> Result<Self, CodecError> {
6289 let message_id = MessageId::from(frame.message_id);
6290 let route = message_id.route().ok_or(CodecError::InvalidValue {
6291 message_id: frame.message_id,
6292 field: "known control message identifier",
6293 value: u64::from(frame.message_id),
6294 })?;
6295 if matches!(
6296 route,
6297 MessageRoute::StationToControl | MessageRoute::ControlToStation
6298 ) {
6299 return Err(CodecError::UnexpectedRoute {
6300 message_id: frame.message_id,
6301 actual: route,
6302 expected: "control/service-node or intra-control route",
6303 });
6304 }
6305
6306 let p = &frame.payload;
6307 match frame.message_id {
6308 wire_id::START_SESSION_TRANSMISSION | wire_id::STOP_SESSION_TRANSMISSION => {
6309 let message = decode_session_transmission(p, protocol, frame.message_id)?;
6310 if frame.message_id == wire_id::START_SESSION_TRANSMISSION {
6311 Ok(Self::StartSessionTransmission(message))
6312 } else {
6313 Ok(Self::StopSessionTransmission(message))
6314 }
6315 }
6316 wire_id::QOS_RESERVATION_NOTIFY => {
6317 let value: WireQosReservationNotify = decode(frame.message_id, p)?;
6318 Ok(Self::QosReservationNotify {
6319 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6320 direction: QosDirection::from(value.direction),
6321 })
6322 }
6323 wire_id::QOS_ERROR_NOTIFY => {
6324 let value: WireQosErrorNotify = decode(frame.message_id, p)?;
6325 Ok(Self::QosErrorNotify {
6326 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6327 direction: QosDirection::from(value.direction),
6328 error_code: QosErrorCode::from(value.error_code),
6329 failure_node: Ipv4Addr::from(value.failure_node),
6330 rsvp_error_code: RsvpErrorCode::from(value.rsvp_error_code),
6331 rsvp_error_subcode: value.rsvp_error_subcode,
6332 rsvp_error_flags: value.rsvp_error_flags,
6333 })
6334 }
6335 wire_id::QOS_LISTEN => {
6336 let value: WireQosListen = decode(frame.message_id, p)?;
6337 Ok(Self::QosListen {
6338 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6339 reservation_style: QosReservationStyle::from(value.reservation_style),
6340 maximum_retries: value.maximum_retries,
6341 retry_timer: value.retry_timer,
6342 confirmation_required: decode_bool_word(
6343 value.confirmation_required,
6344 frame.message_id,
6345 "QoS confirmation required",
6346 )?,
6347 preemption_priority: value.preemption_priority,
6348 defending_priority: value.defending_priority,
6349 traffic: qos_traffic(
6350 value.compression_type,
6351 value.average_bit_rate,
6352 value.burst_size,
6353 value.peak_rate,
6354 ),
6355 application: qos_application_from_wire(value.application)?,
6356 })
6357 }
6358 wire_id::QOS_PATH => {
6359 let value: WireQosPath = decode(frame.message_id, p)?;
6360 Ok(Self::QosPath {
6361 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6362 reservation_style: QosReservationStyle::from(value.reservation_style),
6363 maximum_retries: value.maximum_retries,
6364 retry_timer: value.retry_timer,
6365 preemption_priority: value.preemption_priority,
6366 defending_priority: value.defending_priority,
6367 traffic: qos_traffic(
6368 value.compression_type,
6369 value.average_bit_rate,
6370 value.burst_size,
6371 value.peak_rate,
6372 ),
6373 application: qos_application_from_wire(value.application)?,
6374 })
6375 }
6376 wire_id::QOS_TEARDOWN => {
6377 let value: WireQosReservationNotify = decode(frame.message_id, p)?;
6378 Ok(Self::QosTeardown {
6379 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6380 direction: QosDirection::from(value.direction),
6381 })
6382 }
6383 wire_id::UPDATE_DSCP => {
6384 let value: WireUpdateDscp = decode(frame.message_id, p)?;
6385 let dscp = u8::try_from(value.dscp).map_err(|_| CodecError::InvalidValue {
6386 message_id: frame.message_id,
6387 field: "DSCP",
6388 value: u64::from(value.dscp),
6389 })?;
6390 if dscp > 63 {
6391 return Err(CodecError::InvalidValue {
6392 message_id: frame.message_id,
6393 field: "DSCP",
6394 value: u64::from(dscp),
6395 });
6396 }
6397 Ok(Self::UpdateDscp {
6398 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6399 dscp,
6400 })
6401 }
6402 wire_id::QOS_MODIFY => {
6403 let value: WireQosModify = decode(frame.message_id, p)?;
6404 Ok(Self::QosModify {
6405 flow: qos_flow_from_wire(value.flow, frame.message_id)?,
6406 direction: QosDirection::from(value.direction),
6407 traffic: qos_traffic(
6408 value.compression_type,
6409 value.average_bit_rate,
6410 value.burst_size,
6411 value.peak_rate,
6412 ),
6413 application: qos_application_from_wire(value.application)?,
6414 })
6415 }
6416 wire_id::MWI_NOTIFICATION => {
6417 let value: WireMessageWaitingNotification = decode(frame.message_id, p)?;
6418 validate_zero_payload(&value.alignment, frame.message_id, 2)?;
6419 Ok(Self::MessageWaitingNotification(
6420 MessageWaitingNotification {
6421 target_number: value.target_number.text()?,
6422 control_number: value.control_number.text()?,
6423 messages_waiting: decode_bool_word(
6424 value.messages_waiting,
6425 frame.message_id,
6426 "messages waiting",
6427 )?,
6428 total_voicemail: MessageWaitingCounts {
6429 new: value.total_voicemail_new,
6430 old: value.total_voicemail_old,
6431 },
6432 priority_voicemail: MessageWaitingCounts {
6433 new: value.priority_voicemail_new,
6434 old: value.priority_voicemail_old,
6435 },
6436 total_fax: MessageWaitingCounts {
6437 new: value.total_fax_new,
6438 old: value.total_fax_old,
6439 },
6440 priority_fax: MessageWaitingCounts {
6441 new: value.priority_fax_new,
6442 old: value.priority_fax_old,
6443 },
6444 },
6445 ))
6446 }
6447 wire_id::MWI_RESPONSE => {
6448 let value: WireMessageWaitingResponse = decode(frame.message_id, p)?;
6449 validate_zero_payload(&value.alignment, frame.message_id, 3)?;
6450 Ok(Self::MessageWaitingResponse {
6451 target_number: value.target_number.text()?,
6452 result: MessageWaitingResult::from(value.result),
6453 })
6454 }
6455 wire_id::MEDIA_RESOURCE_NOTIFICATION
6456 | wire_id::PORT_RESPONSE
6457 | wire_id::CREATE_CONFERENCE_RES
6458 | wire_id::DELETE_CONFERENCE_RES
6459 | wire_id::MODIFY_CONFERENCE_RES
6460 | wire_id::ADD_PARTICIPANT_RES
6461 | wire_id::AUDIT_CONFERENCE_RES
6462 | wire_id::AUDIT_PARTICIPANT_RES => Self::from_client_message(
6463 ClientMessage::decode_using_protocol(frame, protocol.wire())?,
6464 ),
6465 wire_id::CLEAR_CONFERENCE
6466 | wire_id::START_ANNOUNCEMENT
6467 | wire_id::STOP_ANNOUNCEMENT
6468 | wire_id::ANNOUNCEMENT_FINISH
6469 | wire_id::CREATE_CONFERENCE_REQ
6470 | wire_id::DELETE_CONFERENCE_REQ
6471 | wire_id::MODIFY_CONFERENCE_REQ
6472 | wire_id::ADD_PARTICIPANT_REQ
6473 | wire_id::DROP_PARTICIPANT_REQ
6474 | wire_id::AUDIT_CONFERENCE_REQ
6475 | wire_id::AUDIT_PARTICIPANT_REQ
6476 | wire_id::CHANGE_PARTICIPANT_REQ => {
6477 Self::from_server_message(ServerMessage::decode_unchecked(frame, protocol)?)
6478 }
6479 _ => preserve_known_message(frame, message_id).map(Self::KnownOpaque),
6480 }
6481 }
6482
6483 fn from_client_message(message: ClientMessage) -> Result<Self, CodecError> {
6484 Ok(match message {
6485 ClientMessage::MediaResourceNotification(value) => {
6486 Self::MediaResourceNotification(value)
6487 }
6488 ClientMessage::PortResponse(value) => Self::PortResponse(value),
6489 ClientMessage::CreateConferenceResponse(value) => Self::CreateConferenceResponse(value),
6490 ClientMessage::DeleteConferenceResponse {
6491 conference_id,
6492 result,
6493 } => Self::DeleteConferenceResponse {
6494 conference_id,
6495 result,
6496 },
6497 ClientMessage::ModifyConferenceResponse(value) => Self::ModifyConferenceResponse(value),
6498 ClientMessage::AddParticipantResponse(value) => Self::AddParticipantResponse(value),
6499 ClientMessage::AuditConferenceResponse(value) => Self::AuditConferenceResponse(value),
6500 ClientMessage::AuditParticipantResponse(value) => Self::AuditParticipantResponse(value),
6501 _ => {
6502 return Err(CodecError::InvalidValue {
6503 message_id: 0,
6504 field: "control message decoded through station codec",
6505 value: 0,
6506 });
6507 }
6508 })
6509 }
6510
6511 fn from_server_message(message: ServerMessage) -> Result<Self, CodecError> {
6512 Ok(match message {
6513 ServerMessage::ClearConference {
6514 conference_id,
6515 service_number,
6516 } => Self::ClearConference {
6517 conference_id,
6518 service_number,
6519 },
6520 ServerMessage::CreateConferenceRequest(value) => Self::CreateConferenceRequest(value),
6521 ServerMessage::DeleteConferenceRequest { conference_id } => {
6522 Self::DeleteConferenceRequest { conference_id }
6523 }
6524 ServerMessage::ModifyConferenceRequest(value) => Self::ModifyConferenceRequest(value),
6525 ServerMessage::AddParticipantRequest(value) => Self::AddParticipantRequest(value),
6526 ServerMessage::DropParticipantRequest {
6527 conference_id,
6528 call_reference,
6529 } => Self::DropParticipantRequest {
6530 conference_id,
6531 call_reference,
6532 },
6533 ServerMessage::AuditConferenceRequest => Self::AuditConferenceRequest,
6534 ServerMessage::AuditParticipantRequest { conference_id } => {
6535 Self::AuditParticipantRequest { conference_id }
6536 }
6537 ServerMessage::ChangeParticipantRequest(value) => Self::ChangeParticipantRequest(value),
6538 ServerMessage::StartAnnouncement {
6539 announcements,
6540 end_of_ack,
6541 conference_id,
6542 matrix_conference_party_ids,
6543 hearing_conference_party_mask,
6544 play_mode,
6545 } => Self::StartAnnouncement {
6546 announcements,
6547 end_of_ack: EndOfAnnouncementAck::from(end_of_ack),
6548 conference_id,
6549 matrix_conference_party_ids,
6550 hearing_conference_party_mask,
6551 play_mode: AnnouncementPlayMode::from(play_mode),
6552 },
6553 ServerMessage::StopAnnouncement { conference_id } => {
6554 Self::StopAnnouncement { conference_id }
6555 }
6556 ServerMessage::AnnouncementFinish {
6557 conference_id,
6558 play_status,
6559 } => Self::AnnouncementFinish {
6560 conference_id,
6561 play_status: AnnouncementPlayStatus::from(play_status),
6562 },
6563 _ => {
6564 return Err(CodecError::InvalidValue {
6565 message_id: 0,
6566 field: "control message decoded through station codec",
6567 value: 0,
6568 });
6569 }
6570 })
6571 }
6572
6573 pub fn encode(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
6578 let (message_id, payload, protocol_version) = match self {
6579 Self::StartSessionTransmission(message) | Self::StopSessionTransmission(message) => {
6580 let message_id = if matches!(self, Self::StartSessionTransmission(_)) {
6581 wire_id::START_SESSION_TRANSMISSION
6582 } else {
6583 wire_id::STOP_SESSION_TRANSMISSION
6584 };
6585 (
6586 message_id,
6587 encode_session_transmission(*message, protocol, message_id)?,
6588 protocol.wire(),
6589 )
6590 }
6591 Self::QosReservationNotify { flow, direction } => (
6592 wire_id::QOS_RESERVATION_NOTIFY,
6593 encode(
6594 wire_id::QOS_RESERVATION_NOTIFY,
6595 &WireQosReservationNotify {
6596 flow: qos_flow_to_wire(*flow),
6597 direction: direction.wire_value(),
6598 },
6599 )?,
6600 protocol.wire(),
6601 ),
6602 Self::QosErrorNotify {
6603 flow,
6604 direction,
6605 error_code,
6606 failure_node,
6607 rsvp_error_code,
6608 rsvp_error_subcode,
6609 rsvp_error_flags,
6610 } => (
6611 wire_id::QOS_ERROR_NOTIFY,
6612 encode(
6613 wire_id::QOS_ERROR_NOTIFY,
6614 &WireQosErrorNotify {
6615 flow: qos_flow_to_wire(*flow),
6616 direction: direction.wire_value(),
6617 error_code: error_code.wire_value(),
6618 failure_node: u32::from(*failure_node),
6619 rsvp_error_code: rsvp_error_code.wire_value(),
6620 rsvp_error_subcode: *rsvp_error_subcode,
6621 rsvp_error_flags: *rsvp_error_flags,
6622 },
6623 )?,
6624 protocol.wire(),
6625 ),
6626 Self::QosListen {
6627 flow,
6628 reservation_style,
6629 maximum_retries,
6630 retry_timer,
6631 confirmation_required,
6632 preemption_priority,
6633 defending_priority,
6634 traffic,
6635 application,
6636 } => (
6637 wire_id::QOS_LISTEN,
6638 encode(
6639 wire_id::QOS_LISTEN,
6640 &WireQosListen {
6641 flow: qos_flow_to_wire(*flow),
6642 reservation_style: reservation_style.wire_value(),
6643 maximum_retries: *maximum_retries,
6644 retry_timer: *retry_timer,
6645 confirmation_required: u32::from(*confirmation_required),
6646 preemption_priority: *preemption_priority,
6647 defending_priority: *defending_priority,
6648 compression_type: traffic.codec.wire_value(),
6649 average_bit_rate: traffic.average_bit_rate,
6650 burst_size: traffic.burst_size,
6651 peak_rate: traffic.peak_rate,
6652 application: qos_application_to_wire(wire_id::QOS_LISTEN, application)?,
6653 },
6654 )?,
6655 protocol.wire(),
6656 ),
6657 Self::QosPath {
6658 flow,
6659 reservation_style,
6660 maximum_retries,
6661 retry_timer,
6662 preemption_priority,
6663 defending_priority,
6664 traffic,
6665 application,
6666 } => (
6667 wire_id::QOS_PATH,
6668 encode(
6669 wire_id::QOS_PATH,
6670 &WireQosPath {
6671 flow: qos_flow_to_wire(*flow),
6672 reservation_style: reservation_style.wire_value(),
6673 maximum_retries: *maximum_retries,
6674 retry_timer: *retry_timer,
6675 preemption_priority: *preemption_priority,
6676 defending_priority: *defending_priority,
6677 compression_type: traffic.codec.wire_value(),
6678 average_bit_rate: traffic.average_bit_rate,
6679 burst_size: traffic.burst_size,
6680 peak_rate: traffic.peak_rate,
6681 application: qos_application_to_wire(wire_id::QOS_PATH, application)?,
6682 },
6683 )?,
6684 protocol.wire(),
6685 ),
6686 Self::QosTeardown { flow, direction } => (
6687 wire_id::QOS_TEARDOWN,
6688 encode(
6689 wire_id::QOS_TEARDOWN,
6690 &WireQosReservationNotify {
6691 flow: qos_flow_to_wire(*flow),
6692 direction: direction.wire_value(),
6693 },
6694 )?,
6695 protocol.wire(),
6696 ),
6697 Self::UpdateDscp { flow, dscp } => {
6698 if *dscp > 63 {
6699 return Err(CodecError::InvalidValue {
6700 message_id: wire_id::UPDATE_DSCP,
6701 field: "DSCP",
6702 value: u64::from(*dscp),
6703 });
6704 }
6705 (
6706 wire_id::UPDATE_DSCP,
6707 encode(
6708 wire_id::UPDATE_DSCP,
6709 &WireUpdateDscp {
6710 flow: qos_flow_to_wire(*flow),
6711 dscp: u32::from(*dscp),
6712 },
6713 )?,
6714 protocol.wire(),
6715 )
6716 }
6717 Self::QosModify {
6718 flow,
6719 direction,
6720 traffic,
6721 application,
6722 } => (
6723 wire_id::QOS_MODIFY,
6724 encode(
6725 wire_id::QOS_MODIFY,
6726 &WireQosModify {
6727 flow: qos_flow_to_wire(*flow),
6728 direction: direction.wire_value(),
6729 compression_type: traffic.codec.wire_value(),
6730 average_bit_rate: traffic.average_bit_rate,
6731 burst_size: traffic.burst_size,
6732 peak_rate: traffic.peak_rate,
6733 application: qos_application_to_wire(wire_id::QOS_MODIFY, application)?,
6734 },
6735 )?,
6736 protocol.wire(),
6737 ),
6738 Self::MessageWaitingNotification(value) => (
6739 wire_id::MWI_NOTIFICATION,
6740 encode(
6741 wire_id::MWI_NOTIFICATION,
6742 &WireMessageWaitingNotification {
6743 target_number: WireFixedText::new(
6744 wire_id::MWI_NOTIFICATION,
6745 "MWI target number",
6746 &value.target_number,
6747 )?,
6748 control_number: WireFixedText::new(
6749 wire_id::MWI_NOTIFICATION,
6750 "MWI control number",
6751 &value.control_number,
6752 )?,
6753 alignment: [0; 2],
6754 messages_waiting: u32::from(value.messages_waiting),
6755 total_voicemail_new: value.total_voicemail.new,
6756 total_voicemail_old: value.total_voicemail.old,
6757 priority_voicemail_new: value.priority_voicemail.new,
6758 priority_voicemail_old: value.priority_voicemail.old,
6759 total_fax_new: value.total_fax.new,
6760 total_fax_old: value.total_fax.old,
6761 priority_fax_new: value.priority_fax.new,
6762 priority_fax_old: value.priority_fax.old,
6763 },
6764 )?,
6765 protocol.wire(),
6766 ),
6767 Self::MessageWaitingResponse {
6768 target_number,
6769 result,
6770 } => (
6771 wire_id::MWI_RESPONSE,
6772 encode(
6773 wire_id::MWI_RESPONSE,
6774 &WireMessageWaitingResponse {
6775 target_number: WireFixedText::new(
6776 wire_id::MWI_RESPONSE,
6777 "MWI target number",
6778 target_number,
6779 )?,
6780 alignment: [0; 3],
6781 result: result.wire_value(),
6782 },
6783 )?,
6784 protocol.wire(),
6785 ),
6786 Self::KnownOpaque(message) => {
6787 ensure_preserve_only(message.id)?;
6788 return Frame::new(
6789 message.protocol_version,
6790 message.id.wire_value(),
6791 message.payload.as_bytes().to_vec(),
6792 )
6793 .encode();
6794 }
6795 other => return other.encode_via_existing(protocol),
6796 };
6797 Frame::new(protocol_version, message_id, payload).encode()
6798 }
6799
6800 fn encode_via_existing(&self, protocol: ProtocolVersion) -> Result<Vec<u8>, CodecError> {
6801 match self {
6802 Self::MediaResourceNotification(value) => {
6803 ClientMessage::MediaResourceNotification(value.clone()).encode_unchecked(protocol)
6804 }
6805 Self::PortResponse(value) => {
6806 ClientMessage::PortResponse(value.clone()).encode_unchecked(protocol)
6807 }
6808 Self::CreateConferenceResponse(value) => {
6809 ClientMessage::CreateConferenceResponse(value.clone()).encode_unchecked(protocol)
6810 }
6811 Self::DeleteConferenceResponse {
6812 conference_id,
6813 result,
6814 } => ClientMessage::DeleteConferenceResponse {
6815 conference_id: *conference_id,
6816 result: *result,
6817 }
6818 .encode_unchecked(protocol),
6819 Self::ModifyConferenceResponse(value) => {
6820 ClientMessage::ModifyConferenceResponse(value.clone()).encode_unchecked(protocol)
6821 }
6822 Self::AddParticipantResponse(value) => {
6823 ClientMessage::AddParticipantResponse(value.clone()).encode_unchecked(protocol)
6824 }
6825 Self::AuditConferenceResponse(value) => {
6826 ClientMessage::AuditConferenceResponse(value.clone()).encode_unchecked(protocol)
6827 }
6828 Self::AuditParticipantResponse(value) => {
6829 ClientMessage::AuditParticipantResponse(value.clone()).encode_unchecked(protocol)
6830 }
6831 Self::ClearConference {
6832 conference_id,
6833 service_number,
6834 } => ServerMessage::ClearConference {
6835 conference_id: *conference_id,
6836 service_number: *service_number,
6837 }
6838 .encode_unchecked(protocol),
6839 Self::CreateConferenceRequest(value) => {
6840 ServerMessage::CreateConferenceRequest(value.clone()).encode_unchecked(protocol)
6841 }
6842 Self::DeleteConferenceRequest { conference_id } => {
6843 ServerMessage::DeleteConferenceRequest {
6844 conference_id: *conference_id,
6845 }
6846 .encode_unchecked(protocol)
6847 }
6848 Self::ModifyConferenceRequest(value) => {
6849 ServerMessage::ModifyConferenceRequest(value.clone()).encode_unchecked(protocol)
6850 }
6851 Self::AddParticipantRequest(value) => {
6852 ServerMessage::AddParticipantRequest(value.clone()).encode_unchecked(protocol)
6853 }
6854 Self::DropParticipantRequest {
6855 conference_id,
6856 call_reference,
6857 } => ServerMessage::DropParticipantRequest {
6858 conference_id: *conference_id,
6859 call_reference: *call_reference,
6860 }
6861 .encode_unchecked(protocol),
6862 Self::AuditConferenceRequest => {
6863 ServerMessage::AuditConferenceRequest.encode_unchecked(protocol)
6864 }
6865 Self::AuditParticipantRequest { conference_id } => {
6866 ServerMessage::AuditParticipantRequest {
6867 conference_id: *conference_id,
6868 }
6869 .encode_unchecked(protocol)
6870 }
6871 Self::ChangeParticipantRequest(value) => {
6872 ServerMessage::ChangeParticipantRequest(value.clone()).encode_unchecked(protocol)
6873 }
6874 Self::StartAnnouncement {
6875 announcements,
6876 end_of_ack,
6877 conference_id,
6878 matrix_conference_party_ids,
6879 hearing_conference_party_mask,
6880 play_mode,
6881 } => ServerMessage::StartAnnouncement {
6882 announcements: announcements.clone(),
6883 end_of_ack: end_of_ack.wire_value(),
6884 conference_id: *conference_id,
6885 matrix_conference_party_ids: matrix_conference_party_ids.clone(),
6886 hearing_conference_party_mask: *hearing_conference_party_mask,
6887 play_mode: play_mode.wire_value(),
6888 }
6889 .encode_unchecked(protocol),
6890 Self::StopAnnouncement { conference_id } => ServerMessage::StopAnnouncement {
6891 conference_id: *conference_id,
6892 }
6893 .encode_unchecked(protocol),
6894 Self::AnnouncementFinish {
6895 conference_id,
6896 play_status,
6897 } => ServerMessage::AnnouncementFinish {
6898 conference_id: *conference_id,
6899 play_status: play_status.wire_value(),
6900 }
6901 .encode_unchecked(protocol),
6902 _ => unreachable!("directly encoded control message"),
6903 }
6904 }
6905}
6906
6907const fn call_state_precedence(state: CallState) -> u32 {
6908 match state {
6909 CallState::OffHook | CallState::Proceed | CallState::Connected | CallState::Transfer => 3,
6910 CallState::RingOut => 4,
6911 _ => 2,
6912 }
6913}
6914
6915#[derive(Clone, Copy, Debug)]
6916struct OpenReceiveParameters {
6917 packet_ms: u32,
6918 codec: Codec,
6919 echo_cancellation: EchoCancellation,
6920 telephone_event_payload: u8,
6921 source_address: IpAddr,
6922 source_port: u16,
6923}
6924
6925fn encode_open_receive(
6926 call: u32,
6927 party: u32,
6928 parameters: OpenReceiveParameters,
6929 encryption: Option<&MediaEncryption>,
6930 wire: Option<&OpenReceiveChannelWire>,
6931 protocol: ProtocolVersion,
6932) -> Result<Vec<u8>, CodecError> {
6933 let OpenReceiveParameters {
6934 packet_ms,
6935 codec,
6936 echo_cancellation,
6937 telephone_event_payload,
6938 source_address,
6939 source_port,
6940 } = parameters;
6941 let conference_id = wire.map_or(call, |value| value.conference_id);
6942 let g723_bitrate = wire.map_or(0, |value| value.g723_bitrate);
6943 let stream_passthrough_id = wire.map_or(0, |value| value.stream_passthrough_id);
6944 let associated_stream_id = wire.map_or(0, |value| value.associated_stream_id);
6945 let dtmf_type = wire.map_or(10, |value| value.dtmf_type);
6946 let mixing_mode = wire.map_or(0, |value| value.mixing_mode);
6947 let direction = wire.map_or(1, |value| value.direction);
6948 let requested_address_type = wire.map_or_else(
6949 || u32::from(matches!(source_address, IpAddr::V6(_))),
6950 |value| value.requested_address_type,
6951 );
6952 let encryption = WireEncryptionInfo::from_public(encryption);
6953 let base = WireOpenReceiveV11 {
6954 conference_id,
6955 passthrough_party_id: party,
6956 packet_millis: packet_ms,
6957 codec: codec.skinny(),
6958 vad: echo_cancellation.wire_value(),
6959 g723_bitrate,
6960 call_reference: call,
6961 encryption,
6962 stream_passthrough_id,
6963 associated_stream_id,
6964 rfc2833_payload: u32::from(telephone_event_payload),
6965 dtmf_type,
6966 };
6967 match protocol.wire() {
6968 21.. => encode(
6969 wire_id::OPEN_RECEIVE_CHANNEL,
6970 &WireOpenReceiveV21 {
6971 base: WireOpenReceiveV18 {
6972 base: WireOpenReceiveV17 {
6973 base: WireOpenReceiveAddressed {
6974 base,
6975 mixing_mode,
6976 direction,
6977 remote: WireExtendedAddress::from_ip(source_address),
6978 remote_port: u32::from(source_port),
6979 },
6980 requested_address_type,
6981 },
6982 audio_level_adjustment: wire.map_or(0, |value| value.audio_level_adjustment),
6983 },
6984 latent_capabilities: WireLatentCapabilities {
6985 bytes: wire.map_or([0; 36], |value| value.latent_capabilities),
6986 },
6987 },
6988 ),
6989 18..=20 => encode(
6990 wire_id::OPEN_RECEIVE_CHANNEL,
6991 &WireOpenReceiveV18 {
6992 base: WireOpenReceiveV17 {
6993 base: WireOpenReceiveAddressed {
6994 base,
6995 mixing_mode,
6996 direction,
6997 remote: WireExtendedAddress::from_ip(source_address),
6998 remote_port: u32::from(source_port),
6999 },
7000 requested_address_type,
7001 },
7002 audio_level_adjustment: wire.map_or(0, |value| value.audio_level_adjustment),
7003 },
7004 ),
7005 17 => encode(
7006 wire_id::OPEN_RECEIVE_CHANNEL,
7007 &WireOpenReceiveV17 {
7008 base: WireOpenReceiveAddressed {
7009 base,
7010 mixing_mode,
7011 direction,
7012 remote: WireExtendedAddress::from_ip(source_address),
7013 remote_port: u32::from(source_port),
7014 },
7015 requested_address_type,
7016 },
7017 ),
7018 version => {
7019 let remote = WireIpv4Address::from_ip(
7020 source_address,
7021 wire_id::OPEN_RECEIVE_CHANNEL,
7022 "IP address family for pre-v17 protocol",
7023 )?;
7024 match version {
7025 12.. => encode(
7026 wire_id::OPEN_RECEIVE_CHANNEL,
7027 &WireOpenReceiveV12 {
7028 base,
7029 mixing_mode,
7030 direction,
7031 remote,
7032 remote_port: u32::from(source_port),
7033 },
7034 ),
7035 _ => encode(wire_id::OPEN_RECEIVE_CHANNEL, &base),
7036 }
7037 }
7038 }
7039}
7040
7041struct StartMediaParameters {
7042 endpoint: MediaEndpoint,
7043 silence_suppression: SilenceSuppression,
7044 traffic_class: MediaTrafficClass,
7045}
7046
7047fn encode_start_media(
7048 call: u32,
7049 party: u32,
7050 parameters: StartMediaParameters,
7051 encryption: Option<&MediaEncryption>,
7052 wire: Option<&StartMediaTransmissionWire>,
7053 protocol: ProtocolVersion,
7054) -> Result<Vec<u8>, CodecError> {
7055 let StartMediaParameters {
7056 endpoint,
7057 silence_suppression,
7058 traffic_class,
7059 } = parameters;
7060 let conference_id = wire.map_or(call, |value| value.conference_id);
7061 let precedence = u32::from(traffic_class);
7062 let g723_bitrate = wire.map_or(0, |value| value.g723_bitrate);
7063 let stream_passthrough_id = wire.map_or(0, |value| value.stream_passthrough_id);
7064 let associated_stream_id = wire.map_or(0, |value| value.associated_stream_id);
7065 let dtmf_type = wire.map_or(10, |value| value.dtmf_type);
7066 let mixing_mode = wire.map_or(0, |value| value.mixing_mode);
7067 let direction = wire.map_or(1, |value| value.direction);
7068 let encryption = WireEncryptionInfo::from_public(encryption);
7069 match protocol.wire() {
7070 21.. => encode(
7071 wire_id::START_MEDIA_TRANSMISSION,
7072 &WireStartMediaV21 {
7073 base: WireStartMediaV17 {
7074 base: WireStartMediaBase {
7075 conference_id,
7076 passthrough_party_id: party,
7077 remote: WireExtendedAddress::from_ip(endpoint.address),
7078 remote_port: u32::from(endpoint.rtp_port),
7079 packet_millis: endpoint.packet_ms,
7080 codec: endpoint.codec.skinny(),
7081 precedence,
7082 silence_suppression: silence_suppression.wire_value(),
7083 max_frames_per_packet: endpoint.max_frames_per_packet,
7084 g723_bitrate,
7085 call_reference: call,
7086 encryption,
7087 stream_passthrough_id,
7088 associated_stream_id,
7089 rfc2833_payload: u32::from(endpoint.telephone_event_payload),
7090 dtmf_type,
7091 },
7092 mixing_mode,
7093 direction,
7094 },
7095 latent_capabilities: WireLatentCapabilities {
7096 bytes: wire.map_or([0; 36], |value| value.latent_capabilities),
7097 },
7098 },
7099 ),
7100 17..=20 => encode(
7101 wire_id::START_MEDIA_TRANSMISSION,
7102 &WireStartMediaV17 {
7103 base: WireStartMediaBase {
7104 conference_id,
7105 passthrough_party_id: party,
7106 remote: WireExtendedAddress::from_ip(endpoint.address),
7107 remote_port: u32::from(endpoint.rtp_port),
7108 packet_millis: endpoint.packet_ms,
7109 codec: endpoint.codec.skinny(),
7110 precedence,
7111 silence_suppression: silence_suppression.wire_value(),
7112 max_frames_per_packet: endpoint.max_frames_per_packet,
7113 g723_bitrate,
7114 call_reference: call,
7115 encryption,
7116 stream_passthrough_id,
7117 associated_stream_id,
7118 rfc2833_payload: u32::from(endpoint.telephone_event_payload),
7119 dtmf_type,
7120 },
7121 mixing_mode,
7122 direction,
7123 },
7124 ),
7125 version => {
7126 let base = WireStartMediaV11 {
7127 conference_id,
7128 passthrough_party_id: party,
7129 remote: WireIpv4Address::from_ip(
7130 endpoint.address,
7131 wire_id::START_MEDIA_TRANSMISSION,
7132 "IP address family for pre-v17 protocol",
7133 )?,
7134 remote_port: u32::from(endpoint.rtp_port),
7135 packet_millis: endpoint.packet_ms,
7136 codec: endpoint.codec.skinny(),
7137 precedence,
7138 silence_suppression: silence_suppression.wire_value(),
7139 max_frames_per_packet: endpoint.max_frames_per_packet,
7140 g723_bitrate,
7141 call_reference: call,
7142 encryption,
7143 stream_passthrough_id,
7144 associated_stream_id,
7145 rfc2833_payload: u32::from(endpoint.telephone_event_payload),
7146 dtmf_type,
7147 };
7148 match version {
7149 12.. => encode(
7150 wire_id::START_MEDIA_TRANSMISSION,
7151 &WireStartMediaV12 {
7152 base,
7153 mixing_mode,
7154 direction,
7155 },
7156 ),
7157 _ => encode(wire_id::START_MEDIA_TRANSMISSION, &base),
7158 }
7159 }
7160 }
7161}
7162
7163fn encode_start_multicast_reception(
7164 message: &MulticastMediaReception,
7165 protocol: ProtocolVersion,
7166) -> Result<Vec<u8>, CodecError> {
7167 match protocol.wire() {
7168 17.. => encode(
7169 wire_id::START_MULTICAST_MEDIA_RECEPTION,
7170 &WireStartMulticastReception::<WireExtendedAddress> {
7171 conference_id: message.conference_id.get(),
7172 passthrough_party_id: message.passthrough_party_id.get(),
7173 address: WireExtendedAddress::from_ip(message.address),
7174 port: u32::from(message.port),
7175 packet_millis: message.packet_millis,
7176 codec: message.codec.wire_value(),
7177 echo_cancellation: message.echo_cancellation.wire_value(),
7178 g723_bitrate: message.g723_bitrate.wire_value(),
7179 call_reference: message.call_reference.get(),
7180 },
7181 ),
7182 _ => encode(
7183 wire_id::START_MULTICAST_MEDIA_RECEPTION,
7184 &WireStartMulticastReception::<WireIpv4Address> {
7185 conference_id: message.conference_id.get(),
7186 passthrough_party_id: message.passthrough_party_id.get(),
7187 address: WireIpv4Address::from_ip(
7188 message.address,
7189 wire_id::START_MULTICAST_MEDIA_RECEPTION,
7190 "IP address family for pre-v17 protocol",
7191 )?,
7192 port: u32::from(message.port),
7193 packet_millis: message.packet_millis,
7194 codec: message.codec.wire_value(),
7195 echo_cancellation: message.echo_cancellation.wire_value(),
7196 g723_bitrate: message.g723_bitrate.wire_value(),
7197 call_reference: message.call_reference.get(),
7198 },
7199 ),
7200 }
7201}
7202
7203fn decode_start_multicast_reception(
7204 payload: &[u8],
7205 protocol: ProtocolVersion,
7206 message_id: u32,
7207) -> Result<ServerMessage, CodecError> {
7208 let (conference_id, party_id, address, port, packet_millis, codec, echo, g723, call_reference) =
7209 match protocol.wire() {
7210 17.. => {
7211 validate_exact_payload(payload, message_id, 52)?;
7212 let value: WireStartMulticastReception<WireExtendedAddress> =
7213 decode(message_id, payload)?;
7214 (
7215 value.conference_id,
7216 value.passthrough_party_id,
7217 value.address.to_ip(message_id)?,
7218 value.port,
7219 value.packet_millis,
7220 value.codec,
7221 value.echo_cancellation,
7222 value.g723_bitrate,
7223 value.call_reference,
7224 )
7225 }
7226 _ => {
7227 validate_exact_payload(payload, message_id, 36)?;
7228 let value: WireStartMulticastReception<WireIpv4Address> =
7229 decode(message_id, payload)?;
7230 (
7231 value.conference_id,
7232 value.passthrough_party_id,
7233 value.address.to_ip(message_id)?,
7234 value.port,
7235 value.packet_millis,
7236 value.codec,
7237 value.echo_cancellation,
7238 value.g723_bitrate,
7239 value.call_reference,
7240 )
7241 }
7242 };
7243 Ok(ServerMessage::StartMulticastMediaReception(
7244 MulticastMediaReception {
7245 conference_id: conference_id.into(),
7246 passthrough_party_id: party_id.into(),
7247 call_reference: call_reference.into(),
7248 address,
7249 port: decode_port(port, message_id, "multicast port")?,
7250 packet_millis,
7251 codec: Codec::from(codec),
7252 echo_cancellation: EchoCancellation::from(echo),
7253 g723_bitrate: G723BitRate::from(g723),
7254 },
7255 ))
7256}
7257
7258fn encode_start_multicast_transmission(
7259 message: &MulticastMediaTransmission,
7260 protocol: ProtocolVersion,
7261) -> Result<Vec<u8>, CodecError> {
7262 match protocol.wire() {
7263 17.. => encode(
7264 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
7265 &WireStartMulticastTransmission::<WireExtendedAddress> {
7266 conference_id: message.conference_id.get(),
7267 passthrough_party_id: message.passthrough_party_id.get(),
7268 address: WireExtendedAddress::from_ip(message.address),
7269 port: u32::from(message.port),
7270 packet_millis: message.packet_millis,
7271 codec: message.codec.wire_value(),
7272 precedence: message.precedence,
7273 silence_suppression: message.silence_suppression,
7274 max_frames_per_packet: message.max_frames_per_packet,
7275 g723_bitrate: message.g723_bitrate.wire_value(),
7276 call_reference: message.call_reference.get(),
7277 },
7278 ),
7279 _ => encode(
7280 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
7281 &WireStartMulticastTransmission::<WireIpv4Address> {
7282 conference_id: message.conference_id.get(),
7283 passthrough_party_id: message.passthrough_party_id.get(),
7284 address: WireIpv4Address::from_ip(
7285 message.address,
7286 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
7287 "IP address family for pre-v17 protocol",
7288 )?,
7289 port: u32::from(message.port),
7290 packet_millis: message.packet_millis,
7291 codec: message.codec.wire_value(),
7292 precedence: message.precedence,
7293 silence_suppression: message.silence_suppression,
7294 max_frames_per_packet: message.max_frames_per_packet,
7295 g723_bitrate: message.g723_bitrate.wire_value(),
7296 call_reference: message.call_reference.get(),
7297 },
7298 ),
7299 }
7300}
7301
7302fn decode_start_multicast_transmission(
7303 payload: &[u8],
7304 protocol: ProtocolVersion,
7305 message_id: u32,
7306) -> Result<ServerMessage, CodecError> {
7307 let (
7308 conference_id,
7309 party_id,
7310 address,
7311 port,
7312 packet_millis,
7313 codec,
7314 precedence,
7315 silence,
7316 max_frames,
7317 g723,
7318 call_reference,
7319 ) = match protocol.wire() {
7320 17.. => {
7321 validate_exact_payload(payload, message_id, 60)?;
7322 let value: WireStartMulticastTransmission<WireExtendedAddress> =
7323 decode(message_id, payload)?;
7324 (
7325 value.conference_id,
7326 value.passthrough_party_id,
7327 value.address.to_ip(message_id)?,
7328 value.port,
7329 value.packet_millis,
7330 value.codec,
7331 value.precedence,
7332 value.silence_suppression,
7333 value.max_frames_per_packet,
7334 value.g723_bitrate,
7335 value.call_reference,
7336 )
7337 }
7338 _ => {
7339 validate_exact_payload(payload, message_id, 44)?;
7340 let value: WireStartMulticastTransmission<WireIpv4Address> =
7341 decode(message_id, payload)?;
7342 (
7343 value.conference_id,
7344 value.passthrough_party_id,
7345 value.address.to_ip(message_id)?,
7346 value.port,
7347 value.packet_millis,
7348 value.codec,
7349 value.precedence,
7350 value.silence_suppression,
7351 value.max_frames_per_packet,
7352 value.g723_bitrate,
7353 value.call_reference,
7354 )
7355 }
7356 };
7357 Ok(ServerMessage::StartMulticastMediaTransmission(
7358 MulticastMediaTransmission {
7359 conference_id: conference_id.into(),
7360 passthrough_party_id: party_id.into(),
7361 call_reference: call_reference.into(),
7362 address,
7363 port: decode_port(port, message_id, "multicast port")?,
7364 packet_millis,
7365 codec: Codec::from(codec),
7366 precedence,
7367 silence_suppression: silence,
7368 max_frames_per_packet: max_frames,
7369 g723_bitrate: G723BitRate::from(g723),
7370 },
7371 ))
7372}
7373
7374fn decode_open_receive(
7375 payload: &[u8],
7376 protocol: ProtocolVersion,
7377 message_id: u32,
7378) -> Result<ServerMessage, CodecError> {
7379 let (
7380 call_reference,
7381 passthrough_party_id,
7382 packet_ms,
7383 codec,
7384 echo,
7385 rfc2833,
7386 source_address,
7387 source_port,
7388 encryption,
7389 wire,
7390 ) = match protocol.wire() {
7391 21.. => {
7392 let value: WireOpenReceiveV21 = decode(message_id, payload)?;
7393 (
7394 value.base.base.base.base.call_reference,
7395 value.base.base.base.base.passthrough_party_id,
7396 value.base.base.base.base.packet_millis,
7397 value.base.base.base.base.codec,
7398 value.base.base.base.base.vad,
7399 value.base.base.base.base.rfc2833_payload,
7400 value.base.base.base.remote.to_ip(message_id)?,
7401 decode_port(
7402 value.base.base.base.remote_port,
7403 message_id,
7404 "source RTP port",
7405 )?,
7406 value.base.base.base.base.encryption,
7407 OpenReceiveChannelWire {
7408 conference_id: value.base.base.base.base.conference_id,
7409 g723_bitrate: value.base.base.base.base.g723_bitrate,
7410 stream_passthrough_id: value.base.base.base.base.stream_passthrough_id,
7411 associated_stream_id: value.base.base.base.base.associated_stream_id,
7412 dtmf_type: value.base.base.base.base.dtmf_type,
7413 mixing_mode: value.base.base.base.mixing_mode,
7414 direction: value.base.base.base.direction,
7415 requested_address_type: value.base.base.requested_address_type,
7416 audio_level_adjustment: value.base.audio_level_adjustment,
7417 latent_capabilities: value.latent_capabilities.bytes,
7418 },
7419 )
7420 }
7421 18..=20 => {
7422 let value: WireOpenReceiveV18 = decode(message_id, payload)?;
7423 (
7424 value.base.base.base.call_reference,
7425 value.base.base.base.passthrough_party_id,
7426 value.base.base.base.packet_millis,
7427 value.base.base.base.codec,
7428 value.base.base.base.vad,
7429 value.base.base.base.rfc2833_payload,
7430 value.base.base.remote.to_ip(message_id)?,
7431 decode_port(value.base.base.remote_port, message_id, "source RTP port")?,
7432 value.base.base.base.encryption,
7433 OpenReceiveChannelWire {
7434 conference_id: value.base.base.base.conference_id,
7435 g723_bitrate: value.base.base.base.g723_bitrate,
7436 stream_passthrough_id: value.base.base.base.stream_passthrough_id,
7437 associated_stream_id: value.base.base.base.associated_stream_id,
7438 dtmf_type: value.base.base.base.dtmf_type,
7439 mixing_mode: value.base.base.mixing_mode,
7440 direction: value.base.base.direction,
7441 requested_address_type: value.base.requested_address_type,
7442 audio_level_adjustment: value.audio_level_adjustment,
7443 latent_capabilities: [0; 36],
7444 },
7445 )
7446 }
7447 17 => {
7448 let value: WireOpenReceiveV17 = decode(message_id, payload)?;
7449 (
7450 value.base.base.call_reference,
7451 value.base.base.passthrough_party_id,
7452 value.base.base.packet_millis,
7453 value.base.base.codec,
7454 value.base.base.vad,
7455 value.base.base.rfc2833_payload,
7456 value.base.remote.to_ip(message_id)?,
7457 decode_port(value.base.remote_port, message_id, "source RTP port")?,
7458 value.base.base.encryption,
7459 OpenReceiveChannelWire {
7460 conference_id: value.base.base.conference_id,
7461 g723_bitrate: value.base.base.g723_bitrate,
7462 stream_passthrough_id: value.base.base.stream_passthrough_id,
7463 associated_stream_id: value.base.base.associated_stream_id,
7464 dtmf_type: value.base.base.dtmf_type,
7465 mixing_mode: value.base.mixing_mode,
7466 direction: value.base.direction,
7467 requested_address_type: value.requested_address_type,
7468 audio_level_adjustment: 0,
7469 latent_capabilities: [0; 36],
7470 },
7471 )
7472 }
7473 12..=16 => {
7474 let value: WireOpenReceiveV12 = decode(message_id, payload)?;
7475 (
7476 value.base.call_reference,
7477 value.base.passthrough_party_id,
7478 value.base.packet_millis,
7479 value.base.codec,
7480 value.base.vad,
7481 value.base.rfc2833_payload,
7482 value.remote.to_ip(message_id)?,
7483 decode_port(value.remote_port, message_id, "source RTP port")?,
7484 value.base.encryption,
7485 OpenReceiveChannelWire {
7486 conference_id: value.base.conference_id,
7487 g723_bitrate: value.base.g723_bitrate,
7488 stream_passthrough_id: value.base.stream_passthrough_id,
7489 associated_stream_id: value.base.associated_stream_id,
7490 dtmf_type: value.base.dtmf_type,
7491 mixing_mode: value.mixing_mode,
7492 direction: value.direction,
7493 requested_address_type: 0,
7494 audio_level_adjustment: 0,
7495 latent_capabilities: [0; 36],
7496 },
7497 )
7498 }
7499 _ => {
7500 let value: WireOpenReceiveV11 = decode(message_id, payload)?;
7501 (
7502 value.call_reference,
7503 value.passthrough_party_id,
7504 value.packet_millis,
7505 value.codec,
7506 value.vad,
7507 value.rfc2833_payload,
7508 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
7509 0,
7510 value.encryption,
7511 OpenReceiveChannelWire {
7512 conference_id: value.conference_id,
7513 g723_bitrate: value.g723_bitrate,
7514 stream_passthrough_id: value.stream_passthrough_id,
7515 associated_stream_id: value.associated_stream_id,
7516 dtmf_type: value.dtmf_type,
7517 mixing_mode: 0,
7518 direction: 0,
7519 requested_address_type: 0,
7520 audio_level_adjustment: 0,
7521 latent_capabilities: [0; 36],
7522 },
7523 )
7524 }
7525 };
7526 let telephone_event_payload = u8::try_from(rfc2833).map_err(|_| CodecError::InvalidValue {
7527 message_id,
7528 field: "RFC2833 payload",
7529 value: u64::from(rfc2833),
7530 })?;
7531 Ok(ServerMessage::OpenReceiveChannel {
7532 call_reference,
7533 passthrough_party_id,
7534 packet_ms,
7535 codec: Codec::from(codec),
7536 echo_cancellation: EchoCancellation::from(echo),
7537 telephone_event_payload,
7538 source_address,
7539 source_port,
7540 encryption: encryption.to_public(message_id)?,
7541 wire: (wire != canonical_open_receive_wire(call_reference, source_address, protocol))
7542 .then_some(wire),
7543 })
7544}
7545
7546fn decode_start_media(
7547 payload: &[u8],
7548 protocol: ProtocolVersion,
7549 message_id: u32,
7550) -> Result<ServerMessage, CodecError> {
7551 let (
7552 call_reference,
7553 passthrough_party_id,
7554 address,
7555 port,
7556 packet_ms,
7557 codec,
7558 precedence,
7559 silence_suppression,
7560 max_frames_per_packet,
7561 rfc2833,
7562 encryption,
7563 wire,
7564 ) = match protocol.wire() {
7565 21.. => {
7566 let value: WireStartMediaV21 = decode(message_id, payload)?;
7567 (
7568 value.base.base.call_reference,
7569 value.base.base.passthrough_party_id,
7570 value.base.base.remote.to_ip(message_id)?,
7571 value.base.base.remote_port,
7572 value.base.base.packet_millis,
7573 value.base.base.codec,
7574 value.base.base.precedence,
7575 value.base.base.silence_suppression,
7576 value.base.base.max_frames_per_packet,
7577 value.base.base.rfc2833_payload,
7578 value.base.base.encryption,
7579 StartMediaTransmissionWire {
7580 conference_id: value.base.base.conference_id,
7581 g723_bitrate: value.base.base.g723_bitrate,
7582 stream_passthrough_id: value.base.base.stream_passthrough_id,
7583 associated_stream_id: value.base.base.associated_stream_id,
7584 dtmf_type: value.base.base.dtmf_type,
7585 mixing_mode: value.base.mixing_mode,
7586 direction: value.base.direction,
7587 latent_capabilities: value.latent_capabilities.bytes,
7588 },
7589 )
7590 }
7591 17..=20 => {
7592 let value: WireStartMediaV17 = decode(message_id, payload)?;
7593 (
7594 value.base.call_reference,
7595 value.base.passthrough_party_id,
7596 value.base.remote.to_ip(message_id)?,
7597 value.base.remote_port,
7598 value.base.packet_millis,
7599 value.base.codec,
7600 value.base.precedence,
7601 value.base.silence_suppression,
7602 value.base.max_frames_per_packet,
7603 value.base.rfc2833_payload,
7604 value.base.encryption,
7605 StartMediaTransmissionWire {
7606 conference_id: value.base.conference_id,
7607 g723_bitrate: value.base.g723_bitrate,
7608 stream_passthrough_id: value.base.stream_passthrough_id,
7609 associated_stream_id: value.base.associated_stream_id,
7610 dtmf_type: value.base.dtmf_type,
7611 mixing_mode: value.mixing_mode,
7612 direction: value.direction,
7613 latent_capabilities: [0; 36],
7614 },
7615 )
7616 }
7617 12..=16 => {
7618 let value: WireStartMediaV12 = decode(message_id, payload)?;
7619 (
7620 value.base.call_reference,
7621 value.base.passthrough_party_id,
7622 value.base.remote.to_ip(message_id)?,
7623 value.base.remote_port,
7624 value.base.packet_millis,
7625 value.base.codec,
7626 value.base.precedence,
7627 value.base.silence_suppression,
7628 value.base.max_frames_per_packet,
7629 value.base.rfc2833_payload,
7630 value.base.encryption,
7631 StartMediaTransmissionWire {
7632 conference_id: value.base.conference_id,
7633 g723_bitrate: value.base.g723_bitrate,
7634 stream_passthrough_id: value.base.stream_passthrough_id,
7635 associated_stream_id: value.base.associated_stream_id,
7636 dtmf_type: value.base.dtmf_type,
7637 mixing_mode: value.mixing_mode,
7638 direction: value.direction,
7639 latent_capabilities: [0; 36],
7640 },
7641 )
7642 }
7643 _ => {
7644 let value: WireStartMediaV11 = decode(message_id, payload)?;
7645 (
7646 value.call_reference,
7647 value.passthrough_party_id,
7648 value.remote.to_ip(message_id)?,
7649 value.remote_port,
7650 value.packet_millis,
7651 value.codec,
7652 value.precedence,
7653 value.silence_suppression,
7654 value.max_frames_per_packet,
7655 value.rfc2833_payload,
7656 value.encryption,
7657 StartMediaTransmissionWire {
7658 conference_id: value.conference_id,
7659 g723_bitrate: value.g723_bitrate,
7660 stream_passthrough_id: value.stream_passthrough_id,
7661 associated_stream_id: value.associated_stream_id,
7662 dtmf_type: value.dtmf_type,
7663 mixing_mode: 0,
7664 direction: 0,
7665 latent_capabilities: [0; 36],
7666 },
7667 )
7668 }
7669 };
7670 let rtp_port = decode_port(port, message_id, "RTP port")?;
7671 let telephone_event_payload = u8::try_from(rfc2833).map_err(|_| CodecError::InvalidValue {
7672 message_id,
7673 field: "RFC2833 payload",
7674 value: u64::from(rfc2833),
7675 })?;
7676 Ok(ServerMessage::StartMediaTransmission {
7677 call_reference,
7678 passthrough_party_id,
7679 endpoint: MediaEndpoint {
7680 address,
7681 rtp_port,
7682 rtcp_port: rtp_port.saturating_add(1),
7683 codec: Codec::from(codec),
7684 packet_ms,
7685 max_frames_per_packet,
7686 telephone_event_payload,
7687 },
7688 silence_suppression: SilenceSuppression::from(silence_suppression),
7689 traffic_class: MediaTrafficClass::from_wire(u8::try_from(precedence).map_err(|_| {
7690 CodecError::InvalidValue {
7691 message_id,
7692 field: "media traffic class",
7693 value: u64::from(precedence),
7694 }
7695 })?),
7696 encryption: encryption.to_public(message_id)?,
7697 wire: (wire != canonical_start_media_wire(call_reference, protocol)).then_some(wire),
7698 })
7699}
7700
7701#[cfg(test)]
7702mod tests {
7703 use super::catalog::MessageDirection;
7704 use super::values::SoftKey;
7705 use super::wire::{FrameDecoder, MAX_FRAME_SIZE};
7706 use super::*;
7707
7708 fn fixture(source: &str) -> Vec<u8> {
7709 source
7710 .split_whitespace()
7711 .map(|byte| u8::from_str_radix(byte, 16).expect("valid fixture byte"))
7712 .collect()
7713 }
7714
7715 fn deterministic_payload(message_id: u32, protocol: u32, length: usize) -> Vec<u8> {
7716 let mut state = u64::from(message_id)
7717 ^ (u64::from(protocol) << 32)
7718 ^ (length as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
7719 (0..length)
7720 .map(|_| {
7721 state ^= state << 13;
7722 state ^= state >> 7;
7723 state ^= state << 17;
7724 state as u8
7725 })
7726 .collect()
7727 }
7728
7729 fn fuzz_lengths() -> impl Iterator<Item = usize> {
7730 (0..=96).chain([127, 255, 511, 1024, MAX_FRAME_SIZE - 12])
7731 }
7732
7733 const fn test_rtp_payload_number(value: u32) -> RtpPayloadNumber {
7734 match RtpPayloadNumber::new(value) {
7735 Ok(value) => value,
7736 Err(_) => panic!("test RTP payload number is out of range"),
7737 }
7738 }
7739
7740 fn typed_video_payload(arm: MultimediaVideoCapabilityArm) -> MultimediaPayload {
7741 let payload_number = match arm.codec() {
7742 Codec::H261 => 31,
7743 Codec::H263 => 34,
7744 Codec::H263Plus => 96,
7745 Codec::H264 => 97,
7746 _ => unreachable!("typed video arms always have a modeled codec"),
7747 };
7748 MultimediaPayload::new(
7749 test_rtp_payload_number(payload_number),
7750 MultimediaVideoCapability::new(
7751 1_024,
7752 [
7753 MultimediaPictureFormat {
7754 format: VideoFormat::Cif4,
7755 minimum_picture_interval: 1,
7756 },
7757 MultimediaPictureFormat {
7758 format: VideoFormat::Cif,
7759 minimum_picture_interval: 2,
7760 },
7761 ],
7762 7,
7763 arm,
7764 )
7765 .unwrap(),
7766 )
7767 }
7768
7769 #[test]
7770 fn every_catalogued_client_decoder_is_panic_free_for_bounded_property_corpus() {
7771 let protocols = [
7772 ProtocolVersion::V3,
7773 ProtocolVersion::V8,
7774 ProtocolVersion::V17,
7775 ProtocolVersion::V22,
7776 ];
7777 let mut cases = 0_usize;
7778 for message_id in MessageId::ALL_KNOWN
7779 .iter()
7780 .copied()
7781 .filter(|id| id.direction() == Some(MessageDirection::DeviceToServer))
7782 {
7783 for protocol in protocols {
7784 for length in fuzz_lengths() {
7785 let frame = Frame::new(
7786 protocol.wire(),
7787 message_id.wire_value(),
7788 deterministic_payload(message_id.wire_value(), protocol.wire(), length),
7789 );
7790 let _ = ClientMessage::decode_with_version(frame, protocol);
7791 cases += 1;
7792 }
7793 }
7794 }
7795 assert!(
7796 cases > 20_000,
7797 "property corpus unexpectedly shrank: {cases}"
7798 );
7799 }
7800
7801 #[test]
7802 fn every_catalogued_server_encoder_round_trips_all_decodable_bounded_inputs() {
7803 let protocols = [
7804 ProtocolVersion::V3,
7805 ProtocolVersion::V8,
7806 ProtocolVersion::V17,
7807 ProtocolVersion::V22,
7808 ];
7809 let mut decoded = 0_usize;
7810 let mut encoded = 0_usize;
7811 for message_id in MessageId::ALL_KNOWN
7812 .iter()
7813 .copied()
7814 .filter(|id| id.direction() == Some(MessageDirection::ServerToDevice))
7815 {
7816 for protocol in protocols {
7817 for length in fuzz_lengths() {
7818 let frame = Frame::new(
7819 protocol.wire(),
7820 message_id.wire_value(),
7821 deterministic_payload(message_id.wire_value(), protocol.wire(), length),
7822 );
7823 let Ok(message) = ServerMessage::decode(frame, protocol) else {
7824 continue;
7825 };
7826 decoded += 1;
7827 let Ok(bytes) = message.encode(protocol) else {
7828 continue;
7829 };
7830 assert!(bytes.len() <= MAX_FRAME_SIZE);
7831 let frames = FrameDecoder::new().push(&bytes).unwrap();
7832 assert_eq!(frames.len(), 1);
7833 assert_eq!(
7834 ServerMessage::decode(frames.into_iter().next().unwrap(), protocol)
7835 .unwrap(),
7836 message
7837 );
7838 encoded += 1;
7839 }
7840 }
7841 }
7842 assert!(
7843 decoded > 1_000,
7844 "decodable encoder corpus unexpectedly shrank: {decoded}"
7845 );
7846 assert!(
7847 encoded > 1_000,
7848 "encodable property corpus unexpectedly shrank: {encoded}"
7849 );
7850 }
7851
7852 #[test]
7853 fn registration_preserves_both_reported_address_families() {
7854 let message = ClientMessage::Register(RegistrationMessage {
7855 device_id: DeviceId::new("SEP001122334455").unwrap(),
7856 reported_address: Some(Ipv4Addr::new(192, 0, 2, 10)),
7857 reported_ipv6_address: Some("2001:db8::10".parse().unwrap()),
7858 device_type: DeviceType::Cisco7962,
7859 advertised_protocol: Some(ProtocolVersion::V22.wire()),
7860 features: PhoneFeatures::empty(),
7861 firmware: "test-load".into(),
7862 configuration_version_stamp: BoundedBytes::default(),
7863 wire: Some(RegistrationWireDetails {
7864 layout: RegistrationWireLayout::default(),
7865 station_user_id: 17,
7866 station_instance: 2,
7867 max_streams: 5,
7868 active_streams: 1,
7869 mac_address_and_padding: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0, 0, 0, 0, 0, 0],
7870 max_conferences: 3,
7871 active_conferences: 1,
7872 ipv4_address_scope: 3,
7873 max_lines: 6,
7874 ipv6_address_scope: 2,
7875 }),
7876 });
7877 let bytes = message.encode(ProtocolVersion::V22).unwrap();
7878 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
7879 assert_eq!(
7880 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
7881 message
7882 );
7883 }
7884
7885 #[test]
7886 fn registration_preserves_every_complete_canonical_prefix() {
7887 let mut payload = [0_u8; REGISTER_CANONICAL_BYTES];
7888 let device_id = b"SEP001122334455";
7889 payload[..device_id.len()].copy_from_slice(device_id);
7890 payload[16..20].copy_from_slice(&17_u32.to_le_bytes());
7891 payload[20..24].copy_from_slice(&2_u32.to_le_bytes());
7892 payload[24..28].copy_from_slice(&[192, 0, 2, 25]);
7893 payload[28..32].copy_from_slice(&DeviceType::Cisco7925.wire_value().to_le_bytes());
7894 payload[32..36].copy_from_slice(&5_u32.to_le_bytes());
7895 payload[36..40].copy_from_slice(&1_u32.to_le_bytes());
7896 payload[40..44].copy_from_slice(&ProtocolVersion::V11.wire().to_le_bytes());
7897 payload[44..48].copy_from_slice(&3_u32.to_le_bytes());
7898 payload[48..52].copy_from_slice(&1_u32.to_le_bytes());
7899 payload[52..64].copy_from_slice(&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 1, 2, 3, 4, 5, 6]);
7900 payload[64..68].copy_from_slice(&3_u32.to_le_bytes());
7901 payload[68..72].copy_from_slice(&6_u32.to_le_bytes());
7902 payload[72..88].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
7903 payload[88..92].copy_from_slice(&2_u32.to_le_bytes());
7904 payload[92..101].copy_from_slice(b"SCCP-test");
7905
7906 for prefix_bytes in [36, 40, 44, 48, 52, 64, 68, 72, 88, 92, 124] {
7907 let expected_payload = payload[..prefix_bytes].to_vec();
7908 let message = ClientMessage::decode_with_version(
7909 Frame::new(0, wire_id::REGISTER, expected_payload.clone()),
7910 ProtocolVersion::V22,
7911 )
7912 .unwrap();
7913 let ClientMessage::Register(registration) = &message else {
7914 unreachable!("registration frame decoded as another message")
7915 };
7916 assert_eq!(registration.device_type, DeviceType::Cisco7925);
7917 assert_eq!(
7918 registration.advertised_protocol,
7919 (prefix_bytes >= 44).then_some(ProtocolVersion::V11.wire())
7920 );
7921 assert_eq!(
7922 registration.reported_ipv6_address,
7923 (prefix_bytes >= 88).then_some(Ipv6Addr::LOCALHOST)
7924 );
7925 assert_eq!(
7926 registration.firmware,
7927 if prefix_bytes == REGISTER_CANONICAL_BYTES {
7928 "SCCP-test"
7929 } else {
7930 ""
7931 }
7932 );
7933 assert!(matches!(
7934 registration.wire,
7935 Some(RegistrationWireDetails {
7936 layout: RegistrationWireLayout::Canonical {
7937 prefix_bytes: actual
7938 },
7939 ..
7940 }) if usize::from(actual) == prefix_bytes
7941 ));
7942
7943 let encoded = message.encode(ProtocolVersion::V22).unwrap();
7944 let encoded_frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
7945 assert_eq!(encoded_frame.payload, expected_payload);
7946 }
7947 }
7948
7949 #[test]
7950 fn registration_preserves_alternate_32_byte_layout() {
7951 let message = ClientMessage::Register(RegistrationMessage {
7952 device_id: DeviceId::new("SEP001122334455").unwrap(),
7953 reported_address: None,
7954 reported_ipv6_address: None,
7955 device_type: DeviceType::Cisco7920,
7956 advertised_protocol: Some(ProtocolVersion::V3.wire()),
7957 features: PhoneFeatures::empty(),
7958 firmware: String::new(),
7959 configuration_version_stamp: BoundedBytes::default(),
7960 wire: Some(RegistrationWireDetails {
7961 layout: RegistrationWireLayout::Alternate32,
7962 station_user_id: 17,
7963 station_instance: 2,
7964 max_streams: 0,
7965 active_streams: 0,
7966 mac_address_and_padding: [0; 12],
7967 max_conferences: 0,
7968 active_conferences: 0,
7969 ipv4_address_scope: 0,
7970 max_lines: 0,
7971 ipv6_address_scope: 0,
7972 }),
7973 });
7974 let bytes = message.encode(ProtocolVersion::V22).unwrap();
7975 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
7976 assert_eq!(frame.payload.len(), REGISTER_ALTERNATE_BYTES);
7977 assert_eq!(
7978 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
7979 message
7980 );
7981 }
7982
7983 #[test]
7984 fn short_zero_protocol_registration_uses_legacy_fallback_and_round_trips() {
7985 for payload_bytes in [32, 44, 48] {
7986 let mut payload = vec![0_u8; payload_bytes];
7987 let device_id = b"SEP001122334455";
7988 payload[..device_id.len()].copy_from_slice(device_id);
7989 payload[28..32].copy_from_slice(&DeviceType::Cisco7925.wire_value().to_le_bytes());
7990
7991 let message = ClientMessage::decode_with_version(
7992 Frame::new(0, wire_id::REGISTER, payload.clone()),
7993 ProtocolVersion::V22,
7994 )
7995 .unwrap();
7996 let ClientMessage::Register(registration) = &message else {
7997 unreachable!("registration frame decoded as another message")
7998 };
7999 assert_eq!(registration.advertised_protocol, None);
8000
8001 let encoded = message.encode(ProtocolVersion::V22).unwrap();
8002 let encoded_frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
8003 assert_eq!(encoded_frame.payload, payload);
8004 }
8005 }
8006
8007 #[test]
8008 fn registration_encoder_rejects_values_omitted_by_selected_prefix() {
8009 let mut message = ClientMessage::Register(RegistrationMessage {
8010 device_id: DeviceId::new("SEP001122334455").unwrap(),
8011 reported_address: Some(Ipv4Addr::LOCALHOST),
8012 reported_ipv6_address: None,
8013 device_type: DeviceType::Cisco7925,
8014 advertised_protocol: None,
8015 features: PhoneFeatures::empty(),
8016 firmware: String::new(),
8017 configuration_version_stamp: BoundedBytes::default(),
8018 wire: Some(RegistrationWireDetails {
8019 layout: RegistrationWireLayout::Canonical { prefix_bytes: 36 },
8020 station_user_id: 0,
8021 station_instance: 1,
8022 max_streams: 5,
8023 active_streams: 0,
8024 mac_address_and_padding: [0; 12],
8025 max_conferences: 0,
8026 active_conferences: 0,
8027 ipv4_address_scope: 0,
8028 max_lines: 0,
8029 ipv6_address_scope: 0,
8030 }),
8031 });
8032 assert!(message.encode(ProtocolVersion::V22).is_ok());
8033
8034 let ClientMessage::Register(registration) = &mut message else {
8035 unreachable!("test message is registration")
8036 };
8037 registration.wire.as_mut().unwrap().active_streams = 1;
8038 assert!(matches!(
8039 message.encode(ProtocolVersion::V22),
8040 Err(CodecError::InvalidValue {
8041 field: "active streams",
8042 ..
8043 })
8044 ));
8045 }
8046
8047 #[test]
8048 fn registration_preserves_every_bounded_configuration_suffix() {
8049 let base = ClientMessage::Register(RegistrationMessage {
8050 device_id: DeviceId::new("SEP001122334455").unwrap(),
8051 reported_address: None,
8052 reported_ipv6_address: None,
8053 device_type: DeviceType::Cisco7962,
8054 advertised_protocol: Some(ProtocolVersion::V22.wire()),
8055 features: PhoneFeatures::empty(),
8056 firmware: "test-load".into(),
8057 configuration_version_stamp: BoundedBytes::default(),
8058 wire: Some(RegistrationWireDetails {
8059 layout: RegistrationWireLayout::default(),
8060 station_user_id: 0,
8061 station_instance: 1,
8062 max_streams: 0,
8063 active_streams: 0,
8064 mac_address_and_padding: [0; 12],
8065 max_conferences: 0,
8066 active_conferences: 0,
8067 ipv4_address_scope: 0,
8068 max_lines: 0,
8069 ipv6_address_scope: 0,
8070 }),
8071 });
8072
8073 for length in 0..=48 {
8074 let mut message = base.clone();
8075 let ClientMessage::Register(registration) = &mut message else {
8076 unreachable!("test message is registration")
8077 };
8078 registration.configuration_version_stamp =
8079 BoundedBytes::try_from(vec![0xa5; length]).unwrap();
8080 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8081 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8082 assert_eq!(frame.payload.len(), 124 + length);
8083 assert_eq!(
8084 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
8085 message
8086 );
8087 }
8088 }
8089
8090 #[test]
8091 fn registration_rejects_unsupported_payload_lengths() {
8092 for invalid_length in [33, 35, 37, 56, 76, 96, 123] {
8093 assert!(matches!(
8094 ClientMessage::decode_with_version(
8095 Frame::new(0, wire_id::REGISTER, vec![0; invalid_length]),
8096 ProtocolVersion::V22,
8097 ),
8098 Err(CodecError::InvalidValue {
8099 field: "registration payload length",
8100 ..
8101 })
8102 ));
8103 }
8104 assert!(matches!(
8105 ClientMessage::decode_with_version(
8106 Frame::new(0, wire_id::REGISTER, vec![0; 31]),
8107 ProtocolVersion::V22,
8108 ),
8109 Err(CodecError::Truncated { needed: 32, .. })
8110 ));
8111 assert!(matches!(
8112 ClientMessage::decode_with_version(
8113 Frame::new(0, wire_id::REGISTER, vec![0; 173]),
8114 ProtocolVersion::V22,
8115 ),
8116 Err(CodecError::TrailingBytes { .. })
8117 ));
8118 }
8119
8120 #[test]
8121 fn alarm_preserves_both_supported_wire_lengths() {
8122 for parameters in [None, Some([0x1122_3344, 0xaabb_ccdd])] {
8123 let message = ClientMessage::Alarm {
8124 severity: AlarmSeverity::Warning,
8125 text: "TFTP load failed".into(),
8126 parameters,
8127 };
8128 let bytes = message.encode(ProtocolVersion::V17).unwrap();
8129 assert_eq!(bytes.len(), if parameters.is_some() { 104 } else { 96 });
8130 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8131 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8132 }
8133 }
8134
8135 #[test]
8136 fn capabilities_response_consumes_all_eighteen_fixed_slots() {
8137 let capabilities = (0_u32..12)
8138 .map(|index| MediaCapability {
8139 codec: if index.is_multiple_of(2) {
8140 Codec::Pcmu
8141 } else {
8142 Codec::Pcma
8143 },
8144 max_packet_ms: index + 1,
8145 codec_parameters: [index as u8; 8],
8146 })
8147 .collect::<Vec<_>>();
8148 let message = ClientMessage::CapabilitiesResponse(capabilities);
8149
8150 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8151 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8152
8153 assert_eq!(frame.payload.len(), 4 + 18 * 16);
8154 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8155 }
8156
8157 #[test]
8158 fn capabilities_response_selects_the_extended_fixed_reservoir() {
8159 let capabilities = (0_u32..20)
8160 .map(|index| MediaCapability {
8161 codec: Codec::Unknown(0x1000 + index),
8162 max_packet_ms: index + 1,
8163 codec_parameters: [index as u8; 8],
8164 })
8165 .collect::<Vec<_>>();
8166 let message = ClientMessage::CapabilitiesResponse(capabilities);
8167
8168 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8169 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8170
8171 assert_eq!(frame.payload.len(), 4 + 24 * 16);
8172 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8173 }
8174
8175 #[test]
8176 fn capabilities_response_accepts_exact_counted_prefixes() {
8177 let capabilities = vec![
8178 MediaCapability {
8179 codec: Codec::Pcmu,
8180 max_packet_ms: 2,
8181 codec_parameters: [1; 8],
8182 },
8183 MediaCapability {
8184 codec: Codec::Pcma,
8185 max_packet_ms: 3,
8186 codec_parameters: [2; 8],
8187 },
8188 ];
8189 let mut payload = 2_u32.to_le_bytes().to_vec();
8190 for capability in &capabilities {
8191 payload.extend_from_slice(
8192 &encode(
8193 wire_id::CAPABILITIES_RES,
8194 &WireMediaCapability {
8195 codec: capability.codec.wire_value(),
8196 max_frames_per_packet: capability.max_packet_ms,
8197 codec_parameters: capability.codec_parameters,
8198 },
8199 )
8200 .unwrap(),
8201 );
8202 }
8203 assert_eq!(payload.len(), 4 + 2 * 16);
8204 assert_eq!(
8205 ClientMessage::decode(Frame::new(
8206 ProtocolVersion::V22.wire(),
8207 wire_id::CAPABILITIES_RES,
8208 payload,
8209 ))
8210 .unwrap(),
8211 ClientMessage::CapabilitiesResponse(capabilities)
8212 );
8213 }
8214
8215 #[test]
8216 fn capabilities_response_rejects_incomplete_or_mismatched_counted_storage() {
8217 let mut partial = vec![0; 4 + 19 * 16];
8218 partial[..4].copy_from_slice(&1_u32.to_le_bytes());
8219 assert!(matches!(
8220 ClientMessage::decode(Frame::new(
8221 ProtocolVersion::V22.wire(),
8222 wire_id::CAPABILITIES_RES,
8223 partial,
8224 )),
8225 Err(CodecError::TrailingBytes { count: 288, .. })
8226 ));
8227
8228 let mut too_many = vec![0; 4 + 18 * 16];
8229 too_many[..4].copy_from_slice(&19_u32.to_le_bytes());
8230 assert!(matches!(
8231 ClientMessage::decode(Frame::new(
8232 ProtocolVersion::V22.wire(),
8233 wire_id::CAPABILITIES_RES,
8234 too_many,
8235 )),
8236 Err(CodecError::CountTooLarge {
8237 field: "audio capabilities",
8238 maximum: 18,
8239 ..
8240 })
8241 ));
8242 }
8243
8244 #[test]
8245 fn capabilities_response_discards_nonzero_inactive_reservoir_storage() {
8246 let mut payload = vec![0xa5; 4 + 24 * 16];
8247 payload[..4].copy_from_slice(&1_u32.to_le_bytes());
8248 payload[4..20].copy_from_slice(
8249 &encode(
8250 wire_id::CAPABILITIES_RES,
8251 &WireMediaCapability {
8252 codec: Codec::Pcmu.wire_value(),
8253 max_frames_per_packet: 2,
8254 codec_parameters: [1; 8],
8255 },
8256 )
8257 .unwrap(),
8258 );
8259 assert_eq!(
8260 ClientMessage::decode(Frame::new(
8261 ProtocolVersion::V22.wire(),
8262 wire_id::CAPABILITIES_RES,
8263 payload,
8264 ))
8265 .unwrap(),
8266 ClientMessage::CapabilitiesResponse(vec![MediaCapability {
8267 codec: Codec::Pcmu,
8268 max_packet_ms: 2,
8269 codec_parameters: [1; 8],
8270 }])
8271 );
8272 }
8273
8274 #[test]
8275 fn enbloc_call_selects_exact_version_and_length_layouts() {
8276 for (version, line_instance, payload_bytes) in [
8277 (ProtocolVersion::V3, 0, 24),
8278 (ProtocolVersion::V17, 2, 28),
8279 (ProtocolVersion::V22, 2, 32),
8280 ] {
8281 let message = ClientMessage::EnblocCall {
8282 called_party: "2001".into(),
8283 line_instance,
8284 };
8285 let bytes = message.encode(version).unwrap();
8286 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8287 assert_eq!(frame.payload.len(), payload_bytes);
8288 assert_eq!(
8289 ClientMessage::decode_with_version(frame, version).unwrap(),
8290 message
8291 );
8292 }
8293
8294 let early_with_line = encode(
8295 wire_id::ENBLOC_CALL,
8296 &WireEnblocWithLine::<24, 0> {
8297 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8298 .unwrap(),
8299 line_instance: 2,
8300 },
8301 )
8302 .unwrap();
8303 assert_eq!(
8304 ClientMessage::decode_with_version(
8305 Frame::new(
8306 ProtocolVersion::V3.wire(),
8307 wire_id::ENBLOC_CALL,
8308 early_with_line,
8309 ),
8310 ProtocolVersion::V3,
8311 )
8312 .unwrap(),
8313 ClientMessage::EnblocCall {
8314 called_party: "2001".into(),
8315 line_instance: 2,
8316 }
8317 );
8318
8319 let packed = encode(
8320 wire_id::ENBLOC_CALL,
8321 &WireEnblocWithLine::<25, 0> {
8322 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8323 .unwrap(),
8324 line_instance: 2,
8325 },
8326 )
8327 .unwrap();
8328 assert_eq!(packed.len(), 29);
8329 assert_eq!(
8330 ClientMessage::decode_with_version(
8331 Frame::new(
8332 ProtocolVersion::V22.wire(),
8333 wire_id::ENBLOC_CALL,
8334 packed.clone(),
8335 ),
8336 ProtocolVersion::V22,
8337 )
8338 .unwrap(),
8339 ClientMessage::EnblocCall {
8340 called_party: "2001".into(),
8341 line_instance: 2,
8342 }
8343 );
8344
8345 let mut padded = encode(
8346 wire_id::ENBLOC_CALL,
8347 &WireEnblocWithLine::<25, 0> {
8348 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8349 .unwrap(),
8350 line_instance: 1,
8351 },
8352 )
8353 .unwrap();
8354 padded.extend_from_slice(&[0; 3]);
8355 assert_eq!(padded.len(), 32);
8356 assert_eq!(
8357 ClientMessage::decode_with_version(
8358 Frame::new(ProtocolVersion::V22.wire(), wire_id::ENBLOC_CALL, padded,),
8359 ProtocolVersion::V22,
8360 )
8361 .unwrap(),
8362 ClientMessage::EnblocCall {
8363 called_party: "2001".into(),
8364 line_instance: 1,
8365 }
8366 );
8367
8368 let aligned = encode(
8369 wire_id::ENBLOC_CALL,
8370 &WireEnblocWithLine::<25, 3> {
8371 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8372 .unwrap(),
8373 line_instance: MAX_STATION_BUTTON_INSTANCE,
8374 },
8375 )
8376 .unwrap();
8377 assert_eq!(
8378 ClientMessage::decode_with_version(
8379 Frame::new(ProtocolVersion::V22.wire(), wire_id::ENBLOC_CALL, aligned),
8380 ProtocolVersion::V22,
8381 )
8382 .unwrap(),
8383 ClientMessage::EnblocCall {
8384 called_party: "2001".into(),
8385 line_instance: MAX_STATION_BUTTON_INSTANCE,
8386 }
8387 );
8388
8389 let invalid_aligned_line = encode(
8390 wire_id::ENBLOC_CALL,
8391 &WireEnblocWithLine::<25, 3> {
8392 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8393 .unwrap(),
8394 line_instance: MAX_STATION_BUTTON_INSTANCE + 1,
8395 },
8396 )
8397 .unwrap();
8398 assert!(
8399 ClientMessage::decode_with_version(
8400 Frame::new(
8401 ProtocolVersion::V22.wire(),
8402 wire_id::ENBLOC_CALL,
8403 invalid_aligned_line,
8404 ),
8405 ProtocolVersion::V22,
8406 )
8407 .is_err()
8408 );
8409
8410 let invalid_line = encode(
8411 wire_id::ENBLOC_CALL,
8412 &WireEnblocWithLine::<25, 0> {
8413 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
8414 .unwrap(),
8415 line_instance: MAX_STATION_BUTTON_INSTANCE + 1,
8416 },
8417 )
8418 .unwrap();
8419 assert!(matches!(
8420 ClientMessage::decode_with_version(
8421 Frame::new(
8422 ProtocolVersion::V22.wire(),
8423 wire_id::ENBLOC_CALL,
8424 invalid_line,
8425 ),
8426 ProtocolVersion::V22,
8427 ),
8428 Err(CodecError::InvalidValue {
8429 field: "line instance",
8430 ..
8431 })
8432 ));
8433 assert!(matches!(
8434 ClientMessage::EnblocCall {
8435 called_party: "2001".into(),
8436 line_instance: MAX_STATION_BUTTON_INSTANCE + 1,
8437 }
8438 .encode(ProtocolVersion::V22),
8439 Err(CodecError::InvalidValue {
8440 field: "line instance",
8441 ..
8442 })
8443 ));
8444 assert!(matches!(
8445 ClientMessage::decode_with_version(
8446 Frame::new(ProtocolVersion::V18.wire(), wire_id::ENBLOC_CALL, packed,),
8447 ProtocolVersion::V18,
8448 ),
8449 Err(CodecError::InvalidLength(wire_id::ENBLOC_CALL))
8450 ));
8451 }
8452
8453 #[test]
8454 fn on_hook_accepts_only_fieldless_and_identified_layouts() {
8455 assert_eq!(
8456 ClientMessage::decode_with_version(
8457 Frame::new(ProtocolVersion::V22.wire(), wire_id::ON_HOOK, Vec::new()),
8458 ProtocolVersion::V22,
8459 )
8460 .unwrap(),
8461 ClientMessage::OnHook {
8462 line_instance: 0,
8463 call_reference: 0,
8464 }
8465 );
8466
8467 let identified = encode(
8468 wire_id::ON_HOOK,
8469 &WireLineCall {
8470 line_instance: 2,
8471 call_reference: 42,
8472 },
8473 )
8474 .unwrap();
8475 assert_eq!(
8476 ClientMessage::decode_with_version(
8477 Frame::new(ProtocolVersion::V22.wire(), wire_id::ON_HOOK, identified),
8478 ProtocolVersion::V22,
8479 )
8480 .unwrap(),
8481 ClientMessage::OnHook {
8482 line_instance: 2,
8483 call_reference: 42,
8484 }
8485 );
8486
8487 for payload_bytes in [1, 2, 3, 4, 5, 6, 7, 9, 12] {
8488 assert!(matches!(
8489 ClientMessage::decode_with_version(
8490 Frame::new(
8491 ProtocolVersion::V22.wire(),
8492 wire_id::ON_HOOK,
8493 vec![0; payload_bytes],
8494 ),
8495 ProtocolVersion::V22,
8496 ),
8497 Err(CodecError::InvalidLength(wire_id::ON_HOOK))
8498 ));
8499 }
8500 }
8501
8502 #[test]
8503 fn call_count_request_preserves_known_length_selected_dialects() {
8504 for (message, expected_payload_len) in [
8505 (
8506 ClientMessage::CallCountRequest(CallCountRequestPayload::Empty),
8507 0,
8508 ),
8509 (
8510 ClientMessage::CallCountRequest(CallCountRequestPayload::LegacyWord(0x1234_5678)),
8511 4,
8512 ),
8513 (
8514 ClientMessage::CallCountRequest(CallCountRequestPayload::Extended(
8515 [0xa5; CALL_COUNT_REQUEST_EXTENDED_BYTES],
8516 )),
8517 CALL_COUNT_REQUEST_EXTENDED_BYTES,
8518 ),
8519 ] {
8520 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8521 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8522
8523 assert_eq!(frame.payload.len(), expected_payload_len);
8524 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
8525 }
8526 }
8527
8528 #[test]
8529 fn call_count_request_rejects_unknown_payload_length() {
8530 let frame = Frame {
8531 protocol_version: ProtocolVersion::V22.wire(),
8532 message_id: wire_id::CALL_COUNT_REQ,
8533 payload: vec![0; 8],
8534 };
8535
8536 assert!(matches!(
8537 ClientMessage::decode(frame),
8538 Err(CodecError::InvalidValue {
8539 field: "call-count request payload length",
8540 value: 8,
8541 ..
8542 })
8543 ));
8544 }
8545
8546 #[test]
8547 fn call_count_response_zero_pads_all_forty_two_line_slots() {
8548 let message = ServerMessage::CallCountResponse(CallCountResponse {
8549 total_configured_lines: 2,
8550 starting_line_instance: 1,
8551 line_data: vec![
8552 CallCountLineData {
8553 max_calls: 4,
8554 busy_trigger: 2,
8555 },
8556 CallCountLineData {
8557 max_calls: 2,
8558 busy_trigger: 1,
8559 },
8560 ],
8561 });
8562
8563 let bytes = message.encode(ProtocolVersion::V22).unwrap();
8564 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
8565
8566 assert_eq!(frame.payload.len(), 12 + 42 * 4);
8567 assert_eq!(
8568 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
8569 message
8570 );
8571 }
8572
8573 #[test]
8574 fn connection_statistics_reject_oversized_quality_payloads() {
8575 let payload = WireConnectionStatisticsV19 {
8576 directory_number: WireAlignedText::new(
8577 wire_id::CONNECTION_STATISTICS_RES,
8578 "directory number",
8579 "2002",
8580 )
8581 .unwrap(),
8582 call_reference: 42,
8583 processing: StatisticsProcessing::Clear.wire_value(),
8584 statistics: WireConnectionStatisticsTail {
8585 counters: WireConnectionStatisticsCounters {
8586 packets_sent: 1,
8587 octets_sent: 2,
8588 packets_received: 3,
8589 octets_received: 4,
8590 packets_lost: 5,
8591 jitter_millis: 6,
8592 latency_millis: 7,
8593 },
8594 quality_size: (CONNECTION_QUALITY_MAX_BYTES + 1) as u32,
8595 },
8596 quality: vec![0; CONNECTION_QUALITY_MAX_BYTES + 1],
8597 };
8598 let mut encoded = encode(wire_id::CONNECTION_STATISTICS_RES, &payload).unwrap();
8599 pad_dynamic_payload(&mut encoded);
8600 let frame = Frame::new(
8601 ProtocolVersion::V22.wire(),
8602 wire_id::CONNECTION_STATISTICS_RES,
8603 encoded,
8604 );
8605 assert!(matches!(
8606 ClientMessage::decode_with_version(frame, ProtocolVersion::V22),
8607 Err(CodecError::CountTooLarge {
8608 field: "quality statistics",
8609 maximum: CONNECTION_QUALITY_MAX_BYTES,
8610 ..
8611 })
8612 ));
8613 }
8614
8615 #[test]
8616 fn packed_connection_statistics_accept_zero_padding_without_a_quality_size() {
8617 let mut payload = encode(
8618 wire_id::CONNECTION_STATISTICS_RES,
8619 &WireConnectionStatisticsPackedBase {
8620 directory_number: WireAlignedText::new(
8621 wire_id::CONNECTION_STATISTICS_RES,
8622 "directory number",
8623 "2002",
8624 )
8625 .unwrap(),
8626 call_reference: 0x1122_3344,
8627 processing: StatisticsProcessing::DoNotClear.wire_value() as u8,
8628 counters: WireConnectionStatisticsCounters {
8629 packets_sent: 0x0102_0304,
8630 octets_sent: 0x1112_1314,
8631 packets_received: 0x2122_2324,
8632 octets_received: 0x3132_3334,
8633 packets_lost: 0x4142_4344,
8634 jitter_millis: 0x5152_5354,
8635 latency_millis: 0x6162_6364,
8636 },
8637 },
8638 )
8639 .unwrap();
8640 assert_eq!(payload.len(), 61);
8641 payload.extend_from_slice(&[0; 3]);
8642
8643 let message = ClientMessage::decode_with_version(
8644 Frame::new(
8645 ProtocolVersion::V22.wire(),
8646 wire_id::CONNECTION_STATISTICS_RES,
8647 payload,
8648 ),
8649 ProtocolVersion::V22,
8650 )
8651 .unwrap();
8652 assert_eq!(
8653 message,
8654 ClientMessage::ConnectionStatisticsResponse(ConnectionStatistics {
8655 directory_number: "2002".into(),
8656 call_reference: 0x1122_3344,
8657 processing: StatisticsProcessing::DoNotClear,
8658 packets_sent: 0x0102_0304,
8659 octets_sent: 0x1112_1314,
8660 packets_received: 0x2122_2324,
8661 octets_received: 0x3132_3334,
8662 packets_lost: 0x4142_4344,
8663 jitter_millis: 0x5152_5354,
8664 latency_millis: 0x6162_6364,
8665 quality: ConnectionQualityStatistics::new(Vec::new()).unwrap(),
8666 })
8667 );
8668 }
8669
8670 #[test]
8671 fn packed_connection_statistics_accepts_a_non_word_aligned_declared_tail() {
8672 let quality = (0_u8..110).collect::<Vec<_>>();
8673 let mut payload = encode(
8674 wire_id::CONNECTION_STATISTICS_RES,
8675 &WireConnectionStatisticsPackedPrefix {
8676 base: WireConnectionStatisticsPackedBase {
8677 directory_number: WireAlignedText::new(
8678 wire_id::CONNECTION_STATISTICS_RES,
8679 "directory number",
8680 "2002",
8681 )
8682 .unwrap(),
8683 call_reference: 42,
8684 processing: StatisticsProcessing::Clear.wire_value() as u8,
8685 counters: WireConnectionStatisticsCounters {
8686 packets_sent: 1,
8687 octets_sent: 2,
8688 packets_received: 3,
8689 octets_received: 4,
8690 packets_lost: 5,
8691 jitter_millis: 6,
8692 latency_millis: 7,
8693 },
8694 },
8695 quality_size: quality.len() as u32,
8696 },
8697 )
8698 .unwrap();
8699 payload.extend_from_slice(&quality);
8700 assert_eq!(payload.len(), 175);
8701
8702 let decoded = ClientMessage::decode_with_version(
8703 Frame::new(
8704 ProtocolVersion::V22.wire(),
8705 wire_id::CONNECTION_STATISTICS_RES,
8706 payload.clone(),
8707 ),
8708 ProtocolVersion::V22,
8709 )
8710 .unwrap();
8711 let ClientMessage::ConnectionStatisticsResponse(statistics) = decoded else {
8712 panic!("expected connection statistics response");
8713 };
8714 assert_eq!(statistics.directory_number, "2002");
8715 assert_eq!(statistics.call_reference, 42);
8716 assert_eq!(statistics.quality.as_bytes(), quality);
8717
8718 payload.extend_from_slice(&[0; 3]);
8719 assert!(
8720 ClientMessage::decode_with_version(
8721 Frame::new(
8722 ProtocolVersion::V22.wire(),
8723 wire_id::CONNECTION_STATISTICS_RES,
8724 payload.clone(),
8725 ),
8726 ProtocolVersion::V22,
8727 )
8728 .is_ok()
8729 );
8730 *payload.last_mut().unwrap() = 1;
8731 assert!(
8732 ClientMessage::decode_with_version(
8733 Frame::new(
8734 ProtocolVersion::V22.wire(),
8735 wire_id::CONNECTION_STATISTICS_RES,
8736 payload,
8737 ),
8738 ProtocolVersion::V22,
8739 )
8740 .is_err()
8741 );
8742 }
8743
8744 #[test]
8745 fn aligned_connection_statistics_discards_inactive_fixed_reservoir_storage() {
8746 let quality = (0_u8..113).collect::<Vec<_>>();
8747 let mut payload = encode(
8748 wire_id::CONNECTION_STATISTICS_RES,
8749 &WireConnectionStatisticsV19Prefix {
8750 directory_number: WireAlignedText::new(
8751 wire_id::CONNECTION_STATISTICS_RES,
8752 "directory number",
8753 "2002",
8754 )
8755 .unwrap(),
8756 call_reference: 42,
8757 processing: StatisticsProcessing::Clear.wire_value(),
8758 statistics: WireConnectionStatisticsTail {
8759 counters: WireConnectionStatisticsCounters {
8760 packets_sent: 1,
8761 octets_sent: 2,
8762 packets_received: 3,
8763 octets_received: 4,
8764 packets_lost: 5,
8765 jitter_millis: 6,
8766 latency_millis: 7,
8767 },
8768 quality_size: quality.len() as u32,
8769 },
8770 },
8771 )
8772 .unwrap();
8773 payload.extend_from_slice(&quality);
8774 payload.extend_from_slice(&[0; CONNECTION_QUALITY_MAX_BYTES - 113]);
8775 assert_eq!(payload.len(), 668);
8776 assert_eq!(payload.len() - 68 - quality.len(), 487);
8777
8778 let decoded = ClientMessage::decode_with_version(
8779 Frame::new(
8780 ProtocolVersion::V22.wire(),
8781 wire_id::CONNECTION_STATISTICS_RES,
8782 payload.clone(),
8783 ),
8784 ProtocolVersion::V22,
8785 )
8786 .unwrap();
8787 let ClientMessage::ConnectionStatisticsResponse(statistics) = decoded else {
8788 panic!("expected connection statistics response");
8789 };
8790 assert_eq!(statistics.quality.as_bytes(), quality);
8791
8792 payload[68 + quality.len()..].fill(0xa5);
8793 let decoded = ClientMessage::decode_with_version(
8794 Frame::new(
8795 ProtocolVersion::V22.wire(),
8796 wire_id::CONNECTION_STATISTICS_RES,
8797 payload,
8798 ),
8799 ProtocolVersion::V22,
8800 )
8801 .unwrap();
8802 let ClientMessage::ConnectionStatisticsResponse(statistics) = decoded else {
8803 panic!("expected connection statistics response");
8804 };
8805 assert_eq!(statistics.quality.as_bytes(), quality);
8806 }
8807
8808 #[test]
8809 fn protocol_19_connection_statistics_accepts_each_transition_shape() {
8810 let message = ClientMessage::ConnectionStatisticsResponse(ConnectionStatistics {
8811 directory_number: "2002".into(),
8812 call_reference: 42,
8813 processing: StatisticsProcessing::Clear,
8814 packets_sent: 1,
8815 octets_sent: 2,
8816 packets_received: 3,
8817 octets_received: 4,
8818 packets_lost: 5,
8819 jitter_millis: 6,
8820 latency_millis: 8,
8821 quality: ConnectionQualityStatistics::new(vec![0xa1, 0xb2, 0xc3]).unwrap(),
8822 });
8823
8824 for encoded_as in [ProtocolVersion::V18, ProtocolVersion::V19] {
8825 let frame = FrameDecoder::new()
8826 .push(&message.encode(encoded_as).unwrap())
8827 .unwrap()
8828 .remove(0);
8829 assert_eq!(
8830 ClientMessage::decode_with_version(
8831 Frame::new(
8832 ProtocolVersion::V19.wire(),
8833 wire_id::CONNECTION_STATISTICS_RES,
8834 frame.payload,
8835 ),
8836 ProtocolVersion::V19,
8837 )
8838 .unwrap(),
8839 message,
8840 "encoded with protocol {}",
8841 encoded_as.wire(),
8842 );
8843 }
8844 }
8845
8846 #[test]
8847 fn canonical_connection_statistics_from_protocol_19_use_the_aligned_layout() {
8848 let quality = vec![0xa1, 0xb2, 0xc3];
8849 let message = ClientMessage::ConnectionStatisticsResponse(ConnectionStatistics {
8850 directory_number: "2002".into(),
8851 call_reference: 0x1122_3344,
8852 processing: StatisticsProcessing::DoNotClear,
8853 packets_sent: 0x0102_0304,
8854 octets_sent: 0x1112_1314,
8855 packets_received: 0x2122_2324,
8856 octets_received: 0x3132_3334,
8857 packets_lost: 0x4142_4344,
8858 jitter_millis: 0x5152_5354,
8859 latency_millis: 0x6162_6364,
8860 quality: ConnectionQualityStatistics::new(quality.clone()).unwrap(),
8861 });
8862 let encoded = message.encode(ProtocolVersion::V22).unwrap();
8863 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
8864
8865 assert_eq!(&frame.payload[28..32], &0x1122_3344_u32.to_le_bytes());
8866 assert_eq!(&frame.payload[32..36], &1_u32.to_le_bytes());
8867 assert_eq!(&frame.payload[36..40], &0x0102_0304_u32.to_le_bytes());
8868 assert_eq!(&frame.payload[40..44], &0x1112_1314_u32.to_le_bytes());
8869 assert_eq!(&frame.payload[44..48], &0x2122_2324_u32.to_le_bytes());
8870 assert_eq!(&frame.payload[48..52], &0x3132_3334_u32.to_le_bytes());
8871 assert_eq!(&frame.payload[52..56], &0x4142_4344_u32.to_le_bytes());
8872 assert_eq!(&frame.payload[56..60], &0x5152_5354_u32.to_le_bytes());
8873 assert_eq!(&frame.payload[60..64], &0x6162_6364_u32.to_le_bytes());
8874 assert_eq!(&frame.payload[64..68], &3_u32.to_le_bytes());
8875 assert_eq!(&frame.payload[68..71], quality.as_slice());
8876 assert_eq!(
8877 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
8878 message
8879 );
8880 }
8881
8882 #[test]
8883 fn media_wire_schemas_round_trip_byte_for_byte() {
8884 let start = fixture(include_str!(
8885 "../../tests/fixtures/golden/start_media_transmission_v17.hex"
8886 ));
8887 let start_value: WireStartMediaV17 = decode(0x008a, &start[12..]).unwrap();
8888 assert_eq!(encode(0x008a, &start_value).unwrap(), &start[12..]);
8889 let start_frame = FrameDecoder::new().push(&start).unwrap().remove(0);
8890 let start_message = ServerMessage::decode(start_frame, ProtocolVersion::V17).unwrap();
8891 assert_eq!(start_message.encode(ProtocolVersion::V17).unwrap(), start);
8892
8893 let open = fixture(include_str!(
8894 "../../tests/fixtures/golden/open_receive_channel_v17.hex"
8895 ));
8896 let open_value: WireOpenReceiveV17 = decode(0x0105, &open[12..]).unwrap();
8897 assert_eq!(encode(0x0105, &open_value).unwrap(), &open[12..]);
8898 let open_frame = FrameDecoder::new().push(&open).unwrap().remove(0);
8899 let open_message = ServerMessage::decode(open_frame, ProtocolVersion::V17).unwrap();
8900 assert_eq!(open_message.encode(ProtocolVersion::V17).unwrap(), open);
8901
8902 let ack = fixture(include_str!(
8903 "../../tests/fixtures/golden/start_media_transmission_ack_v20.hex"
8904 ));
8905 let ack_value: WireStartMediaAckV20 = decode(0x0154, &ack[12..]).unwrap();
8906 assert_eq!(encode(0x0154, &ack_value).unwrap(), &ack[12..]);
8907 let ack_frame = FrameDecoder::new().push(&ack).unwrap().remove(0);
8908 let ack_message =
8909 ClientMessage::decode_with_version(ack_frame, ProtocolVersion::V20).unwrap();
8910 assert_eq!(ack_message.encode(ProtocolVersion::V20).unwrap(), ack);
8911 }
8912
8913 #[test]
8914 fn audio_media_version_boundaries_have_exact_payload_sizes() {
8915 let endpoint = MediaEndpoint {
8916 address: "192.0.2.20".parse().unwrap(),
8917 rtp_port: 16_000,
8918 rtcp_port: 16_001,
8919 codec: Codec::Pcmu,
8920 packet_ms: 20,
8921 max_frames_per_packet: 2,
8922 telephone_event_payload: 101,
8923 };
8924 for (version, open_size, start_size) in [
8925 (11, 92, 108),
8926 (12, 108, 116),
8927 (16, 108, 116),
8928 (17, 128, 132),
8929 (18, 132, 132),
8930 (20, 132, 132),
8931 (21, 168, 168),
8932 (22, 168, 168),
8933 ] {
8934 let protocol = ProtocolVersion::new(version).unwrap();
8935 let open = ServerMessage::OpenReceiveChannel {
8936 call_reference: 7,
8937 passthrough_party_id: 9,
8938 packet_ms: 20,
8939 codec: Codec::Pcmu,
8940 echo_cancellation: EchoCancellation::On,
8941 telephone_event_payload: 101,
8942 source_address: endpoint.address,
8943 source_port: endpoint.rtp_port,
8944 encryption: None,
8945 wire: None,
8946 };
8947 let open_bytes = open.encode(protocol).unwrap();
8948 assert_eq!(open_bytes.len() - 12, open_size, "protocol {version}");
8949 let decoded = ServerMessage::decode(
8950 FrameDecoder::new().push(&open_bytes).unwrap().remove(0),
8951 protocol,
8952 )
8953 .unwrap();
8954 assert_eq!(decoded.encode(protocol).unwrap(), open_bytes);
8955
8956 let start = ServerMessage::StartMediaTransmission {
8957 call_reference: 7,
8958 passthrough_party_id: 9,
8959 endpoint,
8960 silence_suppression: SilenceSuppression::Off,
8961 traffic_class: MediaTrafficClass::default(),
8962 encryption: None,
8963 wire: None,
8964 };
8965 let start_bytes = start.encode(protocol).unwrap();
8966 assert_eq!(start_bytes.len() - 12, start_size, "protocol {version}");
8967 let decoded = ServerMessage::decode(
8968 FrameDecoder::new().push(&start_bytes).unwrap().remove(0),
8969 protocol,
8970 )
8971 .unwrap();
8972 assert_eq!(decoded.encode(protocol).unwrap(), start_bytes);
8973 }
8974 }
8975
8976 #[test]
8977 fn audio_acknowledgements_keep_conference_and_call_references_distinct() {
8978 for (protocol, address, open_size, start_size, failure_size) in [
8979 (
8980 ProtocolVersion::V16,
8981 "192.0.2.21".parse().unwrap(),
8982 20,
8983 24,
8984 20,
8985 ),
8986 (
8987 ProtocolVersion::V17,
8988 "2001:db8::21".parse().unwrap(),
8989 36,
8990 40,
8991 36,
8992 ),
8993 ] {
8994 let open = ClientMessage::OpenReceiveChannelAck {
8995 status: MediaStatus::Ok,
8996 address,
8997 port: 16_000,
8998 passthrough_party_id: 9,
8999 call_reference: 7,
9000 };
9001 let open_bytes = open.encode(protocol).unwrap();
9002 assert_eq!(open_bytes.len() - 12, open_size);
9003 assert_eq!(
9004 ClientMessage::decode_with_version(
9005 FrameDecoder::new().push(&open_bytes).unwrap().remove(0),
9006 protocol,
9007 )
9008 .unwrap(),
9009 open
9010 );
9011
9012 let start = ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
9013 conference_id: 6,
9014 passthrough_party_id: 9,
9015 call_reference: 7,
9016 status: MediaStatus::Ok,
9017 address,
9018 port: 16_000,
9019 wire: None,
9020 });
9021 let start_bytes = start.encode(protocol).unwrap();
9022 assert_eq!(start_bytes.len() - 12, start_size);
9023 let decoded = ClientMessage::decode_with_version(
9024 FrameDecoder::new().push(&start_bytes).unwrap().remove(0),
9025 protocol,
9026 )
9027 .unwrap();
9028 assert_eq!(decoded, start);
9029
9030 let failure = ClientMessage::MediaTransmissionFailure {
9031 conference_id: 6,
9032 passthrough_party_id: 9,
9033 address,
9034 port: 16_000,
9035 call_reference: 7,
9036 status: MediaStatus::UnspecifiedError,
9037 };
9038 let failure_bytes = failure.encode(protocol).unwrap();
9039 assert_eq!(failure_bytes.len() - 12, failure_size);
9040 assert_eq!(
9041 ClientMessage::decode_with_version(
9042 FrameDecoder::new().push(&failure_bytes).unwrap().remove(0),
9043 protocol,
9044 )
9045 .unwrap(),
9046 failure
9047 );
9048 }
9049
9050 for message_id in [
9051 wire_id::CLOSE_RECEIVE_CHANNEL,
9052 wire_id::STOP_MEDIA_TRANSMISSION,
9053 ] {
9054 let payload = [0_u8; 16];
9055 let frame = Frame::new(ProtocolVersion::V22.wire(), message_id, payload.to_vec());
9056 let message = ServerMessage::decode(frame, ProtocolVersion::V22).unwrap();
9057 assert_eq!(message.encode(ProtocolVersion::V22).unwrap().len() - 12, 16);
9058
9059 let truncated = Frame::new(
9060 ProtocolVersion::V22.wire(),
9061 message_id,
9062 payload[..12].to_vec(),
9063 );
9064 assert!(matches!(
9065 ServerMessage::decode(truncated, ProtocolVersion::V22),
9066 Err(CodecError::Truncated { .. })
9067 ));
9068 }
9069 }
9070
9071 #[test]
9072 fn session_and_video_envelopes_preserve_every_wire_byte() {
9073 for (protocol, address, expected_size) in [
9074 (ProtocolVersion::V16, "192.0.2.30".parse().unwrap(), 8),
9075 (ProtocolVersion::V17, "2001:db8::30".parse().unwrap(), 24),
9076 ] {
9077 for message in [
9078 ControlMessage::StartSessionTransmission(SessionTransmission {
9079 remote_address: address,
9080 session_type: 0x1122_3344,
9081 }),
9082 ControlMessage::StopSessionTransmission(SessionTransmission {
9083 remote_address: address,
9084 session_type: 0x5566_7788,
9085 }),
9086 ] {
9087 let bytes = message.encode(protocol).unwrap();
9088 assert_eq!(bytes.len() - 12, expected_size);
9089 let decoded = ControlMessage::decode(
9090 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9091 protocol,
9092 )
9093 .unwrap();
9094 assert_eq!(decoded.encode(protocol).unwrap(), bytes);
9095 }
9096 }
9097
9098 for (version, address, expected_size) in [
9099 (11, "0.0.0.0".parse().unwrap(), 164),
9100 (12, "192.0.2.31".parse().unwrap(), 172),
9101 (16, "192.0.2.31".parse().unwrap(), 172),
9102 (17, "2001:db8::31".parse().unwrap(), 192),
9103 ] {
9104 let protocol = ProtocolVersion::new(version).unwrap();
9105 let message = ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
9106 conference_id: 42.into(),
9107 passthrough_party_id: 9.into(),
9108 line_instance: 1,
9109 call_reference: 7.into(),
9110 payload: typed_video_payload(MultimediaVideoCapabilityArm::H264 {
9111 profile: 100,
9112 level: 42,
9113 custom_max_mbps: 40_500,
9114 custom_max_fs: 1_620,
9115 custom_max_dpb: 8_100,
9116 custom_max_br_and_cpb: 10_000,
9117 }),
9118 conference_creator: true,
9119 encryption: None,
9120 stream_passthrough_id: 10,
9121 associated_stream_id: 11,
9122 source: MediaEndpointAddress {
9123 address,
9124 port: if version < 12 { 0 } else { 16_000 },
9125 },
9126 requested_address_type: if version >= 17 {
9127 IpAddressType::Ipv6
9128 } else {
9129 IpAddressType::Ipv4
9130 },
9131 });
9132 let bytes = message.encode(protocol).unwrap();
9133 assert_eq!(bytes.len() - 12, expected_size, "protocol {version}");
9134 let decoded = ServerMessage::decode(
9135 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9136 protocol,
9137 )
9138 .unwrap();
9139 assert_eq!(decoded.encode(protocol).unwrap(), bytes);
9140 }
9141
9142 for (protocol, address, expected_size) in [
9143 (ProtocolVersion::V16, "192.0.2.32".parse().unwrap(), 168),
9144 (ProtocolVersion::V17, "2001:db8::32".parse().unwrap(), 184),
9145 ] {
9146 let message = ServerMessage::StartMultimediaTransmission(StartMultimediaTransmission {
9147 conference_id: 42.into(),
9148 passthrough_party_id: 9.into(),
9149 endpoint: MediaEndpointAddress {
9150 address,
9151 port: 16_002,
9152 },
9153 call_reference: 7.into(),
9154 payload: typed_video_payload(MultimediaVideoCapabilityArm::H264 {
9155 profile: 100,
9156 level: 42,
9157 custom_max_mbps: 40_500,
9158 custom_max_fs: 1_620,
9159 custom_max_dpb: 8_100,
9160 custom_max_br_and_cpb: 10_000,
9161 }),
9162 traffic_class: MediaTrafficClass::from_wire(184),
9163 encryption: None,
9164 stream_passthrough_id: 10,
9165 associated_stream_id: 11,
9166 });
9167 let bytes = message.encode(protocol).unwrap();
9168 assert_eq!(bytes.len() - 12, expected_size);
9169 let traffic_class_offset = match protocol.wire() {
9170 17.. => 60,
9171 _ => 44,
9172 };
9173 assert_eq!(
9174 &bytes[traffic_class_offset..traffic_class_offset + 4],
9175 &184_u32.to_le_bytes()
9176 );
9177 let decoded = ServerMessage::decode(
9178 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9179 protocol,
9180 )
9181 .unwrap();
9182 assert_eq!(decoded.encode(protocol).unwrap(), bytes);
9183 }
9184
9185 let miscellaneous = ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
9186 conference_id: 42.into(),
9187 passthrough_party_id: 9.into(),
9188 call_reference: 7.into(),
9189 command: values::MiscCommandType::LostPartialPicture,
9190 data: BoundedBytes::try_from((0_u8..36).collect::<Vec<_>>()).unwrap(),
9191 });
9192 let bytes = miscellaneous.encode(ProtocolVersion::V22).unwrap();
9193 assert_eq!(bytes.len() - 12, 52);
9194 let decoded = ServerMessage::decode(
9195 FrameDecoder::new().push(&bytes).unwrap().remove(0),
9196 ProtocolVersion::V22,
9197 )
9198 .unwrap();
9199 assert_eq!(decoded, miscellaneous);
9200
9201 for (message_id, protocol, expected) in [
9202 (wire_id::OPEN_MULTIMEDIA_CHANNEL, ProtocolVersion::V17, 192),
9203 (
9204 wire_id::START_MULTIMEDIA_TRANSMISSION,
9205 ProtocolVersion::V17,
9206 184,
9207 ),
9208 (wire_id::MISCELLANEOUS_COMMAND, ProtocolVersion::V22, 52),
9209 ] {
9210 for actual in [expected - 1, expected + 1] {
9211 let frame = Frame::new(protocol.wire(), message_id, vec![0; actual]);
9212 assert!(ServerMessage::decode(frame, protocol).is_err());
9213 }
9214 }
9215 for (protocol, expected) in [(ProtocolVersion::V16, 8), (ProtocolVersion::V17, 24)] {
9216 for actual in [expected - 1, expected + 1] {
9217 let frame = Frame::new(
9218 protocol.wire(),
9219 wire_id::START_SESSION_TRANSMISSION,
9220 vec![0; actual],
9221 );
9222 assert!(ControlMessage::decode(frame, protocol).is_err());
9223 }
9224 }
9225 }
9226
9227 #[test]
9228 fn unsupported_decoded_multimedia_payloads_are_lossless_and_provenance_bound() {
9229 let mut words = [0; MULTIMEDIA_CAPABILITY_BYTES / 4];
9230 words[0] = 2_048;
9231 words[1] = 1;
9232 words[2] = VideoFormat::Cif.wire_value();
9233 words[3] = 2;
9234 words[12] = 7;
9235 words[13..].copy_from_slice(&[61, 62, 63, 64, 65, 66]);
9236 let capability = multimedia_capability_bytes(words);
9237 let payload = MultimediaPayload::from_wire(
9238 0,
9239 test_rtp_payload_number(97),
9240 capability,
9241 Codec::H265,
9242 MultimediaPayloadDirection::Receive,
9243 ProtocolVersion::V17,
9244 );
9245 assert_eq!(payload.codec(), Codec::H265);
9246 assert_eq!(payload.video_capability(), None);
9247 let open = ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
9248 conference_id: 42.into(),
9249 passthrough_party_id: 9.into(),
9250 line_instance: 1,
9251 call_reference: 7.into(),
9252 payload: payload.clone(),
9253 conference_creator: false,
9254 encryption: None,
9255 stream_passthrough_id: 10,
9256 associated_stream_id: 0,
9257 source: MediaEndpointAddress {
9258 address: "192.0.2.31".parse().unwrap(),
9259 port: 16_000,
9260 },
9261 requested_address_type: IpAddressType::Ipv4,
9262 });
9263 let encoded = open.encode(ProtocolVersion::V17).unwrap();
9264 let decoded = ServerMessage::decode(
9265 FrameDecoder::new().push(&encoded).unwrap().remove(0),
9266 ProtocolVersion::V17,
9267 )
9268 .unwrap();
9269 assert_eq!(decoded.encode(ProtocolVersion::V17).unwrap(), encoded);
9270 assert!(matches!(
9271 open.encode(ProtocolVersion::V16),
9272 Err(CodecError::InvalidValue {
9273 message_id: wire_id::OPEN_MULTIMEDIA_CHANNEL,
9274 field: "multimedia payload provenance",
9275 ..
9276 })
9277 ));
9278
9279 let start = ServerMessage::StartMultimediaTransmission(StartMultimediaTransmission {
9280 conference_id: 42.into(),
9281 passthrough_party_id: 9.into(),
9282 endpoint: MediaEndpointAddress {
9283 address: "192.0.2.32".parse().unwrap(),
9284 port: 16_002,
9285 },
9286 call_reference: 7.into(),
9287 payload,
9288 traffic_class: MediaTrafficClass::from_wire(136),
9289 encryption: None,
9290 stream_passthrough_id: 11,
9291 associated_stream_id: 0,
9292 });
9293 assert!(matches!(
9294 start.encode(ProtocolVersion::V17),
9295 Err(CodecError::InvalidValue {
9296 message_id: wire_id::START_MULTIMEDIA_TRANSMISSION,
9297 field: "multimedia payload provenance",
9298 ..
9299 })
9300 ));
9301 }
9302
9303 #[test]
9304 fn capabilities_incompatible_with_outer_compression_remain_opaque_and_lossless() {
9305 let message = ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
9306 conference_id: 42.into(),
9307 passthrough_party_id: 9.into(),
9308 line_instance: 1,
9309 call_reference: 7.into(),
9310 payload: typed_video_payload(MultimediaVideoCapabilityArm::H264 {
9311 profile: 100,
9312 level: 42,
9313 custom_max_mbps: 40_500,
9314 custom_max_fs: 1_620,
9315 custom_max_dpb: 8_100,
9316 custom_max_br_and_cpb: 10_000,
9317 }),
9318 conference_creator: false,
9319 encryption: None,
9320 stream_passthrough_id: 10,
9321 associated_stream_id: 0,
9322 source: MediaEndpointAddress {
9323 address: "192.0.2.31".parse().unwrap(),
9324 port: 16_000,
9325 },
9326 requested_address_type: IpAddressType::Ipv4,
9327 });
9328 let mut mismatched = message.encode(ProtocolVersion::V17).unwrap();
9329 let compression_offset = super::wire::HEADER_SIZE + 8;
9330 mismatched[compression_offset..compression_offset + 4]
9331 .copy_from_slice(&Codec::H263.wire_value().to_le_bytes());
9332
9333 let decoded = ServerMessage::decode(
9334 FrameDecoder::new().push(&mismatched).unwrap().remove(0),
9335 ProtocolVersion::V17,
9336 )
9337 .unwrap();
9338 let ServerMessage::OpenMultimediaChannel(open) = &decoded else {
9339 panic!("multimedia message decoded as a different command");
9340 };
9341 assert_eq!(open.payload.codec(), Codec::H263);
9342 assert_eq!(open.payload.compression_codec(), Codec::H263);
9343 assert_eq!(open.payload.video_capability(), None);
9344 assert_eq!(decoded.encode(ProtocolVersion::V17).unwrap(), mismatched);
9345 assert_ne!(decoded, message);
9346
9347 let mut invalid_payload_number = mismatched;
9348 let descriptor_offset = super::wire::HEADER_SIZE + 20;
9349 invalid_payload_number[descriptor_offset + 4..descriptor_offset + 8]
9350 .copy_from_slice(&128_u32.to_le_bytes());
9351 assert!(matches!(
9352 ServerMessage::decode(
9353 FrameDecoder::new()
9354 .push(&invalid_payload_number)
9355 .unwrap()
9356 .remove(0),
9357 ProtocolVersion::V17,
9358 ),
9359 Err(CodecError::InvalidValue {
9360 field: "RTP payload number",
9361 value: 128,
9362 ..
9363 })
9364 ));
9365 }
9366
9367 #[test]
9368 fn typed_multimedia_video_arms_encode_at_the_evidenced_offsets() {
9369 let arms = [
9370 (
9371 MultimediaVideoCapabilityArm::H261 {
9372 temporal_spatial_trade_off_capability: 11,
9373 still_image_transmission: 12,
9374 },
9375 [11, 12, 0, 0, 0, 0],
9376 ),
9377 (
9378 MultimediaVideoCapabilityArm::H263 {
9379 capability_bitfield: 21,
9380 annex_n_and_w_future_use: 22,
9381 },
9382 [21, 22, 0, 0, 0, 0],
9383 ),
9384 (
9385 MultimediaVideoCapabilityArm::H263Plus {
9386 model_number: 31,
9387 bandwidth: 32,
9388 },
9389 [31, 32, 0, 0, 0, 0],
9390 ),
9391 (
9392 MultimediaVideoCapabilityArm::H264 {
9393 profile: 41,
9394 level: 42,
9395 custom_max_mbps: 43,
9396 custom_max_fs: 44,
9397 custom_max_dpb: 45,
9398 custom_max_br_and_cpb: 46,
9399 },
9400 [41, 42, 43, 44, 45, 46],
9401 ),
9402 ];
9403
9404 for (arm, expected_arm) in arms {
9405 let payload = typed_video_payload(arm);
9406 let bytes = multimedia_capability_to_wire(&payload);
9407 let words = multimedia_capability_words(bytes);
9408 assert_eq!(words[0], 1_024);
9409 assert_eq!(words[1], 2);
9410 assert_eq!(
9411 &words[2..6],
9412 &[
9413 VideoFormat::Cif4.wire_value(),
9414 1,
9415 VideoFormat::Cif.wire_value(),
9416 2,
9417 ]
9418 );
9419 assert_eq!(&words[6..12], &[0; 6]);
9420 assert_eq!(words[12], 7);
9421 assert_eq!(&words[13..], &expected_arm);
9422
9423 let decoded = decoded_multimedia_capability(bytes, arm.codec());
9424 let MultimediaCapabilityState::Video(decoded) = decoded else {
9425 panic!("typed codec arm was not decoded");
9426 };
9427 assert_eq!(decoded.arm(), arm);
9428 assert_eq!(decoded.picture_formats().len(), 2);
9429 let decoded_payload = MultimediaPayload::from_decoded(
9430 payload.descriptor(),
9431 MultimediaCapabilityState::Video(decoded),
9432 MultimediaPayloadDirection::Transmit,
9433 ProtocolVersion::V17,
9434 arm.codec(),
9435 );
9436 assert_eq!(multimedia_capability_to_wire(&decoded_payload), bytes);
9437 }
9438 }
9439
9440 #[test]
9441 fn multimedia_descriptor_carries_the_negotiated_rtp_mapping() {
9442 for (arm, expected_payload_number) in [
9443 (
9444 MultimediaVideoCapabilityArm::H261 {
9445 temporal_spatial_trade_off_capability: 0,
9446 still_image_transmission: 0,
9447 },
9448 31,
9449 ),
9450 (
9451 MultimediaVideoCapabilityArm::H263 {
9452 capability_bitfield: 0,
9453 annex_n_and_w_future_use: 0,
9454 },
9455 34,
9456 ),
9457 (
9458 MultimediaVideoCapabilityArm::H263Plus {
9459 model_number: 0,
9460 bandwidth: 0,
9461 },
9462 96,
9463 ),
9464 (
9465 MultimediaVideoCapabilityArm::H264 {
9466 profile: 0,
9467 level: 0,
9468 custom_max_mbps: 0,
9469 custom_max_fs: 0,
9470 custom_max_dpb: 0,
9471 custom_max_br_and_cpb: 0,
9472 },
9473 97,
9474 ),
9475 ] {
9476 let payload = typed_video_payload(arm);
9477 let descriptor = payload.descriptor();
9478 assert_eq!(descriptor.rfc_number(), 0);
9479 assert_eq!(descriptor.payload_number().get(), expected_payload_number);
9480 assert_eq!(payload.codec(), arm.codec());
9481 assert_eq!(
9482 encode(
9483 wire_id::OPEN_MULTIMEDIA_CHANNEL,
9484 &WireMultimediaPayloadDescriptor::from(descriptor)
9485 )
9486 .unwrap(),
9487 [0, 0, 0, 0, expected_payload_number, 0, 0, 0,]
9488 );
9489 }
9490
9491 let payload = typed_video_payload(MultimediaVideoCapabilityArm::H263 {
9492 capability_bitfield: 0,
9493 annex_n_and_w_future_use: 0,
9494 });
9495 let descriptor = MultimediaPayloadDescriptor::new(4, payload.payload_number());
9496 assert_eq!(
9497 encode(
9498 wire_id::OPEN_MULTIMEDIA_CHANNEL,
9499 &WireMultimediaPayloadDescriptor::from(descriptor),
9500 )
9501 .unwrap(),
9502 [4, 0, 0, 0, 34, 0, 0, 0]
9503 );
9504 }
9505
9506 #[test]
9507 fn multimedia_acknowledgements_use_distinct_versioned_layouts() {
9508 for (protocol, address, open_size, start_size) in [
9509 (ProtocolVersion::V16, "192.0.2.33".parse().unwrap(), 20, 24),
9510 (
9511 ProtocolVersion::V17,
9512 "2001:db8::33".parse().unwrap(),
9513 36,
9514 40,
9515 ),
9516 ] {
9517 let open =
9518 ClientMessage::OpenMultimediaReceiveChannelAck(OpenMultimediaReceiveChannelAck {
9519 status: MediaStatus::Ok,
9520 endpoint: MediaEndpointAddress {
9521 address,
9522 port: 16_000,
9523 },
9524 passthrough_party_id: 9.into(),
9525 call_reference: 7.into(),
9526 });
9527 let open_bytes = open.encode(protocol).unwrap();
9528 assert_eq!(open_bytes.len() - 12, open_size);
9529 assert_eq!(
9530 ClientMessage::decode_with_version(
9531 FrameDecoder::new().push(&open_bytes).unwrap().remove(0),
9532 protocol,
9533 )
9534 .unwrap(),
9535 open
9536 );
9537
9538 let start =
9539 ClientMessage::StartMultimediaTransmissionAck(StartMultimediaTransmissionAck {
9540 conference_id: 42.into(),
9541 passthrough_party_id: 9.into(),
9542 call_reference: 7.into(),
9543 endpoint: MediaEndpointAddress {
9544 address,
9545 port: 16_002,
9546 },
9547 status: MediaStatus::Ok,
9548 });
9549 let start_bytes = start.encode(protocol).unwrap();
9550 assert_eq!(start_bytes.len() - 12, start_size);
9551 assert_eq!(
9552 ClientMessage::decode_with_version(
9553 FrameDecoder::new().push(&start_bytes).unwrap().remove(0),
9554 protocol,
9555 )
9556 .unwrap(),
9557 start
9558 );
9559 }
9560 }
9561
9562 #[test]
9563 fn port_messages_switch_layouts_at_protocol_twenty() {
9564 for (protocol, request_size, close_size, response_size) in [
9565 (ProtocolVersion::V19, 16, 12, 24),
9566 (ProtocolVersion::V20, 24, 16, 44),
9567 ] {
9568 let extended = protocol.wire() >= 20;
9569 let request = ServerMessage::PortRequest(PortRequest {
9570 conference_id: 42.into(),
9571 call_reference: 7.into(),
9572 passthrough_party_id: 9.into(),
9573 transport: MediaTransport::Rtp,
9574 address_type: extended.then_some(IpAddressType::Ipv4AndIpv6),
9575 media_type: extended.then_some(MediaType::Audio),
9576 });
9577 let request_bytes = request.encode(protocol).unwrap();
9578 assert_eq!(request_bytes.len() - 12, request_size);
9579 assert_eq!(
9580 ServerMessage::decode(
9581 FrameDecoder::new().push(&request_bytes).unwrap().remove(0),
9582 protocol,
9583 )
9584 .unwrap(),
9585 request
9586 );
9587
9588 let close = ServerMessage::PortClose(PortClose {
9589 conference_id: 42.into(),
9590 call_reference: 7.into(),
9591 passthrough_party_id: 9.into(),
9592 media_type: extended.then_some(MediaType::Audio),
9593 });
9594 let close_bytes = close.encode(protocol).unwrap();
9595 assert_eq!(close_bytes.len() - 12, close_size);
9596 assert_eq!(
9597 ServerMessage::decode(
9598 FrameDecoder::new().push(&close_bytes).unwrap().remove(0),
9599 protocol,
9600 )
9601 .unwrap(),
9602 close
9603 );
9604
9605 let response = ControlMessage::PortResponse(PortEndpoint {
9606 conference_id: 42,
9607 call_reference: 7,
9608 passthrough_party_id: 9,
9609 address: if extended {
9610 "2001:db8::34".parse().unwrap()
9611 } else {
9612 "192.0.2.34".parse().unwrap()
9613 },
9614 rtp_port: 16_000,
9615 rtcp_port: 16_001,
9616 media_type: extended.then_some(MediaType::Audio),
9617 });
9618 let response_bytes = response.encode(protocol).unwrap();
9619 assert_eq!(response_bytes.len() - 12, response_size);
9620 assert_eq!(
9621 ControlMessage::decode(
9622 FrameDecoder::new().push(&response_bytes).unwrap().remove(0),
9623 protocol,
9624 )
9625 .unwrap(),
9626 response
9627 );
9628 }
9629 }
9630
9631 #[test]
9632 fn wire_encryption_rejects_invalid_lengths_without_debugging_secrets() {
9633 let encryption = WireEncryptionInfo {
9634 algorithm: EncryptionMethod::Aes128HmacSha1_80.wire_value(),
9635 key_length: 17,
9636 salt_length: 16,
9637 key: [0xa5; 16],
9638 salt: [0x5a; 16],
9639 mki_present: 1,
9640 key_derivation_rate: 64,
9641 };
9642 let debug = format!("{encryption:?}");
9643 assert!(debug.contains("<redacted>"));
9644 assert!(!debug.contains("165"));
9645 assert!(!debug.contains("90"));
9646
9647 let error = encryption
9648 .to_public(wire_id::OPEN_MULTIMEDIA_CHANNEL)
9649 .unwrap_err();
9650 assert!(matches!(
9651 error,
9652 CodecError::SecretTooLong {
9653 field: "media encryption key",
9654 actual: 17,
9655 maximum: 16,
9656 }
9657 ));
9658 assert!(!error.to_string().contains("165"));
9659 }
9660
9661 #[test]
9662 fn wire_encryption_preserves_bytes_after_declared_lengths() {
9663 let wire = WireEncryptionInfo {
9664 algorithm: EncryptionMethod::Aes128HmacSha1_80.wire_value(),
9665 key_length: 1,
9666 salt_length: 1,
9667 key: [0xa5, 0x7f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
9668 salt: [0x5a, 0x6f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
9669 mki_present: 0,
9670 key_derivation_rate: 0,
9671 };
9672 let encryption = wire.to_public(wire_id::OPEN_MULTIMEDIA_CHANNEL).unwrap();
9673
9674 assert_eq!(WireEncryptionInfo::from_public(encryption.as_ref()), wire);
9675 assert_eq!(encryption.as_ref().unwrap().key(), &[0xa5]);
9676 assert_eq!(encryption.as_ref().unwrap().salt(), &[0x5a]);
9677 }
9678
9679 #[test]
9680 fn announcement_messages_use_bounded_fixed_wire_layouts() {
9681 let message = ControlMessage::StartAnnouncement {
9682 announcements: vec![AnnouncementEntry {
9683 locale: 1,
9684 country: 46,
9685 tone: Tone::Zip,
9686 }],
9687 end_of_ack: EndOfAnnouncementAck::Required,
9688 conference_id: 42,
9689 matrix_conference_party_ids: vec![7, 9],
9690 hearing_conference_party_mask: 0b11,
9691 play_mode: AnnouncementPlayMode::Continuous,
9692 };
9693 let bytes = message.encode(ProtocolVersion::V22).unwrap();
9694 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
9695 assert_eq!(frame.message_id, wire_id::START_ANNOUNCEMENT);
9696 assert_eq!(frame.payload.len(), 464);
9697 assert_eq!(
9698 ControlMessage::decode(frame.clone(), ProtocolVersion::V22).unwrap(),
9699 message
9700 );
9701
9702 let mut truncated = frame;
9703 truncated.payload.pop();
9704 assert!(matches!(
9705 ControlMessage::decode(truncated, ProtocolVersion::V22),
9706 Err(CodecError::Truncated {
9707 message_id: wire_id::START_ANNOUNCEMENT,
9708 needed: 464,
9709 actual: 463,
9710 })
9711 ));
9712
9713 for (message, expected_payload_len) in [
9714 (ControlMessage::StopAnnouncement { conference_id: 42 }, 4),
9715 (
9716 ControlMessage::AnnouncementFinish {
9717 conference_id: 42,
9718 play_status: AnnouncementPlayStatus::Unknown(3),
9719 },
9720 8,
9721 ),
9722 ] {
9723 let bytes = message.encode(ProtocolVersion::V22).unwrap();
9724 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
9725 assert_eq!(frame.payload.len(), expected_payload_len);
9726 assert_eq!(
9727 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9728 message
9729 );
9730 }
9731 }
9732
9733 #[test]
9734 fn conference_lifecycle_messages_use_documented_wire_sizes() {
9735 let server_messages = [
9736 (
9737 ControlMessage::ClearConference {
9738 conference_id: 42.into(),
9739 service_number: 3,
9740 },
9741 wire_id::CLEAR_CONFERENCE,
9742 8,
9743 ),
9744 (
9745 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
9746 conference_id: 42.into(),
9747 reserved_participants: 8,
9748 resource_type: ConferenceResourceType::Conference,
9749 application_id: 7.into(),
9750 application_conference_id: "festival-42".into(),
9751 application_data: "main-stage".into(),
9752 passthrough_data: vec![1, 2, 3],
9753 }),
9754 wire_id::CREATE_CONFERENCE_REQ,
9755 80,
9756 ),
9757 (
9758 ControlMessage::DeleteConferenceRequest {
9759 conference_id: 42.into(),
9760 },
9761 wire_id::DELETE_CONFERENCE_REQ,
9762 4,
9763 ),
9764 (
9765 ControlMessage::ModifyConferenceRequest(ModifyConferenceRequest {
9766 conference_id: 42.into(),
9767 reserved_participants: 12,
9768 application_id: 7.into(),
9769 application_conference_id: "festival-42".into(),
9770 application_data: "main-stage".into(),
9771 passthrough_data: vec![4, 5],
9772 }),
9773 wire_id::MODIFY_CONFERENCE_REQ,
9774 76,
9775 ),
9776 (
9777 ControlMessage::AuditConferenceRequest,
9778 wire_id::AUDIT_CONFERENCE_REQ,
9779 0,
9780 ),
9781 ];
9782 for (message, expected_id, expected_payload_len) in server_messages {
9783 let frame = FrameDecoder::new()
9784 .push(&message.encode(ProtocolVersion::V22).unwrap())
9785 .unwrap()
9786 .remove(0);
9787 assert_eq!(frame.message_id, expected_id);
9788 assert_eq!(frame.payload.len(), expected_payload_len);
9789 assert_eq!(
9790 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9791 message
9792 );
9793 }
9794
9795 let audit = AuditConferenceResponse {
9796 last: 1,
9797 entries: vec![AuditConferenceEntry {
9798 conference_id: 42.into(),
9799 resource_type: ConferenceResourceType::Conference,
9800 reserved_participants: 8,
9801 active_participants: 3,
9802 application_id: 7.into(),
9803 application_conference_id: "festival-42".into(),
9804 application_data: "main-stage".into(),
9805 }],
9806 };
9807 let client_messages = [
9808 (
9809 ControlMessage::CreateConferenceResponse(CreateConferenceResponse {
9810 conference_id: 42.into(),
9811 result: CreateConferenceResult::Ok,
9812 passthrough_data: vec![1, 2, 3],
9813 }),
9814 wire_id::CREATE_CONFERENCE_RES,
9815 16,
9816 ),
9817 (
9818 ControlMessage::DeleteConferenceResponse {
9819 conference_id: 42.into(),
9820 result: DeleteConferenceResult::Ok,
9821 },
9822 wire_id::DELETE_CONFERENCE_RES,
9823 8,
9824 ),
9825 (
9826 ControlMessage::ModifyConferenceResponse(ModifyConferenceResponse {
9827 conference_id: 42.into(),
9828 result: ModifyConferenceResult::Ok,
9829 passthrough_data: vec![4, 5],
9830 }),
9831 wire_id::MODIFY_CONFERENCE_RES,
9832 16,
9833 ),
9834 (
9835 ControlMessage::AuditConferenceResponse(audit),
9836 wire_id::AUDIT_CONFERENCE_RES,
9837 84,
9838 ),
9839 ];
9840 for (message, expected_id, expected_payload_len) in client_messages {
9841 let frame = FrameDecoder::new()
9842 .push(&message.encode(ProtocolVersion::V22).unwrap())
9843 .unwrap()
9844 .remove(0);
9845 assert_eq!(frame.message_id, expected_id);
9846 assert_eq!(frame.payload.len(), expected_payload_len);
9847 assert_eq!(
9848 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9849 message
9850 );
9851 }
9852 }
9853
9854 #[test]
9855 fn conference_participant_messages_and_application_changes_round_trip() {
9856 let participant = ConferenceParticipant {
9857 call_reference: 100.into(),
9858 presentation_restrictions: PartyInformationRestrictions::CALLING_NUMBER
9859 | PartyInformationRestrictions::LAST_REDIRECT_NAME,
9860 name: "Festival Caller".into(),
9861 number: "1001".into(),
9862 conference_name: "Main Stage".into(),
9863 };
9864 let server_messages = [
9865 (
9866 ControlMessage::AddParticipantRequest(AddParticipantRequest {
9867 conference_id: 42.into(),
9868 participant: participant.clone(),
9869 }),
9870 wire_id::ADD_PARTICIPANT_REQ,
9871 108,
9872 ),
9873 (
9874 ControlMessage::DropParticipantRequest {
9875 conference_id: 42.into(),
9876 call_reference: 100.into(),
9877 },
9878 wire_id::DROP_PARTICIPANT_REQ,
9879 8,
9880 ),
9881 (
9882 ControlMessage::AuditParticipantRequest {
9883 conference_id: 42.into(),
9884 },
9885 wire_id::AUDIT_PARTICIPANT_REQ,
9886 4,
9887 ),
9888 ];
9889 for (message, expected_id, expected_payload_len) in server_messages {
9890 let frame = FrameDecoder::new()
9891 .push(&message.encode(ProtocolVersion::V22).unwrap())
9892 .unwrap()
9893 .remove(0);
9894 assert_eq!(frame.message_id, expected_id);
9895 assert_eq!(frame.payload.len(), expected_payload_len);
9896 assert_eq!(
9897 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9898 message
9899 );
9900 }
9901
9902 let client_messages = [
9903 (
9904 ControlMessage::AddParticipantResponse(AddParticipantResponse {
9905 conference_id: 42.into(),
9906 call_reference: 100.into(),
9907 result: AddParticipantResult::Ok,
9908 bridge_participant_id: BoundedBytes::try_from(vec![3; 257]).unwrap(),
9909 }),
9910 wire_id::ADD_PARTICIPANT_RES,
9911 272,
9912 ),
9913 (
9914 ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
9915 result: AuditParticipantResult::Ok,
9916 last: 1,
9917 conference_id: 42.into(),
9918 number_of_entries: 2,
9919 participant_entries: vec![1, 2, 3, 4],
9920 }),
9921 wire_id::AUDIT_PARTICIPANT_RES,
9922 20,
9923 ),
9924 ];
9925 for (message, expected_id, expected_payload_len) in client_messages {
9926 let frame = FrameDecoder::new()
9927 .push(&message.encode(ProtocolVersion::V22).unwrap())
9928 .unwrap()
9929 .remove(0);
9930 assert_eq!(frame.message_id, expected_id);
9931 assert_eq!(frame.payload.len(), expected_payload_len);
9932 assert_eq!(
9933 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
9934 message
9935 );
9936 }
9937
9938 let change = ConferenceParticipantChange {
9939 conference_id: 42.into(),
9940 participant,
9941 };
9942 let routing = ParticipantChangeRouting {
9943 application_id: 7.into(),
9944 line_instance: 1,
9945 transaction_id: 9.into(),
9946 sequence_flag: 1,
9947 display_priority: 2,
9948 application_instance_id: 3.into(),
9949 routing: 4,
9950 };
9951 let envelope = change.to_user_data_v1(routing).unwrap();
9952 assert_eq!(envelope.data.len(), 108);
9953 assert_eq!(
9954 ConferenceParticipantChange::from_user_data_v1(&envelope).unwrap(),
9955 change
9956 );
9957
9958 let mut mismatched = envelope;
9959 mismatched.conference_id += 1;
9960 assert!(matches!(
9961 ConferenceParticipantChange::from_user_data_v1(&mismatched),
9962 Err(CodecError::InvalidValue {
9963 field: "participant change conference ID",
9964 ..
9965 })
9966 ));
9967 }
9968
9969 #[test]
9970 fn participant_messages_enforce_text_and_audit_bounds() {
9971 let oversized = ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
9972 result: AuditParticipantResult::Ok,
9973 last: 1,
9974 conference_id: 42.into(),
9975 number_of_entries: 1,
9976 participant_entries: vec![0; 257],
9977 });
9978 assert!(matches!(
9979 oversized.encode(ProtocolVersion::V22),
9980 Err(CodecError::CountTooLarge {
9981 field: "participant audit data",
9982 count: 257,
9983 maximum: 256,
9984 ..
9985 })
9986 ));
9987
9988 let mut oversized_payload = vec![0; 16 + 257];
9989 oversized_payload[8..12].copy_from_slice(&42_u32.to_le_bytes());
9990 assert!(matches!(
9991 ControlMessage::decode(
9992 Frame::new(22, wire_id::AUDIT_PARTICIPANT_RES, oversized_payload),
9993 ProtocolVersion::V22,
9994 ),
9995 Err(CodecError::CountTooLarge {
9996 field: "participant audit data",
9997 count: 257,
9998 maximum: 256,
9999 ..
10000 })
10001 ));
10002
10003 let long_name = ControlMessage::AddParticipantRequest(AddParticipantRequest {
10004 conference_id: 42.into(),
10005 participant: ConferenceParticipant {
10006 call_reference: 100.into(),
10007 presentation_restrictions: PartyInformationRestrictions::empty(),
10008 name: "x".repeat(40),
10009 number: "1001".into(),
10010 conference_name: "Main Stage".into(),
10011 },
10012 });
10013 assert!(matches!(
10014 long_name.encode(ProtocolVersion::V22),
10015 Err(CodecError::TextTooLong {
10016 field: "participant name",
10017 actual: 40,
10018 maximum: 39,
10019 ..
10020 })
10021 ));
10022 }
10023
10024 #[test]
10025 fn multicast_media_layouts_cover_legacy_and_extended_addresses() {
10026 let acknowledgement = ClientMessage::MulticastMediaReceptionAck {
10027 status: MediaStatus::Ok,
10028 passthrough_party_id: 9.into(),
10029 call_reference: 7.into(),
10030 };
10031 let frame = FrameDecoder::new()
10032 .push(&acknowledgement.encode(ProtocolVersion::V3).unwrap())
10033 .unwrap()
10034 .remove(0);
10035 assert_eq!(frame.message_id, wire_id::MULTICAST_MEDIA_RECEPTION_ACK);
10036 assert_eq!(frame.payload.len(), 12);
10037 assert_eq!(ClientMessage::decode(frame).unwrap(), acknowledgement);
10038
10039 let reception_v3 = ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
10040 conference_id: 42.into(),
10041 passthrough_party_id: 9.into(),
10042 call_reference: 7.into(),
10043 address: "239.1.2.3".parse().unwrap(),
10044 port: 16_000,
10045 packet_millis: 20,
10046 codec: Codec::Pcmu,
10047 echo_cancellation: EchoCancellation::On,
10048 g723_bitrate: G723BitRate::Rate6_3,
10049 });
10050 let transmission_v3 =
10051 ServerMessage::StartMulticastMediaTransmission(MulticastMediaTransmission {
10052 conference_id: 42.into(),
10053 passthrough_party_id: 9.into(),
10054 call_reference: 7.into(),
10055 address: "239.1.2.3".parse().unwrap(),
10056 port: 16_002,
10057 packet_millis: 20,
10058 codec: Codec::Pcmu,
10059 precedence: 5,
10060 silence_suppression: 1,
10061 max_frames_per_packet: 2,
10062 g723_bitrate: G723BitRate::Rate5_3,
10063 });
10064 for (message, expected_id, expected_payload_len) in [
10065 (reception_v3, wire_id::START_MULTICAST_MEDIA_RECEPTION, 36),
10066 (
10067 transmission_v3,
10068 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
10069 44,
10070 ),
10071 ] {
10072 let frame = FrameDecoder::new()
10073 .push(&message.encode(ProtocolVersion::V3).unwrap())
10074 .unwrap()
10075 .remove(0);
10076 assert_eq!(frame.message_id, expected_id);
10077 assert_eq!(frame.payload.len(), expected_payload_len);
10078 assert_eq!(
10079 ServerMessage::decode(frame, ProtocolVersion::V3).unwrap(),
10080 message
10081 );
10082 }
10083
10084 let reception_v17 = ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
10085 conference_id: 43.into(),
10086 passthrough_party_id: 10.into(),
10087 call_reference: 8.into(),
10088 address: "ff3e::1234".parse().unwrap(),
10089 port: 17_000,
10090 packet_millis: 30,
10091 codec: Codec::Pcma,
10092 echo_cancellation: EchoCancellation::Unknown(7),
10093 g723_bitrate: G723BitRate::Unknown(9),
10094 });
10095 let transmission_v17 =
10096 ServerMessage::StartMulticastMediaTransmission(MulticastMediaTransmission {
10097 conference_id: 43.into(),
10098 passthrough_party_id: 10.into(),
10099 call_reference: 8.into(),
10100 address: "ff3e::1234".parse().unwrap(),
10101 port: 17_002,
10102 packet_millis: 30,
10103 codec: Codec::Pcma,
10104 precedence: 6,
10105 silence_suppression: 2,
10106 max_frames_per_packet: 3,
10107 g723_bitrate: G723BitRate::Unknown(9),
10108 });
10109 for (message, expected_id, expected_payload_len) in [
10110 (reception_v17, wire_id::START_MULTICAST_MEDIA_RECEPTION, 52),
10111 (
10112 transmission_v17,
10113 wire_id::START_MULTICAST_MEDIA_TRANSMISSION,
10114 60,
10115 ),
10116 ] {
10117 let frame = FrameDecoder::new()
10118 .push(&message.encode(ProtocolVersion::V17).unwrap())
10119 .unwrap()
10120 .remove(0);
10121 assert_eq!(frame.message_id, expected_id);
10122 assert_eq!(frame.payload.len(), expected_payload_len);
10123 assert_eq!(
10124 ServerMessage::decode(frame, ProtocolVersion::V17).unwrap(),
10125 message
10126 );
10127 }
10128
10129 for (message, expected_id) in [
10130 (
10131 ServerMessage::StopMulticastMediaReception {
10132 conference_id: 42.into(),
10133 passthrough_party_id: 9.into(),
10134 call_reference: 100.into(),
10135 },
10136 wire_id::STOP_MULTICAST_MEDIA_RECEPTION,
10137 ),
10138 (
10139 ServerMessage::StopMulticastMediaTransmission {
10140 conference_id: 42.into(),
10141 passthrough_party_id: 9.into(),
10142 call_reference: 100.into(),
10143 },
10144 wire_id::STOP_MULTICAST_MEDIA_TRANSMISSION,
10145 ),
10146 ] {
10147 let frame = FrameDecoder::new()
10148 .push(&message.encode(ProtocolVersion::V22).unwrap())
10149 .unwrap()
10150 .remove(0);
10151 assert_eq!(frame.message_id, expected_id);
10152 assert_eq!(frame.payload.len(), 12);
10153 assert_eq!(
10154 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10155 message
10156 );
10157 }
10158 }
10159
10160 #[test]
10161 fn legacy_multicast_rejects_ipv6_and_invalid_ports() {
10162 let ipv6 = ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
10163 conference_id: 42.into(),
10164 passthrough_party_id: 9.into(),
10165 call_reference: 7.into(),
10166 address: "ff3e::1234".parse().unwrap(),
10167 port: 16_000,
10168 packet_millis: 20,
10169 codec: Codec::Pcmu,
10170 echo_cancellation: EchoCancellation::On,
10171 g723_bitrate: G723BitRate::Rate6_3,
10172 });
10173 assert!(matches!(
10174 ipv6.encode(ProtocolVersion::V15),
10175 Err(CodecError::InvalidValue {
10176 field: "IP address family for pre-v17 protocol",
10177 ..
10178 })
10179 ));
10180
10181 let mut payload = vec![0; 52];
10182 payload[8..12].copy_from_slice(&1_u32.to_le_bytes());
10183 payload[28..32].copy_from_slice(&70_000_u32.to_le_bytes());
10184 assert!(matches!(
10185 ServerMessage::decode(
10186 Frame::new(17, wire_id::START_MULTICAST_MEDIA_RECEPTION, payload),
10187 ProtocolVersion::V17,
10188 ),
10189 Err(CodecError::InvalidValue {
10190 field: "multicast port",
10191 value: 70_000,
10192 ..
10193 })
10194 ));
10195 }
10196
10197 #[test]
10198 fn qos_control_messages_use_field_typed_service_layouts() {
10199 let flow = QosFlow {
10200 conference_id: 42.into(),
10201 call_reference: 7.into(),
10202 passthrough_party_id: 9.into(),
10203 address: "192.0.2.20".parse().unwrap(),
10204 port: 16_000,
10205 };
10206 let traffic = QosTrafficSpecification {
10207 codec: Codec::Pcmu,
10208 average_bit_rate: 64_000,
10209 burst_size: 1_200,
10210 peak_rate: 128_000,
10211 };
10212 let application = QosApplicationIdentifier {
10213 vendor_id: "Cisco".into(),
10214 version: "1".into(),
10215 application_name: "SCCP audio".into(),
10216 sub_application_id: "primary".into(),
10217 };
10218 let messages = [
10219 ControlMessage::QosReservationNotify {
10220 flow,
10221 direction: QosDirection::Send,
10222 },
10223 ControlMessage::QosErrorNotify {
10224 flow,
10225 direction: QosDirection::Send,
10226 error_code: QosErrorCode::ListenFailed,
10227 failure_node: "198.51.100.9".parse().unwrap(),
10228 rsvp_error_code: RsvpErrorCode::NoSenderInformation,
10229 rsvp_error_subcode: 5,
10230 rsvp_error_flags: 6,
10231 },
10232 ControlMessage::QosListen {
10233 flow,
10234 reservation_style: QosReservationStyle::SharedExplicit,
10235 maximum_retries: 3,
10236 retry_timer: 4,
10237 confirmation_required: true,
10238 preemption_priority: 5,
10239 defending_priority: 6,
10240 traffic,
10241 application: application.clone(),
10242 },
10243 ControlMessage::QosPath {
10244 flow,
10245 reservation_style: QosReservationStyle::SharedExplicit,
10246 maximum_retries: 3,
10247 retry_timer: 4,
10248 preemption_priority: 5,
10249 defending_priority: 6,
10250 traffic,
10251 application: application.clone(),
10252 },
10253 ControlMessage::QosTeardown {
10254 flow,
10255 direction: QosDirection::Send,
10256 },
10257 ControlMessage::UpdateDscp { flow, dscp: 46 },
10258 ControlMessage::QosModify {
10259 flow,
10260 direction: QosDirection::Send,
10261 traffic,
10262 application,
10263 },
10264 ];
10265 for (message, expected_size) in messages.into_iter().zip([24, 44, 172, 168, 24, 24, 152]) {
10266 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10267 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10268 assert_eq!(frame.payload.len(), expected_size);
10269 assert_eq!(
10270 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10271 message
10272 );
10273 }
10274 }
10275
10276 #[test]
10277 fn fixed_layout_alignment_bytes_must_be_zero() {
10278 let register_ack = WireRegisterAck {
10279 keepalive_seconds: 30,
10280 date_template: *b"D/M/Y\0",
10281 alignment: [1, 0],
10282 secondary_keepalive_seconds: 30,
10283 protocol_features: [22, 0, 0, 0],
10284 };
10285 assert!(
10286 ServerMessage::decode(
10287 Frame::new(
10288 ProtocolVersion::V22.wire(),
10289 wire_id::REGISTER_ACK,
10290 encode(wire_id::REGISTER_ACK, ®ister_ack).unwrap(),
10291 ),
10292 ProtocolVersion::V22,
10293 )
10294 .is_err()
10295 );
10296
10297 let notification = WireMessageWaitingNotification {
10298 target_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "target", "1001").unwrap(),
10299 control_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "control", "5000")
10300 .unwrap(),
10301 alignment: [1, 0],
10302 messages_waiting: 1,
10303 total_voicemail_new: 0,
10304 total_voicemail_old: 0,
10305 priority_voicemail_new: 0,
10306 priority_voicemail_old: 0,
10307 total_fax_new: 0,
10308 total_fax_old: 0,
10309 priority_fax_new: 0,
10310 priority_fax_old: 0,
10311 };
10312 let notification = encode(wire_id::MWI_NOTIFICATION, ¬ification).unwrap();
10313 assert_eq!(notification.len(), 88);
10314 assert!(
10315 ControlMessage::decode(
10316 Frame::new(
10317 ProtocolVersion::V22.wire(),
10318 wire_id::MWI_NOTIFICATION,
10319 notification,
10320 ),
10321 ProtocolVersion::V22,
10322 )
10323 .is_err()
10324 );
10325
10326 let response = WireMessageWaitingResponse {
10327 target_number: WireFixedText::new(wire_id::MWI_RESPONSE, "target", "1001").unwrap(),
10328 alignment: [0, 1, 0],
10329 result: MessageWaitingResult::Ok.wire_value(),
10330 };
10331 let response = encode(wire_id::MWI_RESPONSE, &response).unwrap();
10332 assert_eq!(response.len(), 32);
10333 assert!(
10334 ControlMessage::decode(
10335 Frame::new(ProtocolVersion::V22.wire(), wire_id::MWI_RESPONSE, response,),
10336 ProtocolVersion::V22,
10337 )
10338 .is_err()
10339 );
10340
10341 let connection_statistics = WireConnectionStatisticsV19 {
10342 directory_number: WireAlignedText {
10343 value: WireFixedText::new(
10344 wire_id::CONNECTION_STATISTICS_RES,
10345 "directory number",
10346 "2002",
10347 )
10348 .unwrap(),
10349 alignment: [1, 0, 0],
10350 },
10351 call_reference: 42,
10352 processing: StatisticsProcessing::Clear.wire_value(),
10353 statistics: WireConnectionStatisticsTail {
10354 counters: WireConnectionStatisticsCounters {
10355 packets_sent: 0,
10356 octets_sent: 0,
10357 packets_received: 0,
10358 octets_received: 0,
10359 packets_lost: 0,
10360 jitter_millis: 0,
10361 latency_millis: 0,
10362 },
10363 quality_size: 0,
10364 },
10365 quality: Vec::new(),
10366 };
10367 assert!(
10368 ClientMessage::decode_with_version(
10369 Frame::new(
10370 ProtocolVersion::V20.wire(),
10371 wire_id::CONNECTION_STATISTICS_RES,
10372 encode(wire_id::CONNECTION_STATISTICS_RES, &connection_statistics).unwrap(),
10373 ),
10374 ProtocolVersion::V20,
10375 )
10376 .is_err()
10377 );
10378
10379 let mut enbloc = encode(
10380 wire_id::ENBLOC_CALL,
10381 &WireEnblocWithLine::<25, 0> {
10382 called_party: WireAlignedText::new(wire_id::ENBLOC_CALL, "called party", "2001")
10383 .unwrap(),
10384 line_instance: 2,
10385 },
10386 )
10387 .unwrap();
10388 enbloc.extend_from_slice(&[1, 0, 0]);
10389 assert!(
10390 ClientMessage::decode_with_version(
10391 Frame::new(ProtocolVersion::V19.wire(), wire_id::ENBLOC_CALL, enbloc),
10392 ProtocolVersion::V19,
10393 )
10394 .is_err()
10395 );
10396
10397 let mut off_hook = FrameDecoder::new()
10398 .push(
10399 &ClientMessage::OffHookWithCallingParty {
10400 calling_party_number: "2001".into(),
10401 voice_mailbox: "5000".into(),
10402 line_instance: 2,
10403 }
10404 .encode(ProtocolVersion::V19)
10405 .unwrap(),
10406 )
10407 .unwrap()
10408 .remove(0);
10409 off_hook.payload[50] = 1;
10410 assert!(ClientMessage::decode_with_version(off_hook, ProtocolVersion::V19).is_err());
10411
10412 let mut dialed = FrameDecoder::new()
10413 .push(
10414 &ServerMessage::DialedNumber {
10415 number: "2001".into(),
10416 line_instance: 2,
10417 call_reference: 42,
10418 }
10419 .encode(ProtocolVersion::V19)
10420 .unwrap(),
10421 )
10422 .unwrap()
10423 .remove(0);
10424 dialed.payload[25] = 1;
10425 assert!(ServerMessage::decode(dialed, ProtocolVersion::V19).is_err());
10426
10427 let mut forwarding = FrameDecoder::new()
10428 .push(
10429 &ServerMessage::ForwardStatus {
10430 line_instance: 2,
10431 forward_all: Some("2001".into()),
10432 forward_busy: None,
10433 forward_no_answer: None,
10434 }
10435 .encode(ProtocolVersion::V19)
10436 .unwrap(),
10437 )
10438 .unwrap()
10439 .remove(0);
10440 forwarding.payload[37] = 1;
10441 assert!(ServerMessage::decode(forwarding, ProtocolVersion::V19).is_err());
10442 }
10443
10444 #[test]
10445 fn boolean_control_words_reject_non_boolean_values() {
10446 let recording = encode(
10447 wire_id::RECORDING_STATUS,
10448 &WireRecordingStatus {
10449 call_reference: 7,
10450 active: 2,
10451 },
10452 )
10453 .unwrap();
10454 assert!(matches!(
10455 ServerMessage::decode(
10456 Frame::new(
10457 ProtocolVersion::V22.wire(),
10458 wire_id::RECORDING_STATUS,
10459 recording
10460 ),
10461 ProtocolVersion::V22,
10462 ),
10463 Err(CodecError::InvalidValue {
10464 field: "recording active",
10465 value: 2,
10466 ..
10467 })
10468 ));
10469
10470 let notification = WireMessageWaitingNotification {
10471 target_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "target", "1001").unwrap(),
10472 control_number: WireFixedText::new(wire_id::MWI_NOTIFICATION, "control", "5000")
10473 .unwrap(),
10474 alignment: [0; 2],
10475 messages_waiting: 2,
10476 total_voicemail_new: 0,
10477 total_voicemail_old: 0,
10478 priority_voicemail_new: 0,
10479 priority_voicemail_old: 0,
10480 total_fax_new: 0,
10481 total_fax_old: 0,
10482 priority_fax_new: 0,
10483 priority_fax_old: 0,
10484 };
10485 let payload = encode(wire_id::MWI_NOTIFICATION, ¬ification).unwrap();
10486 assert!(matches!(
10487 ControlMessage::decode(
10488 Frame::new(
10489 ProtocolVersion::V22.wire(),
10490 wire_id::MWI_NOTIFICATION,
10491 payload
10492 ),
10493 ProtocolVersion::V22,
10494 ),
10495 Err(CodecError::InvalidValue {
10496 field: "messages waiting",
10497 value: 2,
10498 ..
10499 })
10500 ));
10501
10502 let flow = QosFlow {
10503 conference_id: 1.into(),
10504 call_reference: 2.into(),
10505 passthrough_party_id: 3.into(),
10506 address: "192.0.2.1".parse().unwrap(),
10507 port: 16_000,
10508 };
10509 let traffic = QosTrafficSpecification {
10510 codec: Codec::Pcmu,
10511 average_bit_rate: 64_000,
10512 burst_size: 1_200,
10513 peak_rate: 128_000,
10514 };
10515 let application = QosApplicationIdentifier {
10516 vendor_id: "Cisco".into(),
10517 version: "1".into(),
10518 application_name: "SCCP audio".into(),
10519 sub_application_id: "primary".into(),
10520 };
10521 let mut qos_listen = FrameDecoder::new()
10522 .push(
10523 &ControlMessage::QosListen {
10524 flow,
10525 reservation_style: QosReservationStyle::SharedExplicit,
10526 maximum_retries: 3,
10527 retry_timer: 4,
10528 confirmation_required: true,
10529 preemption_priority: 5,
10530 defending_priority: 6,
10531 traffic,
10532 application,
10533 }
10534 .encode(ProtocolVersion::V22)
10535 .unwrap(),
10536 )
10537 .unwrap()
10538 .remove(0);
10539 qos_listen.payload[32..36].copy_from_slice(&2_u32.to_le_bytes());
10540 assert!(matches!(
10541 ControlMessage::decode(qos_listen, ProtocolVersion::V22),
10542 Err(CodecError::InvalidValue {
10543 field: "QoS confirmation required",
10544 value: 2,
10545 ..
10546 })
10547 ));
10548
10549 assert!(matches!(
10550 ControlMessage::UpdateDscp { flow, dscp: 64 }.encode(ProtocolVersion::V22),
10551 Err(CodecError::InvalidValue {
10552 field: "DSCP",
10553 value: 64,
10554 ..
10555 })
10556 ));
10557
10558 let invalid_dscp = encode(
10559 wire_id::UPDATE_DSCP,
10560 &WireUpdateDscp {
10561 flow: qos_flow_to_wire(flow),
10562 dscp: 64,
10563 },
10564 )
10565 .unwrap();
10566 assert!(matches!(
10567 ControlMessage::decode(
10568 Frame::new(
10569 ProtocolVersion::V22.wire(),
10570 wire_id::UPDATE_DSCP,
10571 invalid_dscp
10572 ),
10573 ProtocolVersion::V22,
10574 ),
10575 Err(CodecError::InvalidValue {
10576 field: "DSCP",
10577 value: 64,
10578 ..
10579 })
10580 ));
10581 }
10582
10583 #[test]
10584 fn compact_multimedia_dtmf_and_addon_layouts_round_trip_exactly() {
10585 let open_ack = OpenMultimediaReceiveChannelAck {
10586 status: MediaStatus::Ok,
10587 endpoint: MediaEndpointAddress {
10588 address: "2001:db8::20".parse().unwrap(),
10589 port: 16_000,
10590 },
10591 passthrough_party_id: 9.into(),
10592 call_reference: 7.into(),
10593 };
10594 let start_ack = StartMultimediaTransmissionAck {
10595 conference_id: 42.into(),
10596 passthrough_party_id: 9.into(),
10597 call_reference: 7.into(),
10598 endpoint: MediaEndpointAddress {
10599 address: "2001:db8::20".parse().unwrap(),
10600 port: 16_000,
10601 },
10602 status: MediaStatus::Ok,
10603 };
10604 for (message, expected_len) in [
10605 (ClientMessage::OpenMultimediaReceiveChannelAck(open_ack), 48),
10606 (ClientMessage::StartMultimediaTransmissionAck(start_ack), 52),
10607 ] {
10608 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10609 assert_eq!(bytes.len(), expected_len);
10610 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10611 assert_eq!(ClientMessage::decode(frame).unwrap(), message);
10612 }
10613
10614 let addon = ClientMessage::ExtensionDeviceCapabilities(ExtensionDeviceCapabilities {
10615 unknown_1: 1,
10616 unknown_2: 2,
10617 unknown_3: 3,
10618 description: "7914 sidecar".into(),
10619 });
10620 let bytes = addon.encode(ProtocolVersion::V22).unwrap();
10621 assert_eq!(bytes.len(), 176);
10622 assert_eq!(
10623 ClientMessage::decode(FrameDecoder::new().push(&bytes).unwrap().remove(0)).unwrap(),
10624 addon
10625 );
10626
10627 let dtmf = DtmfToneControl {
10628 tone: Tone::Dtmf5,
10629 conference_id: 42.into(),
10630 passthrough_party_id: 9,
10631 };
10632 for message in [
10633 ServerMessage::NotifyDtmfTone(dtmf),
10634 ServerMessage::SendDtmfTone(dtmf),
10635 ] {
10636 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10637 assert_eq!(bytes.len(), 24);
10638 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10639 assert_eq!(
10640 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10641 message
10642 );
10643 }
10644
10645 let lifecycle = MultimediaStreamControl {
10646 conference_id: 42.into(),
10647 passthrough_party_id: 9.into(),
10648 call_reference: 7.into(),
10649 port_handling_flag: 1,
10650 };
10651 let flow = VideoFlowControl {
10652 conference_id: 42.into(),
10653 passthrough_party_id: 9.into(),
10654 call_reference: 7.into(),
10655 maximum_bit_rate: 512_000,
10656 };
10657 for message in [
10658 ServerMessage::StopMultimediaTransmission(lifecycle),
10659 ServerMessage::CloseMultimediaReceiveChannel(lifecycle),
10660 ServerMessage::FlowControlCommand(flow),
10661 ServerMessage::FlowControlNotify(flow),
10662 ] {
10663 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10664 assert_eq!(bytes.len(), 28);
10665 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10666 assert_eq!(
10667 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10668 message
10669 );
10670 }
10671 let display = ServerMessage::VideoDisplayCommand {
10672 conference_id: 42.into(),
10673 call_reference: 7.into(),
10674 layout_id: 2,
10675 };
10676 let bytes = display.encode(ProtocolVersion::V22).unwrap();
10677 assert_eq!(bytes.len(), 24);
10678 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10679 assert_eq!(
10680 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10681 display
10682 );
10683
10684 let failure_detection = ServerMessage::StartMediaFailureDetection(MediaFailureDetection {
10685 conference_id: 42.into(),
10686 passthrough_party_id: 9,
10687 packet_millis: 20,
10688 codec: Codec::Pcmu,
10689 echo_cancellation: EchoCancellation::On,
10690 codec_qualifier: [1, 2, 3, 4],
10691 call_reference: 7.into(),
10692 });
10693 let bytes = failure_detection.encode(ProtocolVersion::V22).unwrap();
10694 assert_eq!(bytes.len(), 40);
10695 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10696 assert_eq!(
10697 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10698 failure_detection
10699 );
10700
10701 let dynamic = ServerMessage::ConfigStatus(ConfigurationStatus {
10702 device_name: "SEP001122334455".into(),
10703 station_user_id: 0xfeed,
10704 station_instance: 2,
10705 line_count: 6,
10706 speed_dial_count: 12,
10707 user_name: "festival".into(),
10708 server_name: "sccp.example.test".into(),
10709 });
10710 let bytes = dynamic.encode(ProtocolVersion::V22).unwrap();
10711 assert_eq!(bytes.len() % 4, 0);
10712 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10713 assert_eq!(
10714 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10715 dynamic
10716 );
10717 }
10718
10719 #[test]
10720 fn soft_key_template_keeps_cisco_event_positions() {
10721 let bytes = ServerMessage::SoftKeyTemplate {
10722 actions: SoftKeyProfile::default().template_actions(),
10723 }
10724 .encode(ProtocolVersion::V22)
10725 .unwrap();
10726 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10727 let payload: WireSoftKeyTemplate = decode(frame.message_id, &frame.payload).unwrap();
10728 assert_eq!(payload.count, 32);
10729 assert_eq!(payload.definitions[31].event, 32);
10730 assert_eq!(
10731 payload
10732 .definitions
10733 .iter()
10734 .map(|definition| definition.event)
10735 .collect::<Vec<_>>(),
10736 (1..=32).collect::<Vec<_>>()
10737 );
10738 }
10739
10740 #[test]
10741 fn line_only_button_template_preserves_the_fixed_wire_layout() {
10742 let message = ServerMessage::ButtonTemplate {
10743 offset: 0,
10744 total: 1,
10745 buttons: vec![ButtonTemplateEntry {
10746 instance: 1,
10747 button_type: ButtonType::Line,
10748 }],
10749 };
10750 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10751 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10752 let payload: WireButtonTemplate = decode(frame.message_id, &frame.payload).unwrap();
10753
10754 assert_eq!(payload.offset, 0);
10755 assert_eq!(payload.count, 1);
10756 assert_eq!(payload.total, 1);
10757 assert_eq!(
10758 payload.definitions[0],
10759 WireButtonDefinition {
10760 instance: 1,
10761 button_type: ButtonType::Line.wire_value() as u8,
10762 }
10763 );
10764 assert!(
10765 payload.definitions[1..]
10766 .iter()
10767 .all(|definition| *definition == WireButtonDefinition::default())
10768 );
10769 assert_eq!(
10770 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10771 message
10772 );
10773 }
10774
10775 #[test]
10776 fn mixed_button_template_round_trips_ordered_semantic_entries() {
10777 let message = ServerMessage::ButtonTemplate {
10778 offset: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10779 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 + 6,
10780 buttons: vec![
10781 ButtonTemplateEntry {
10782 instance: 1,
10783 button_type: ButtonType::Line,
10784 },
10785 ButtonTemplateEntry {
10786 instance: 2,
10787 button_type: ButtonType::SpeedDial,
10788 },
10789 ButtonTemplateEntry {
10790 instance: 3,
10791 button_type: ButtonType::DoNotDisturb,
10792 },
10793 ButtonTemplateEntry {
10794 instance: 4,
10795 button_type: ButtonType::ServiceUrl,
10796 },
10797 ButtonTemplateEntry {
10798 instance: 0,
10799 button_type: ButtonType::Unused,
10800 },
10801 ButtonTemplateEntry {
10802 instance: 5,
10803 button_type: ButtonType::BlfSpeedDial,
10804 },
10805 ],
10806 };
10807
10808 let bytes = message.encode(ProtocolVersion::V22).unwrap();
10809 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10810 assert_eq!(
10811 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
10812 message
10813 );
10814 }
10815
10816 #[test]
10817 fn button_template_rejects_unrepresentable_entries_and_counts() {
10818 let too_many = ServerMessage::ButtonTemplate {
10819 offset: 0,
10820 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 + 1,
10821 buttons: vec![
10822 ButtonTemplateEntry {
10823 instance: 1,
10824 button_type: ButtonType::Line,
10825 };
10826 BUTTON_TEMPLATE_ENTRIES_PER_CHUNK + 1
10827 ],
10828 };
10829 assert!(matches!(
10830 too_many.encode(ProtocolVersion::V22),
10831 Err(CodecError::CountTooLarge { .. })
10832 ));
10833
10834 let large_instance = ServerMessage::ButtonTemplate {
10835 offset: 0,
10836 total: 1,
10837 buttons: vec![ButtonTemplateEntry {
10838 instance: 256,
10839 button_type: ButtonType::Line,
10840 }],
10841 };
10842 assert!(matches!(
10843 large_instance.encode(ProtocolVersion::V22),
10844 Err(CodecError::InvalidValue {
10845 field: "button instance",
10846 ..
10847 })
10848 ));
10849
10850 let payload = WireButtonTemplate {
10851 offset: 0,
10852 count: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32 + 1,
10853 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10854 definitions: [WireButtonDefinition::default(); BUTTON_TEMPLATE_ENTRIES_PER_CHUNK],
10855 };
10856 let frame = Frame::new(
10857 ProtocolVersion::V22.wire(),
10858 wire_id::BUTTON_TEMPLATE,
10859 encode(wire_id::BUTTON_TEMPLATE, &payload).unwrap(),
10860 );
10861 assert!(matches!(
10862 ServerMessage::decode(frame, ProtocolVersion::V22),
10863 Err(CodecError::CountTooLarge {
10864 field: "button definitions in message",
10865 ..
10866 })
10867 ));
10868
10869 let payload = WireButtonTemplate {
10870 offset: 1,
10871 count: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10872 total: BUTTON_TEMPLATE_ENTRIES_PER_CHUNK as u32,
10873 definitions: [WireButtonDefinition::default(); BUTTON_TEMPLATE_ENTRIES_PER_CHUNK],
10874 };
10875 let frame = Frame::new(
10876 ProtocolVersion::V22.wire(),
10877 wire_id::BUTTON_TEMPLATE,
10878 encode(wire_id::BUTTON_TEMPLATE, &payload).unwrap(),
10879 );
10880 assert!(matches!(
10881 ServerMessage::decode(frame, ProtocolVersion::V22),
10882 Err(CodecError::InvalidValue {
10883 field: "button template range",
10884 ..
10885 })
10886 ));
10887 }
10888
10889 #[test]
10890 fn station_statuses_select_and_round_trip_dynamic_layouts() {
10891 let cases = [
10892 (
10893 ServerMessage::LineStatus {
10894 instance: 3,
10895 number: "1003".into(),
10896 display_name: "A dynamic line label".into(),
10897 },
10898 ProtocolVersion::V8,
10899 wire_id::LINE_STAT,
10900 ),
10901 (
10902 ServerMessage::LineStatus {
10903 instance: 3,
10904 number: "1003".into(),
10905 display_name: "A dynamic line label".into(),
10906 },
10907 ProtocolVersion::V9,
10908 wire_id::LINE_STAT_DYNAMIC,
10909 ),
10910 (
10911 ServerMessage::SpeedDialStatus {
10912 instance: 4,
10913 number: "2004".into(),
10914 display_name: "Warehouse".into(),
10915 },
10916 ProtocolVersion::V8,
10917 wire_id::SPEED_DIAL_STAT,
10918 ),
10919 (
10920 ServerMessage::SpeedDialStatus {
10921 instance: 4,
10922 number: "2004".into(),
10923 display_name: "Warehouse".into(),
10924 },
10925 ProtocolVersion::V9,
10926 wire_id::SPEED_DIAL_STAT_DYNAMIC,
10927 ),
10928 (
10929 ServerMessage::FeatureStatus {
10930 instance: 5,
10931 button_type: ButtonType::DoNotDisturb,
10932 label: "Do not disturb".into(),
10933 state: 0x0002_0101,
10934 },
10935 ProtocolVersion::V22,
10936 wire_id::FEATURE_STAT,
10937 ),
10938 (
10939 ServerMessage::ServiceUrlStatus {
10940 index: 6,
10941 url: "http://services.invalid/directory".into(),
10942 label: "Directory".into(),
10943 extension_text: String::new(),
10944 },
10945 ProtocolVersion::V8,
10946 wire_id::SERVICE_URL_STAT,
10947 ),
10948 (
10949 ServerMessage::ServiceUrlStatus {
10950 index: 6,
10951 url: "http://services.invalid/directory".into(),
10952 label: "Directory".into(),
10953 extension_text: String::new(),
10954 },
10955 ProtocolVersion::V9,
10956 wire_id::SERVICE_URL_STAT_DYNAMIC,
10957 ),
10958 ];
10959
10960 for (message, protocol, expected_id) in cases {
10961 let bytes = message.encode(protocol).unwrap();
10962 assert_eq!(bytes.len() % 4, 0, "message 0x{expected_id:04x}");
10963 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10964 assert_eq!(frame.message_id, expected_id);
10965 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
10966 }
10967
10968 let message = ServerMessage::FeatureStatus {
10969 instance: 5,
10970 button_type: ButtonType::DoNotDisturb,
10971 label: "Do not disturb".into(),
10972 state: 0x0002_0101,
10973 };
10974 let session =
10975 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES);
10976 let bytes = message.encode_for_session(session).unwrap();
10977 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
10978 assert_eq!(frame.message_id, wire_id::FEATURE_STAT_DYNAMIC);
10979 assert_eq!(
10980 ServerMessage::decode(frame, ProtocolVersion::V8).unwrap(),
10981 message
10982 );
10983 }
10984
10985 #[test]
10986 fn configuration_status_is_lossless_across_session_selected_layouts() {
10987 let message = ServerMessage::ConfigStatus(ConfigurationStatus {
10988 device_name: "SEP001122334455".into(),
10989 station_user_id: 17,
10990 station_instance: 2,
10991 line_count: 6,
10992 speed_dial_count: 12,
10993 user_name: "festival".into(),
10994 server_name: "sccp.example.test".into(),
10995 });
10996 for (session, expected_id) in [
10997 (
10998 StationSessionContext::from(ProtocolVersion::V8),
10999 wire_id::CONFIG_STAT,
11000 ),
11001 (
11002 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES),
11003 wire_id::CONFIG_STAT_DYNAMIC,
11004 ),
11005 (
11006 StationSessionContext::from(ProtocolVersion::V9),
11007 wire_id::CONFIG_STAT_DYNAMIC,
11008 ),
11009 ] {
11010 let bytes = message.encode_for_session(session).unwrap();
11011 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11012 assert_eq!(frame.message_id, expected_id);
11013 assert_eq!(
11014 ServerMessage::decode(frame, session.protocol).unwrap(),
11015 message
11016 );
11017 }
11018 }
11019
11020 #[test]
11021 fn speed_dial_status_uses_session_selection_and_variable_wire_layout() {
11022 let message = ServerMessage::SpeedDialStatus {
11023 instance: 4,
11024 number: "2004".into(),
11025 display_name: "Warehouse".into(),
11026 };
11027 for (session, expected_id) in [
11028 (
11029 StationSessionContext::from(ProtocolVersion::V8),
11030 wire_id::SPEED_DIAL_STAT,
11031 ),
11032 (
11033 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES),
11034 wire_id::SPEED_DIAL_STAT_DYNAMIC,
11035 ),
11036 (
11037 StationSessionContext::from(ProtocolVersion::V9),
11038 wire_id::SPEED_DIAL_STAT_DYNAMIC,
11039 ),
11040 ] {
11041 let bytes = message.encode_for_session(session).unwrap();
11042 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11043 assert_eq!(frame.message_id, expected_id);
11044 assert_eq!(
11045 ServerMessage::decode(frame, session.protocol).unwrap(),
11046 message
11047 );
11048 }
11049
11050 let bytes = ServerMessage::SpeedDialStatus {
11051 instance: 2,
11052 number: "2001".into(),
11053 display_name: "Reception".into(),
11054 }
11055 .encode(ProtocolVersion::V9)
11056 .unwrap();
11057 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11058 assert_eq!(frame.message_id, wire_id::SPEED_DIAL_STAT_DYNAMIC);
11059 assert_eq!(
11060 frame.payload,
11061 [
11062 0x02, 0x00, 0x00, 0x00, b'2', b'0', b'0', b'1', 0x00, b'R', b'e', b'c', b'e', b'p',
11063 b't', b'i', b'o', b'n', 0x00, 0x00,
11064 ]
11065 );
11066 }
11067
11068 #[test]
11069 fn dynamic_call_information_uses_the_versioned_string_count() {
11070 let message = ServerMessage::CallInfo {
11071 info: CallInfo {
11072 direction: crate::types::CallDirection::Inbound,
11073 calling_name: "Alice".into(),
11074 calling_number: "1001".into(),
11075 called_name: "Bob".into(),
11076 called_number: "2001".into(),
11077 original_called_name: "Carol".into(),
11078 original_called_number: "3001".into(),
11079 last_redirecting_name: "Dave".into(),
11080 last_redirecting_number: "4001".into(),
11081 original_redirect_reason: 2,
11082 last_redirect_reason: 4,
11083 party_restrictions: 0,
11084 },
11085 line_instance: 2,
11086 call_reference: 42,
11087 };
11088
11089 for (protocol, count) in [
11090 (ProtocolVersion::V15, 12),
11091 (ProtocolVersion::V16, 13),
11092 (ProtocolVersion::V18, 13),
11093 (ProtocolVersion::V19, 15),
11094 ] {
11095 let bytes = message.encode(protocol).unwrap();
11096 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11097 assert_eq!(frame.message_id, wire_id::CALL_INFO_DYNAMIC);
11098 assert_eq!(
11099 decode_dynamic_texts(frame.message_id, &frame.payload, 32, count)
11100 .unwrap()
11101 .len(),
11102 count
11103 );
11104 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
11105 }
11106 }
11107
11108 #[test]
11109 fn dynamic_service_status_adds_the_extension_field_from_version_nineteen() {
11110 let unsupported = ServerMessage::ServiceUrlStatus {
11111 index: 3,
11112 url: "http://services.invalid/directory".into(),
11113 label: "Directory".into(),
11114 extension_text: "extension".into(),
11115 };
11116 assert!(matches!(
11117 unsupported.encode(ProtocolVersion::V18),
11118 Err(CodecError::InvalidValue {
11119 field: "service URL extension for this protocol version",
11120 ..
11121 })
11122 ));
11123
11124 let before = ServerMessage::ServiceUrlStatus {
11125 index: 3,
11126 url: "http://services.invalid/directory".into(),
11127 label: "Directory".into(),
11128 extension_text: String::new(),
11129 };
11130 let bytes = before.encode(ProtocolVersion::V18).unwrap();
11131 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11132 assert_eq!(
11133 decode_dynamic_texts(frame.message_id, &frame.payload, 4, 2).unwrap(),
11134 ["http://services.invalid/directory", "Directory"]
11135 );
11136 assert_eq!(
11137 ServerMessage::decode(frame, ProtocolVersion::V18).unwrap(),
11138 before
11139 );
11140
11141 let from = ServerMessage::ServiceUrlStatus {
11142 index: 3,
11143 url: "http://services.invalid/directory".into(),
11144 label: "Directory".into(),
11145 extension_text: "extension".into(),
11146 };
11147 let bytes = from.encode(ProtocolVersion::V19).unwrap();
11148 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11149 assert_eq!(
11150 decode_dynamic_texts(frame.message_id, &frame.payload, 4, 3).unwrap(),
11151 [
11152 "http://services.invalid/directory",
11153 "Directory",
11154 "extension"
11155 ]
11156 );
11157 assert_eq!(
11158 ServerMessage::decode(frame, ProtocolVersion::V19).unwrap(),
11159 from
11160 );
11161 }
11162
11163 #[test]
11164 fn dynamic_7961_line_status_has_cisco_word_padding() {
11165 let bytes = ServerMessage::LineStatus {
11166 instance: 1,
11167 number: "1006".into(),
11168 display_name: "1006".into(),
11169 }
11170 .encode(ProtocolVersion::V22)
11171 .unwrap();
11172 assert_eq!(bytes.len(), 36);
11173 assert_eq!(&bytes[..4], &28_u32.to_le_bytes());
11174 assert_eq!(
11175 &bytes[12..],
11176 b"\x01\0\0\0\x0f\0\0\x001006\x001006\x001006\0\0"
11177 );
11178 }
11179
11180 #[test]
11181 fn dynamic_station_decoders_reject_missing_or_nonzero_word_padding() {
11182 let bytes = ServerMessage::LineStatus {
11183 instance: 1,
11184 number: "1006".into(),
11185 display_name: "1006".into(),
11186 }
11187 .encode(ProtocolVersion::V22)
11188 .unwrap();
11189 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11190
11191 let mut missing_padding = frame.clone();
11192 missing_padding.payload.pop();
11193 assert!(matches!(
11194 ServerMessage::decode(missing_padding, ProtocolVersion::V22),
11195 Err(CodecError::InvalidAlignment { actual: 23, .. })
11196 ));
11197
11198 let mut nonzero_padding = frame.clone();
11199 *nonzero_padding.payload.last_mut().unwrap() = 0x7f;
11200 assert!(matches!(
11201 ServerMessage::decode(nonzero_padding, ProtocolVersion::V22),
11202 Err(CodecError::TrailingBytes { count: 1, .. })
11203 ));
11204
11205 let mut extension = frame;
11206 extension.payload.extend_from_slice(&[0; 4]);
11207 assert!(matches!(
11208 ServerMessage::decode(extension, ProtocolVersion::V22),
11209 Err(CodecError::TrailingBytes { count: 5, .. })
11210 ));
11211 }
11212
11213 #[test]
11214 fn dynamic_display_decoders_validate_the_same_padding_contract() {
11215 let bytes = ServerMessage::DisplayNotify {
11216 timeout_seconds: 4,
11217 text: "status".into(),
11218 }
11219 .encode(ProtocolVersion::V22)
11220 .unwrap();
11221 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11222 assert_eq!(frame.payload.len() % 4, 0);
11223
11224 let mut bad = frame;
11225 bad.payload.extend_from_slice(&[0, 0, 0, 1]);
11226 assert!(matches!(
11227 ServerMessage::decode(bad, ProtocolVersion::V22),
11228 Err(CodecError::TrailingBytes { .. })
11229 ));
11230 }
11231
11232 #[test]
11233 fn legacy_station_labels_use_the_configured_single_byte_code_page() {
11234 let message = ServerMessage::LineStatus {
11235 instance: 1,
11236 number: "1001".into(),
11237 display_name: "Räksmörgås".into(),
11238 };
11239 let latin1 = message
11240 .encode_for_legacy_station(ProtocolVersion::V3, LegacyCodePage::Iso8859_1)
11241 .unwrap();
11242 let latin1 = FrameDecoder::new().push(&latin1).unwrap().remove(0);
11243 let expected = b"R\xe4ksm\xf6rg\xe5s";
11244 assert!(
11245 latin1
11246 .payload
11247 .windows(expected.len())
11248 .any(|bytes| bytes == expected)
11249 );
11250
11251 let ascii = message
11252 .encode_for_legacy_station(ProtocolVersion::V3, LegacyCodePage::Ascii)
11253 .unwrap();
11254 let ascii = FrameDecoder::new().push(&ascii).unwrap().remove(0);
11255 assert!(
11256 ascii
11257 .payload
11258 .windows(10)
11259 .any(|bytes| bytes == b"R?ksm?rg?s")
11260 );
11261
11262 let utf8 = message.encode(ProtocolVersion::V3).unwrap();
11263 let utf8 = FrameDecoder::new().push(&utf8).unwrap().remove(0);
11264 assert!(
11265 utf8.payload
11266 .windows(13)
11267 .any(|bytes| bytes == "Räksmörgås".as_bytes())
11268 );
11269 }
11270
11271 #[test]
11272 fn dynamic_station_statuses_support_extended_labels_and_require_terminators() {
11273 let label = "A label that is intentionally longer than the static forty-byte field";
11274 for message in [
11275 ServerMessage::LineStatus {
11276 instance: 1,
11277 number: "1001".into(),
11278 display_name: label.into(),
11279 },
11280 ServerMessage::ServiceUrlStatus {
11281 index: 3,
11282 url: "http://services.invalid/directory".into(),
11283 label: label.into(),
11284 extension_text: String::new(),
11285 },
11286 ] {
11287 let bytes = message.encode(ProtocolVersion::V17).unwrap();
11288 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11289 assert_eq!(
11290 ServerMessage::decode(frame, ProtocolVersion::V17).unwrap(),
11291 message
11292 );
11293 }
11294
11295 let feature = ServerMessage::FeatureStatus {
11296 instance: 2,
11297 button_type: ButtonType::DoNotDisturb,
11298 label: label.into(),
11299 state: 1,
11300 };
11301 let session =
11302 StationSessionContext::new(ProtocolVersion::V8, PhoneFeatures::DYNAMIC_MESSAGES);
11303 let bytes = feature.encode_for_session(session).unwrap();
11304 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11305 assert_eq!(frame.message_id, wire_id::FEATURE_STAT_DYNAMIC);
11306 assert_eq!(
11307 ServerMessage::decode(frame, ProtocolVersion::V8).unwrap(),
11308 feature
11309 );
11310
11311 let unterminated_service = Frame::new(
11312 ProtocolVersion::V17.wire(),
11313 wire_id::SERVICE_URL_STAT_DYNAMIC,
11314 [3_u32.to_le_bytes().as_slice(), b"x\0YZ"].concat(),
11315 );
11316 assert!(matches!(
11317 ServerMessage::decode(unterminated_service, ProtocolVersion::V17),
11318 Err(CodecError::Truncated { .. })
11319 ));
11320 }
11321
11322 #[test]
11323 fn call_info_layouts_preserve_redirecting_and_presentation_fields() {
11324 let info = CallInfo {
11325 direction: crate::types::CallDirection::Inbound,
11326 calling_name: "Festival Caller".into(),
11327 calling_number: "1001".into(),
11328 called_name: "Festival Phone".into(),
11329 called_number: "1006".into(),
11330 original_called_name: "Reception".into(),
11331 original_called_number: "1000".into(),
11332 last_redirecting_name: "Front Desk".into(),
11333 last_redirecting_number: "1002".into(),
11334 original_redirect_reason: 4,
11335 last_redirect_reason: 2,
11336 party_restrictions: 0xf,
11337 };
11338 for (protocol, expected_id) in [
11339 (ProtocolVersion::V3, wire_id::CALL_INFO),
11340 (ProtocolVersion::V8, wire_id::CALL_INFO),
11341 (ProtocolVersion::V16, wire_id::CALL_INFO_DYNAMIC),
11342 (ProtocolVersion::V22, wire_id::CALL_INFO_DYNAMIC),
11343 ] {
11344 let message = ServerMessage::CallInfo {
11345 info: info.clone(),
11346 line_instance: 1,
11347 call_reference: 42,
11348 };
11349 let bytes = message.encode(protocol).unwrap();
11350 assert_eq!(bytes.len() % 4, 0, "message 0x{expected_id:04x}");
11351 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11352 assert_eq!(frame.message_id, expected_id);
11353 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
11354 }
11355
11356 let bytes = ServerMessage::DisplayPrompt {
11357 timeout_seconds: 0,
11358 text: "From Festival Caller (1001)".into(),
11359 line_instance: 1,
11360 call_reference: 42,
11361 }
11362 .encode(ProtocolVersion::V22)
11363 .unwrap();
11364 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11365 assert_eq!(frame.message_id, wire_id::DISPLAY_DYNAMIC_PROMPT_STATUS);
11366 assert_eq!(
11367 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11368 ServerMessage::DisplayPrompt {
11369 timeout_seconds: 0,
11370 text: "From Festival Caller (1001)".into(),
11371 line_instance: 1,
11372 call_reference: 42,
11373 }
11374 );
11375 }
11376
11377 #[test]
11378 fn notification_frames_select_static_or_dynamic_layout_and_keep_priority_six() {
11379 for (protocol, expected_id) in [
11380 (ProtocolVersion::V3, wire_id::DISPLAY_PRIORITY_NOTIFY),
11381 (
11382 ProtocolVersion::V22,
11383 wire_id::DISPLAY_DYNAMIC_PRIORITY_NOTIFY,
11384 ),
11385 ] {
11386 let message = ServerMessage::DisplayPriorityNotify {
11387 timeout_seconds: 10,
11388 priority: NotificationPriority::Timed,
11389 text: "Status line".into(),
11390 };
11391 let bytes = message.encode(protocol).unwrap();
11392 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11393 assert_eq!(frame.message_id, expected_id);
11394 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
11395 }
11396
11397 let message = ServerMessage::DisplayNotify {
11398 timeout_seconds: 3,
11399 text: "Dynamic notification text longer than thirty-one bytes".into(),
11400 };
11401 let bytes = message.encode(ProtocolVersion::V22).unwrap();
11402 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11403 assert_eq!(frame.message_id, wire_id::DISPLAY_DYNAMIC_NOTIFY);
11404 assert_eq!(
11405 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11406 message
11407 );
11408 }
11409
11410 #[test]
11411 fn call_state_uses_cisco_visibility_then_precedence_layout() {
11412 let bytes = ServerMessage::CallState {
11413 state: CallState::RingIn,
11414 line_instance: 1,
11415 call_reference: 42,
11416 }
11417 .encode(ProtocolVersion::V22)
11418 .unwrap();
11419 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11420 let words = (0..frame.payload.len() / 4)
11421 .map(|index| {
11422 let offset = index * 4;
11423 u32::from_le_bytes([
11424 frame.payload[offset],
11425 frame.payload[offset + 1],
11426 frame.payload[offset + 2],
11427 frame.payload[offset + 3],
11428 ])
11429 })
11430 .collect::<Vec<_>>();
11431
11432 assert_eq!(
11433 words,
11434 vec![CallState::RingIn.wire_value(), 1, 42, 0, 2, 0],
11435 "CallState is state, line, call, visibility, priority, domain"
11436 );
11437
11438 for (state, expected) in [
11439 (CallState::OffHook, 3),
11440 (CallState::Proceed, 3),
11441 (CallState::Connected, 3),
11442 (CallState::RingOut, 4),
11443 ] {
11444 let bytes = ServerMessage::CallState {
11445 state,
11446 line_instance: 1,
11447 call_reference: 42,
11448 }
11449 .encode(ProtocolVersion::V22)
11450 .unwrap();
11451 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11452 assert_eq!(
11453 u32::from_le_bytes(frame.payload[16..20].try_into().unwrap()),
11454 expected,
11455 "wrong precedence for {state:?}"
11456 );
11457 }
11458 }
11459
11460 #[test]
11461 fn soft_key_sets_and_masks_only_advertise_implemented_actions() {
11462 let profile = SoftKeyProfile::default();
11463 let bytes = ServerMessage::SoftKeySet {
11464 profile: profile.clone(),
11465 }
11466 .encode(ProtocolVersion::V22)
11467 .unwrap();
11468 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11469 let payload: WireSoftKeySet = decode(frame.message_id, &frame.payload).unwrap();
11470
11471 assert_eq!(
11472 &payload.sets[KeyMode::RingIn.wire_value() as usize].template_indexes[..2],
11473 &[
11474 SoftKey::Answer.wire_value() as u8,
11475 SoftKey::EndCall.wire_value() as u8
11476 ]
11477 );
11478 assert_eq!(profile.valid_mask(KeyMode::RingIn), 0b11);
11479 assert_eq!(profile.valid_mask(KeyMode::Connected), 0b111);
11480 assert_eq!(profile.valid_mask(KeyMode::Empty), 0);
11481 }
11482
11483 #[test]
11484 fn configured_soft_key_set_round_trips_order_and_empty_modes() {
11485 let profile = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
11486 let actions = match mode {
11487 KeyMode::OnHook => vec![SoftKey::Redial, SoftKey::NewCall],
11488 KeyMode::Connected => vec![SoftKey::EndCall, SoftKey::Hold],
11489 _ => Vec::new(),
11490 };
11491 (mode, actions)
11492 }))
11493 .unwrap();
11494 let message = ServerMessage::SoftKeySet {
11495 profile: profile.clone(),
11496 };
11497 let bytes = message.encode(ProtocolVersion::V22).unwrap();
11498 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11499 let payload: WireSoftKeySet = decode(frame.message_id, &frame.payload).unwrap();
11500
11501 assert_eq!(
11502 &payload.sets[KeyMode::OnHook.wire_value() as usize].template_indexes[..3],
11503 &[
11504 SoftKey::Redial.wire_value() as u8,
11505 SoftKey::NewCall.wire_value() as u8,
11506 0,
11507 ]
11508 );
11509 assert_eq!(
11510 &payload.sets[KeyMode::Connected.wire_value() as usize].template_indexes[..3],
11511 &[
11512 SoftKey::EndCall.wire_value() as u8,
11513 SoftKey::Hold.wire_value() as u8,
11514 0,
11515 ]
11516 );
11517 assert_eq!(
11518 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11519 message
11520 );
11521 assert_eq!(profile.valid_mask(KeyMode::OnHook), 0b11);
11522 assert_eq!(profile.valid_mask(KeyMode::RingIn), 0);
11523
11524 let template = ServerMessage::SoftKeyTemplate {
11525 actions: profile.template_actions(),
11526 };
11527 let bytes = template.encode(ProtocolVersion::V22).unwrap();
11528 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
11529 let payload: WireSoftKeyTemplate = decode(frame.message_id, &frame.payload).unwrap();
11530 assert_eq!(payload.definitions[0].event, SoftKey::Redial.wire_value());
11531 assert_eq!(payload.definitions[2].event, SoftKey::Hold.wire_value());
11532 assert_eq!(payload.definitions[3].event, 0);
11533 assert_eq!(
11534 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11535 template
11536 );
11537 }
11538
11539 #[test]
11540 fn nominally_empty_requests_accept_bounded_extensions() {
11541 for message_id in [
11542 wire_id::CONFIG_STAT_REQ,
11543 wire_id::TIME_DATE_REQ,
11544 wire_id::VERSION_REQ,
11545 wire_id::SERVER_REQ,
11546 wire_id::SOFT_KEY_SET_REQ,
11547 wire_id::SOFT_KEY_TEMPLATE_REQ,
11548 ] {
11549 ClientMessage::decode(Frame::new(22, message_id, 34_u32.to_le_bytes().to_vec()))
11550 .unwrap();
11551 }
11552 }
11553
11554 #[test]
11555 fn dtmf_payload_messages_use_their_structural_word_layouts() {
11556 let identity = DtmfPayloadIdentity {
11557 payload_type: 101,
11558 conference_id: 0x1122_3344,
11559 passthrough_party_id: 0x5566_7788,
11560 };
11561 let request = DtmfPayloadRequest {
11562 payload_type: identity.payload_type,
11563 conference_id: identity.conference_id,
11564 passthrough_party_id: identity.passthrough_party_id,
11565 dtmf_type: 2,
11566 };
11567 let identity_payload = [
11568 identity.payload_type.to_le_bytes(),
11569 identity.conference_id.to_le_bytes(),
11570 identity.passthrough_party_id.to_le_bytes(),
11571 ]
11572 .concat();
11573 let request_payload = [
11574 request.payload_type.to_le_bytes(),
11575 request.conference_id.to_le_bytes(),
11576 request.passthrough_party_id.to_le_bytes(),
11577 request.dtmf_type.to_le_bytes(),
11578 ]
11579 .concat();
11580
11581 for message in [
11582 ClientMessage::SubscribeDtmfPayloadResponse(identity),
11583 ClientMessage::UnsubscribeDtmfPayloadResponse(identity),
11584 ] {
11585 let frame = FrameDecoder::new()
11586 .push(&message.encode(ProtocolVersion::V22).unwrap())
11587 .unwrap()
11588 .remove(0);
11589 assert_eq!(frame.payload, identity_payload);
11590 assert_eq!(
11591 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
11592 message
11593 );
11594 }
11595
11596 for (message, expected_payload) in [
11597 (
11598 ServerMessage::SubscribeDtmfPayloadRequest(request),
11599 request_payload.as_slice(),
11600 ),
11601 (
11602 ServerMessage::SubscribeDtmfPayloadError(identity),
11603 identity_payload.as_slice(),
11604 ),
11605 (
11606 ServerMessage::UnsubscribeDtmfPayloadRequest(request),
11607 request_payload.as_slice(),
11608 ),
11609 (
11610 ServerMessage::UnsubscribeDtmfPayloadError(identity),
11611 identity_payload.as_slice(),
11612 ),
11613 ] {
11614 let frame = FrameDecoder::new()
11615 .push(&message.encode(ProtocolVersion::V22).unwrap())
11616 .unwrap()
11617 .remove(0);
11618 assert_eq!(frame.payload.as_slice(), expected_payload);
11619 assert_eq!(
11620 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11621 message
11622 );
11623 }
11624
11625 assert!(
11626 ClientMessage::decode_with_version(
11627 Frame::new(22, wire_id::SUBSCRIBE_DTMF_PAYLOAD_RES, vec![0; 13]),
11628 ProtocolVersion::V22,
11629 )
11630 .is_err()
11631 );
11632 assert!(
11633 ServerMessage::decode(
11634 Frame::new(22, wire_id::SUBSCRIBE_DTMF_PAYLOAD_REQ, vec![0; 17]),
11635 ProtocolVersion::V22,
11636 )
11637 .is_err()
11638 );
11639 }
11640
11641 #[test]
11642 fn add_participant_response_preserves_progressive_identifier_bytes() {
11643 for identifier_len in [0, 1, 64, 256] {
11644 let identifier = (0..identifier_len)
11645 .map(|index| (index as u8).wrapping_mul(17).wrapping_add(3))
11646 .collect::<Vec<_>>();
11647 let mut payload = [
11648 42_u32.to_le_bytes(),
11649 100_u32.to_le_bytes(),
11650 0_u32.to_le_bytes(),
11651 ]
11652 .concat();
11653 payload.extend_from_slice(&identifier);
11654 let decoded = ControlMessage::decode(
11655 Frame::new(22, wire_id::ADD_PARTICIPANT_RES, payload),
11656 ProtocolVersion::V22,
11657 )
11658 .unwrap();
11659 let ControlMessage::AddParticipantResponse(response) = &decoded else {
11660 panic!("expected add-participant response");
11661 };
11662 assert_eq!(response.bridge_participant_id.as_bytes(), identifier);
11663 let frame = FrameDecoder::new()
11664 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
11665 .unwrap()
11666 .remove(0);
11667 assert_eq!(frame.payload.len(), 272);
11668 assert_eq!(&frame.payload[12..12 + identifier_len], identifier);
11669 assert!(
11670 frame.payload[12 + identifier_len..]
11671 .iter()
11672 .all(|byte| *byte == 0)
11673 );
11674 }
11675
11676 let identifier = (0..257)
11677 .map(|index| (index as u8).wrapping_mul(17).wrapping_add(3))
11678 .collect::<Vec<_>>();
11679 let canonical = ControlMessage::AddParticipantResponse(AddParticipantResponse {
11680 conference_id: 42.into(),
11681 call_reference: 100.into(),
11682 result: AddParticipantResult::Ok,
11683 bridge_participant_id: BoundedBytes::try_from(identifier).unwrap(),
11684 });
11685 let frame = FrameDecoder::new()
11686 .push(&canonical.encode(ProtocolVersion::V22).unwrap())
11687 .unwrap()
11688 .remove(0);
11689 assert_eq!(frame.payload.len(), 272);
11690 assert_eq!(
11691 ControlMessage::decode(frame, ProtocolVersion::V22).unwrap(),
11692 canonical
11693 );
11694
11695 for invalid_len in [270, 271, 273] {
11696 assert!(
11697 ControlMessage::decode(
11698 Frame::new(22, wire_id::ADD_PARTICIPANT_RES, vec![0; invalid_len]),
11699 ProtocolVersion::V22,
11700 )
11701 .is_err()
11702 );
11703 }
11704 let mut invalid_alignment = vec![0; 272];
11705 invalid_alignment[271] = 1;
11706 assert!(
11707 ControlMessage::decode(
11708 Frame::new(22, wire_id::ADD_PARTICIPANT_RES, invalid_alignment),
11709 ProtocolVersion::V22,
11710 )
11711 .is_err()
11712 );
11713 }
11714
11715 #[test]
11716 fn xml_alarm_accepts_and_preserves_every_bounded_frame_form() {
11717 for payload_len in [0, 1, 2_000, 2_004, 2_048] {
11718 let payload = (0..payload_len)
11719 .map(|index| (index as u8).wrapping_mul(29).wrapping_add(1))
11720 .collect::<Vec<_>>();
11721 let decoded = ClientMessage::decode_with_version(
11722 Frame::new(22, wire_id::XML_ALARM, payload.clone()),
11723 ProtocolVersion::V22,
11724 )
11725 .unwrap();
11726 let ClientMessage::XmlAlarm(message) = &decoded else {
11727 panic!("expected XML alarm");
11728 };
11729 assert_eq!(message.wire_payload(), payload.as_slice());
11730 let frame = FrameDecoder::new()
11731 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
11732 .unwrap()
11733 .remove(0);
11734 assert_eq!(frame.payload, payload);
11735 }
11736
11737 assert!(matches!(
11738 ClientMessage::decode_with_version(
11739 Frame::new(22, wire_id::XML_ALARM, vec![0; 2_049]),
11740 ProtocolVersion::V22,
11741 ),
11742 Err(CodecError::CountTooLarge {
11743 message_id: wire_id::XML_ALARM,
11744 count: 2_049,
11745 maximum: 2_048,
11746 ..
11747 })
11748 ));
11749
11750 let with_suffix =
11751 XmlAlarmMessage::from_wire_payload(b"<alarm/>\0ignored".to_vec()).unwrap();
11752 assert_eq!(with_suffix.xml_bytes(), b"<alarm/>");
11753 assert_eq!(with_suffix.wire_payload(), b"<alarm/>\0ignored");
11754
11755 let canonical = XmlAlarmMessage::from_xml(vec![b'x'; 2_000]).unwrap();
11756 assert_eq!(canonical.xml_bytes().len(), 2_000);
11757 assert_eq!(canonical.wire_payload().len(), 2_004);
11758 assert!(XmlAlarmMessage::from_xml(vec![b'x'; 2_001]).is_err());
11759 }
11760
11761 #[test]
11762 fn xml_alarm_preserves_bounded_wire_payload() {
11763 let xml = "<?xml version=\"1.0\"?><x-cisco-alarm></x-cisco-alarm>";
11764 let mut payload = vec![0; 2_000];
11765 payload[..xml.len()].copy_from_slice(xml.as_bytes());
11766
11767 let decoded =
11768 ClientMessage::decode(Frame::new(0, wire_id::XML_ALARM, payload.clone())).unwrap();
11769 let ClientMessage::XmlAlarm(message) = &decoded else {
11770 panic!("expected XML alarm");
11771 };
11772 assert_eq!(message.xml_bytes(), xml.as_bytes());
11773 assert_eq!(message.wire_payload(), payload);
11774 let frame = FrameDecoder::new()
11775 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
11776 .unwrap()
11777 .remove(0);
11778 assert_eq!(frame.payload, payload);
11779 }
11780
11781 #[test]
11782 fn location_information_uses_text_storage_followed_by_zero_alignment() {
11783 let maximum = "x".repeat(2_400);
11784 let encoded = ClientMessage::LocationInfo {
11785 xml: maximum.clone(),
11786 }
11787 .encode(ProtocolVersion::V22)
11788 .unwrap();
11789 let frame = FrameDecoder::new().push(&encoded).unwrap().remove(0);
11790 assert_eq!(frame.payload.len(), 2_404);
11791 assert_eq!(&frame.payload[2_400..], &[0, 0, 0, 0]);
11792 assert_eq!(
11793 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
11794 ClientMessage::LocationInfo { xml: maximum }
11795 );
11796
11797 assert!(matches!(
11798 ClientMessage::LocationInfo {
11799 xml: "x".repeat(2_401),
11800 }
11801 .encode(ProtocolVersion::V22),
11802 Err(CodecError::TextTooLong {
11803 message_id: wire_id::LOCATION_INFO,
11804 maximum: 2_400,
11805 ..
11806 })
11807 ));
11808
11809 let mut nonzero_alignment = vec![0; 2_404];
11810 nonzero_alignment[2_401] = 1;
11811 assert!(matches!(
11812 ClientMessage::decode_with_version(
11813 Frame::new(22, wire_id::LOCATION_INFO, nonzero_alignment),
11814 ProtocolVersion::V22,
11815 ),
11816 Err(CodecError::InvalidValue {
11817 message_id: wire_id::LOCATION_INFO,
11818 field: "reserved payload byte",
11819 ..
11820 })
11821 ));
11822 }
11823
11824 #[test]
11825 fn decodes_7961_button_template_request_with_payload() {
11826 assert_eq!(
11827 ClientMessage::decode(Frame::new(
11828 22,
11829 wire_id::BUTTON_TEMPLATE_REQ,
11830 34_u32.to_le_bytes().to_vec(),
11831 ))
11832 .unwrap(),
11833 ClientMessage::ButtonTemplateRequest
11834 );
11835 }
11836}