Skip to main content

sccp_protocol/message/
codec.rs

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