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