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