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