Skip to main content

sccp_protocol/message/
codec.rs

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