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