Skip to main content

sccp_protocol/
server.rs

1//! Stateful SCCP station-session boundary.
2//!
3//! A successful socket write or TCP acknowledgement proves only transport
4//! delivery. Media and other correlated operations remain provisional until
5//! the station sends the matching SCCP response. Every new media transaction
6//! gets a fresh nonzero wire token (a deliberately coupled ORC/SMT pair shares
7//! one), and the session accepts an acknowledgement only for the exact
8//! direction and current request generation. The zero-party acknowledgement
9//! fallback is limited to the first generation, where the
10//! stable call reference still makes it unambiguous; reopened media fails
11//! closed instead of letting an old acknowledgement settle a new request.
12//! Deadlines retire that same generation before a late response is considered.
13//! Handset presentation, receive media, transmit media, and call ownership are
14//! therefore separate states rather than one broad "connected" flag.
15//!
16//! One explicit wire exception writes OpenReceiveChannel and
17//! StartMediaTransmission together for coupled outbound NAT media. A matching
18//! successful receive acknowledgement atomically settles both halves and
19//! emits [`DeviceEventKind::TransmitChannelImplied`]; ordinary staged media never infers
20//! transmit success from a receive acknowledgement.  Close, failure, timeout,
21//! disconnect, and replacement paths retire the coupled transaction.
22//!
23//! # Runtime workflow
24//!
25//! [`Server::bind`] creates a plain TCP listener, while
26//! [`Server::with_ingress`] lets a transport owner inject clear or already
27//! decrypted streams through [`ServerIngress`]. Both constructors return a
28//! [`ServerHandle`] for commands and a bounded [`Event`] receiver for handset
29//! input and session outcomes. The caller must run [`Server::run`] for any of
30//! those channels to make progress and should consume events continuously so a
31//! full event queue cannot apply backpressure to station sessions.
32//!
33//! A station becomes addressable by [`Command`] only after registration has
34//! selected a configured [`DeviceDefinition`] and emitted
35//! [`DeviceEventKind::Registered`]. Call adapters reserve or receive a
36//! [`CallId`], send commands through the handle, and react to correlated media
37//! acknowledgements delivered as device events. [`ServerHandle::send`] confirms
38//! queue admission; [`ServerHandle::send_confirmed`] additionally waits for the
39//! complete encoded command to reach the station stream. Neither operation
40//! substitutes for a protocol acknowledgement when the command defines one.
41//!
42//! Reconfiguration replaces definitions atomically and disconnects only the
43//! sessions named by [`ReconfigureResult`] or explicitly marked as affected.
44//! [`ServerHandle::shutdown`] asks the run loop to disconnect all sessions and
45//! finish. Dropping every handle also closes the command channel and causes the
46//! same orderly exit.
47
48mod qos;
49mod transport;
50
51pub use qos::{
52    SignalingSocket, SocketQosFailure, SocketQosMark, SocketQosPolicy, SocketQosReport,
53    StationSocketQos, apply_socket_qos,
54};
55pub use transport::{ServerIngress, StationIo};
56
57use std::collections::{HashMap, HashSet};
58use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
59use std::num::NonZeroU16;
60use std::sync::atomic::{AtomicU64, Ordering};
61use std::sync::{Arc, RwLock};
62use std::time::{Duration, SystemTime, UNIX_EPOCH};
63
64use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
65use thiserror::Error;
66use tokio::io::{AsyncReadExt, AsyncWriteExt};
67use tokio::net::TcpListener;
68#[cfg(test)]
69use tokio::net::TcpStream;
70use tokio::sync::{Mutex, mpsc, oneshot};
71use tokio::time::Instant;
72use tracing::{debug, info, warn};
73
74use crate::message::capabilities::StationMediaCapabilities;
75use crate::message::values::{
76    AlarmSeverity, BusyLampFieldState, ButtonType, CallHistoryDisposition, CallState, Codec,
77    CodecKind, DeviceType, Digit, DtmfMode, EchoCancellation, EncryptionCapability, G723BitRate,
78    IpAddressType, KeyMode, LampMode, MediaStatus, MicrophoneMode, MiscCommandType,
79    NotificationPriority, PhoneFeatures, ProtocolVersion, ReceiveTransmit, ResetType, RingDuration,
80    RingerMode, SilenceSuppression, SoftKey, SpeakerMode, StationSessionContext,
81    StatisticsProcessing, Stimulus, SubscriptionCause, Tone, ToneDirection,
82};
83use crate::message::wire::{CodecError, FrameDecoder};
84use crate::message::{
85    AnnouncementEntry, AudioStreamControl, BoundedBytes, ButtonTemplateEntry, ClientMessage,
86    ConnectionStatistics, MediaEncryption, MediaEndpointAddress, MediaRequestIdentity,
87    MediaRequestToken, MiscellaneousCommand, MulticastMediaReception, MulticastMediaTransmission,
88    MultimediaPayload, MultimediaPayloadDirection, MultimediaStreamControl, OpenMultimediaChannel,
89    ServerMessage, SignalingServerEndpoint,
90    StartMultimediaTransmission as MultimediaTransmissionStart, UserDataV1Message,
91    VideoFlowControl,
92};
93#[cfg(test)]
94use crate::message::{ControlMessage, MediaCapability, XmlAlarmMessage};
95use crate::phone::service::{
96    PhoneServiceEvent, PhoneServiceExtendedRouting, PhoneServiceMessageKind, PhoneServicePayload,
97    PhoneServiceRouting, parse_phone_service_payload,
98};
99#[cfg(test)]
100use crate::phone::xml::{
101    self as phone_xml, CiscoIpPhoneGraphicFileMenu, CiscoIpPhoneImageFile, CiscoIpPhoneInputItem,
102    CiscoIpPhoneKeyItem, CiscoIpPhoneSoftKeyItem, CiscoIpPhoneStatus, CiscoIpPhoneStatusFile,
103    CiscoIpPhoneTouchAreaMenuItem, PHONE_EXECUTE_MAX_ITEMS, PHONE_STATUS_BITMAP_MAX_BYTES,
104    PhoneBackgroundHttpUrl, PhoneBitmapData, PhoneExecutePriority, PhoneImageUrl, PhoneInputFlags,
105    PhoneInputParameterName, PhoneRingtoneUrl, PhoneTouchArea, PhoneXmlKey,
106};
107use crate::phone::xml::{
108    CiscoIpPhoneExecute, CiscoIpPhoneExecuteItem, CiscoIpPhoneInput, CiscoIpPhoneMenu,
109    CiscoIpPhoneMenuItem, CiscoIpPhoneSetBackground, CiscoIpPhoneSetBackgroundPreview,
110    CiscoIpPhoneSetRingTone, CiscoIpPhoneText, ConferenceListAction, ConferenceListDocument,
111    ConferenceListEntry, ConferenceMenuFamily, ConferenceParticipantActionsDocument,
112    PHONE_BACKGROUND_APPLICATION_ID, PHONE_EXECUTE_MAX_BYTES, PHONE_IMAGE_MAX_BYTES,
113    PHONE_INPUT_MAX_BYTES, PHONE_RINGTONE_APPLICATION_ID, PHONE_STATUS_MAX_BYTES,
114    PHONE_TEXT_APPLICATION_ID, PHONE_TEXT_LEGACY_MAX_CHARS, PhoneAlarmTelemetry,
115    PhoneBackgroundControlDocument, PhoneImageDocument, PhoneLocationTelemetry,
116    PhoneServicePriority, PhoneStatusDocument, PhoneXmlError, parse_phone_alarm,
117    parse_phone_location,
118};
119use crate::types::SignalingQos;
120use crate::types::{
121    ApplicationId, AudioProcessingPolicy, BlfCallerInfo, BlfState, ButtonDefinition, CallId,
122    CallInfo, CallReference, ConferenceId, DEFAULT_AUDIO_MAX_FRAMES_PER_PACKET,
123    DEFAULT_AUDIO_PACKET_MS, DeviceDefinition, DeviceId, DeviceRegistration, LineAppearance,
124    LineDefinition, LineInstance, MediaEndpoint, MediaTrafficClass, ParticipantId,
125    PassthroughPartyId, SessionGeneration, SoftKeyProfile, StationTransport,
126    StationTransportRequirement, TransactionId,
127};
128use transport::AcceptedStation;
129
130const EVENT_CAPACITY: usize = 1024;
131const COMMAND_CAPACITY: usize = 1024;
132const SESSION_COMMAND_CAPACITY: usize = 256;
133const SESSION_ACCEPT_CAPACITY: usize = 128;
134/// Maximum time a phone may leave an ordering-sensitive media command
135/// unacknowledged before the call owner is notified and the stale correlation
136/// state is retired.
137pub const HANDSET_ACKNOWLEDGEMENT_TIMEOUT: Duration = Duration::from_secs(5);
138/// Bound for the writer acknowledgement used to serialize commands whose
139/// resources must remain owned until their complete frame reaches the socket.
140pub const ORDERING_ACKNOWLEDGEMENT_TIMEOUT: Duration = Duration::from_secs(5);
141// A 79x1 normally follows the active accessory's Off event with OnHook within
142// a few dozen milliseconds.  A route change instead reports the replacement
143// accessory On immediately.  Keep the release pending long enough to
144// distinguish those two transactions when firmware omits the final OnHook.
145const MEDIA_PATH_RELEASE_GRACE: Duration = Duration::from_millis(150);
146// A timeout only releases pending correlation state; statistics are never polled.
147const CONNECTION_STATISTICS_TIMEOUT: Duration = Duration::from_secs(10);
148const MAX_PENDING_CONNECTION_STATISTICS: usize = 32;
149// Retired references prevent a late reply from binding to a replacement call.
150const MAX_STATISTICS_REFERENCES_PER_SESSION: usize = 4096;
151const PARKING_APPLICATION_ID: u32 = 9090;
152/// Shortest retry delay accepted for a rejected registration token.
153pub const MIN_REGISTRATION_BACKOFF: Duration = Duration::from_secs(30);
154/// Longest retry delay accepted for a rejected registration token.
155pub const MAX_REGISTRATION_BACKOFF: Duration = Duration::from_secs(86_400);
156/// Maximum number of parked calls rendered in one station selection menu.
157///
158/// Higher-level parking state may contain more calls; callers select and order
159/// the bounded subset passed to [`CommandAction::ShowParkingMenu`].
160pub const PARKING_MENU_MAX_ITEMS: usize = 32;
161
162/// One selectable parked call rendered by the station parking application.
163///
164/// `slot` is the stable parking-space identifier returned by a subsequent
165/// [`DeviceEventKind::ParkingMenuSelection`]. The caller and connected-party
166/// fields are presentation text and do not participate in selection identity.
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct ParkingMenuEntry {
169    pub slot: u32,
170    pub caller_name: String,
171    pub caller_number: String,
172    pub connected_name: String,
173    pub connected_number: String,
174}
175
176/// Audible presentation to apply to a newly offered incoming call.
177///
178/// Passing `None` to an incoming-offer method presents the call silently;
179/// [`IncomingRing::default`] selects an ordinary inside ring.
180#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub struct IncomingRing {
182    pub mode: RingerMode,
183    pub duration: RingDuration,
184}
185
186/// Device-wide do-not-disturb state rendered by configured feature buttons.
187#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub enum DoNotDisturbMode {
189    Off,
190    Silent,
191    Reject,
192}
193
194/// Behavior selected for one do-not-disturb feature button.
195#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
196pub enum DoNotDisturbButtonMode {
197    #[default]
198    Cycle,
199    Silent,
200    Reject,
201}
202
203impl Default for IncomingRing {
204    fn default() -> Self {
205        Self {
206            mode: RingerMode::Inside,
207            duration: RingDuration::Normal,
208        }
209    }
210}
211
212/// One fully correlated, privacy-safe station media snapshot retained independently of call
213/// teardown. The firmware-specific quality payload remains opaque and is represented only by its
214/// bounded byte count.
215#[derive(Clone, Debug, Eq, PartialEq)]
216pub struct MediaStatisticsSnapshot {
217    /// Monotonic generation assigned to the statistics request.
218    ///
219    /// A response is retained only when it matches the live request generation,
220    /// preventing a delayed response from replacing newer statistics.
221    pub request_generation: u64,
222    pub call_id: CallId,
223    pub line_instance: LineInstance,
224    pub codec: Codec,
225    pub packet_ms: u32,
226    pub max_frames_per_packet: u32,
227    pub receive_peer: Option<MediaEndpoint>,
228    pub transmit_peer: Option<MediaEndpoint>,
229    pub packets_sent: u32,
230    pub octets_sent: u32,
231    pub packets_received: u32,
232    pub octets_received: u32,
233    pub packets_lost: u32,
234    pub jitter_millis: u32,
235    pub latency_millis: u32,
236    /// Length of the bounded opaque quality report; its contents are not
237    /// retained in management state.
238    pub quality_byte_count: usize,
239}
240
241/// Device-wide status-line mutation.
242///
243/// The optional priority selects a protocol-specific notification plane. A
244/// clear with a priority removes only that plane; an unqualified clear removes
245/// the ordinary status message.
246#[derive(Clone, Debug, Eq, PartialEq)]
247pub enum HandsetStatusMessage {
248    Display {
249        text: String,
250        /// Zero keeps the message until another status mutation replaces it.
251        timeout_seconds: u8,
252        priority: Option<NotificationPriority>,
253    },
254    Clear {
255        priority: Option<NotificationPriority>,
256    },
257}
258
259/// Immutable policy shared by every session owned by one [`Server`].
260///
261/// Station definitions are supplied separately to the constructor and may be
262/// replaced at runtime through [`ServerHandle::reconfigure`].
263#[derive(Clone, Debug)]
264pub struct ServerConfig {
265    pub bind: SocketAddr,
266    /// Baseline marking applied before a station identifies itself. A device
267    /// definition may replace it for the remainder of that session.
268    pub signaling_qos: SignalingQos,
269    /// Configured fallback used only if the accepted socket does not have a
270    /// concrete local address. Normal server-list replies use the local
271    /// interface selected by the operating system for that connection.
272    pub advertised_address: Ipv4Addr,
273    /// IPv6 fallback for an unspecified accepted local socket.
274    pub advertised_ipv6_address: Option<Ipv6Addr>,
275    pub server_name: String,
276    /// Keepalive interval advertised to stations; session expiry uses a bounded
277    /// multiple of this interval.
278    pub keepalive_seconds: u32,
279    /// Keepalive interval advertised for sessions using a secondary server.
280    pub secondary_keepalive_seconds: u32,
281    /// Ordered failover endpoints. An empty list advertises only the endpoint
282    /// that accepted the current connection.
283    pub signaling_servers: Vec<SignalingServerRoute>,
284    /// Admission and retry policy for pre-registration token probes.
285    pub registration_tokens: RegistrationTokenPolicy,
286    pub firmware_version: String,
287    pub dial_terminator: Digit,
288    pub record_dial_terminator: bool,
289    pub call_answer_order: CallSelectionOrder,
290    /// Fixed station wall-clock offset from UTC. SCCP does not carry a named
291    /// timezone or daylight-saving transition table.
292    pub timezone_offset_minutes: i16,
293    pub date_template: crate::types::DateTemplate,
294    /// Optional policy-neutral station template for an otherwise unknown
295    /// guest-hotline registration. The dial destination remains owned by the
296    /// channel adapter and is never exposed in the handset definition.
297    pub anonymous_hotline: Option<AnonymousHotlineDefinition>,
298}
299
300/// One configured server and the ports available for each signaling transport.
301#[derive(Clone, Debug, Eq, PartialEq)]
302pub struct SignalingServerRoute {
303    pub priority: u8,
304    pub name: String,
305    pub address: IpAddr,
306    pub clear_port: Option<NonZeroU16>,
307    pub secure_port: Option<NonZeroU16>,
308}
309
310impl SignalingServerRoute {
311    fn endpoint(&self, transport: StationTransport) -> Option<SignalingServerEndpoint> {
312        let port = match transport {
313            StationTransport::Clear => self.clear_port,
314            StationTransport::Secure => self.secure_port,
315        }?;
316        Some(SignalingServerEndpoint {
317            name: self.name.clone(),
318            address: self.address,
319            port,
320        })
321    }
322}
323
324/// Decision applied to a pre-registration token probe after station and
325/// transport eligibility have been established.
326#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
327pub enum RegistrationFallback {
328    #[default]
329    Reject,
330    ReturnToPrimary,
331    DeviceIdOdd,
332    DeviceIdEven,
333}
334
335/// Policy applied when a station probes this server while registered elsewhere.
336#[derive(Clone, Debug, Eq, PartialEq)]
337pub struct RegistrationTokenPolicy {
338    pub fallback: RegistrationFallback,
339    pub backoff: Duration,
340    pub server_priority: u8,
341}
342
343impl Default for RegistrationTokenPolicy {
344    fn default() -> Self {
345        Self {
346            fallback: RegistrationFallback::Reject,
347            backoff: Duration::from_secs(60),
348            server_priority: 1,
349        }
350    }
351}
352
353impl RegistrationTokenPolicy {
354    fn accepts(&self, device_id: &DeviceId) -> bool {
355        let last_nibble = device_id
356            .as_str()
357            .strip_prefix("SEP")
358            .filter(|mac| mac.len() == 12 && mac.bytes().all(|byte| byte.is_ascii_hexdigit()))
359            .and_then(|mac| mac.as_bytes().last().copied())
360            .and_then(|byte| char::from(byte).to_digit(16));
361        match self.fallback {
362            RegistrationFallback::Reject => false,
363            RegistrationFallback::ReturnToPrimary => self.server_priority == 1,
364            RegistrationFallback::DeviceIdOdd => last_nibble.is_some_and(|value| value % 2 == 1),
365            RegistrationFallback::DeviceIdEven => last_nibble.is_some_and(|value| value % 2 == 0),
366        }
367    }
368}
369
370/// Restricted definition used to admit an otherwise unknown station as a
371/// single-line hotline device.
372///
373/// The server supplies only station-visible policy. Routing and authorization
374/// of the resulting off-hook event remain the adapter's responsibility.
375#[derive(Clone, Debug, Eq, PartialEq)]
376pub struct AnonymousHotlineDefinition {
377    label: String,
378}
379
380impl AnonymousHotlineDefinition {
381    /// Build a hotline template with a validated station-visible label.
382    ///
383    /// Labels must contain 1 through 79 bytes and no control characters.
384    pub fn new(label: impl Into<String>) -> Result<Self, ServerError> {
385        let label = label.into();
386        if label.is_empty() || label.len() > 79 || label.chars().any(char::is_control) {
387            return Err(ServerError::InvalidConfig(
388                "anonymous-hotline label must contain 1..=79 non-control bytes".into(),
389            ));
390        }
391        Ok(Self { label })
392    }
393
394    fn device_definition(&self, id: DeviceId) -> DeviceDefinition {
395        let soft_keys = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
396            let actions = match mode {
397                KeyMode::OnHook => vec![SoftKey::NewCall],
398                KeyMode::OffHook | KeyMode::RingOut => vec![SoftKey::EndCall],
399                _ => Vec::new(),
400            };
401            (mode, actions)
402        }))
403        .expect("minimal anonymous-hotline soft keys are valid");
404        DeviceDefinition {
405            id,
406            description: self.label.clone(),
407            transport: StationTransportRequirement::Either,
408            signaling_qos: None,
409            buttons: vec![ButtonDefinition::Line(LineAppearance::new(
410                1,
411                LineDefinition {
412                    number: "hotline".into(),
413                    display_name: self.label.clone(),
414                },
415            ))],
416            soft_keys,
417            ui: Default::default(),
418        }
419    }
420}
421
422/// Ordering used when a station asks to answer without identifying a call.
423#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
424pub enum CallSelectionOrder {
425    #[default]
426    OldestFirst,
427    LastFirst,
428}
429
430/// Network and framing policy for one station multicast audio direction.
431#[derive(Clone, Copy, Debug, Eq, PartialEq)]
432pub struct MulticastMediaRoute {
433    pub address: IpAddr,
434    pub port: u16,
435    pub codec: Codec,
436    pub packet_millis: u32,
437}
438
439/// Complete application-owned description of one station video receive flow.
440///
441/// Session-owned call, line, and request identities are deliberately absent.
442/// The opaque payload is accepted only when retained from a decoded receive
443/// message for the live session's protocol version.
444#[derive(Clone, Debug, Eq, PartialEq)]
445pub struct MultimediaReceiveDescriptor {
446    pub conference_id: ConferenceId,
447    pub payload: MultimediaPayload,
448    pub conference_creator: bool,
449    pub encryption: Option<MediaEncryption>,
450    pub stream_passthrough_id: u32,
451    pub associated_stream_id: u32,
452    pub source: MediaEndpointAddress,
453    pub requested_address_type: IpAddressType,
454}
455
456impl MultimediaReceiveDescriptor {
457    /// Rejects descriptors whose typed envelope cannot represent a video
458    /// receive flow. Session-specific protocol and station capabilities are
459    /// checked when the command is dispatched.
460    pub fn validate(self) -> Result<Self, ServerError> {
461        validate_multimedia_receive_descriptor(&self)?;
462        Ok(self)
463    }
464}
465
466/// Complete application-owned description of one station video transmit flow.
467///
468/// The opaque payload is accepted only when retained from a decoded transmit
469/// message for the live session's protocol version. Call and request identities
470/// remain session-owned.
471#[derive(Clone, Debug, Eq, PartialEq)]
472pub struct MultimediaTransmitDescriptor {
473    pub conference_id: ConferenceId,
474    pub endpoint: MediaEndpointAddress,
475    pub payload: MultimediaPayload,
476    /// Full traffic-class octet; configuration DSCP is shifted left by two.
477    pub traffic_class: MediaTrafficClass,
478    pub encryption: Option<MediaEncryption>,
479    pub stream_passthrough_id: u32,
480    pub associated_stream_id: u32,
481}
482
483impl MultimediaTransmitDescriptor {
484    /// Rejects descriptors whose typed envelope cannot represent a video
485    /// transmit flow. Live session policy is checked during dispatch.
486    pub fn validate(self) -> Result<Self, ServerError> {
487        validate_multimedia_transmit_descriptor(&self)?;
488        Ok(self)
489    }
490}
491
492/// Parameters for one command applied to an exact live station video encoder.
493///
494/// The server derives the wire command selector and fixed parameter area from
495/// these variants. Arbitrary parameter bytes are intentionally unavailable at
496/// this stateful command boundary.
497#[derive(Clone, Debug, Eq, PartialEq)]
498pub enum MultimediaTransmitControl {
499    FreezePicture,
500    FastPictureUpdate {
501        first_gob: u32,
502        gob_count: u32,
503    },
504    FastGobUpdate {
505        first_gob: u32,
506        gob_count: u32,
507    },
508    FastMacroblockUpdate {
509        first_gob: u32,
510        first_macroblock: u32,
511        macroblock_count: u32,
512    },
513    LostPicture {
514        picture_number: u32,
515        long_term_picture_index: u32,
516    },
517    LostPartialPicture {
518        picture_number: u32,
519        long_term_picture_index: u32,
520        first_macroblock: u32,
521        macroblock_count: u32,
522    },
523    /// Requests recovery from at most four prior picture references.
524    RecoveryReferencePicture {
525        pictures: VideoPictureReferences,
526    },
527    TemporalSpatialTradeoff {
528        value: u32,
529    },
530}
531
532/// Picture identity carried by recovery feedback.
533#[derive(Clone, Copy, Debug, Eq, PartialEq)]
534pub struct VideoPictureReference {
535    pub picture_number: u32,
536    pub long_term_picture_index: u32,
537}
538
539/// A recovery request's bounded ordered picture-reference list.
540#[derive(Clone, Debug, Eq, PartialEq)]
541pub struct VideoPictureReferences(Box<[VideoPictureReference]>);
542
543impl VideoPictureReferences {
544    /// Collects at most five items before rejecting a list beyond the wire
545    /// capacity, so even an unbounded iterator cannot cause unbounded storage.
546    pub fn new(
547        pictures: impl IntoIterator<Item = VideoPictureReference>,
548    ) -> Result<Self, ServerError> {
549        let pictures = pictures.into_iter().take(5).collect::<Vec<_>>();
550        if pictures.len() > 4 {
551            return Err(ServerError::InvalidMultimediaTransmitControl(
552                "recovery picture count exceeds four",
553            ));
554        }
555        Ok(Self(pictures.into_boxed_slice()))
556    }
557
558    /// Returns picture identities in wire order.
559    pub fn as_slice(&self) -> &[VideoPictureReference] {
560        &self.0
561    }
562}
563
564impl TryFrom<Vec<VideoPictureReference>> for VideoPictureReferences {
565    type Error = ServerError;
566
567    fn try_from(pictures: Vec<VideoPictureReference>) -> Result<Self, Self::Error> {
568        Self::new(pictures)
569    }
570}
571
572impl Default for ServerConfig {
573    fn default() -> Self {
574        Self {
575            bind: SocketAddr::from(([0, 0, 0, 0], 2000)),
576            signaling_qos: SignalingQos::default(),
577            advertised_address: Ipv4Addr::LOCALHOST,
578            advertised_ipv6_address: None,
579            server_name: "sccp-protocol".to_string(),
580            keepalive_seconds: 30,
581            secondary_keepalive_seconds: 30,
582            signaling_servers: Vec::new(),
583            registration_tokens: RegistrationTokenPolicy::default(),
584            firmware_version: String::new(),
585            dial_terminator: Digit::Pound,
586            record_dial_terminator: false,
587            call_answer_order: CallSelectionOrder::OldestFirst,
588            timezone_offset_minutes: 0,
589            date_template: Default::default(),
590            anonymous_hotline: None,
591        }
592    }
593}
594
595#[derive(Clone, Debug, Eq, PartialEq)]
596/// Output emitted by the running server to its integration adapter.
597///
598/// The receiver returned by a server constructor is the sole event stream for
599/// every accepted connection. Consumers should drain it continuously and use
600/// the device ID carried by [`Event::Device`] rather than inferring ownership
601/// from event order. They should also retain the generation from the latest
602/// registration and reject later-delivered events from replaced sessions.
603pub enum Event {
604    SessionError {
605        peer: SocketAddr,
606        error: String,
607    },
608    /// A malformed non-registration message was discarded while the session
609    /// remained usable.
610    ProtocolWarning {
611        peer: SocketAddr,
612        /// Registered device identity, or `None` before registration.
613        device_id: Option<DeviceId>,
614        message_id: u32,
615        error: String,
616    },
617    Device(DeviceEvent),
618}
619
620impl Event {
621    pub fn device(
622        device_id: DeviceId,
623        session_generation: SessionGeneration,
624        event: DeviceEventKind,
625    ) -> Self {
626        Self::Device(DeviceEvent::new(device_id, session_generation, event))
627    }
628}
629
630/// One station-scoped item in the server event stream.
631#[derive(Clone, Debug, Eq, PartialEq)]
632pub struct DeviceEvent {
633    pub device_id: DeviceId,
634    /// Identifies the connection that produced this event across reconnects.
635    pub session_generation: SessionGeneration,
636    pub event: DeviceEventKind,
637}
638
639impl DeviceEvent {
640    pub fn new(
641        device_id: DeviceId,
642        session_generation: SessionGeneration,
643        event: DeviceEventKind,
644    ) -> Self {
645        Self {
646            device_id,
647            session_generation,
648            event,
649        }
650    }
651}
652
653/// State transitions and handset input produced by one station session.
654///
655/// Call-bearing variants use the server-owned [`CallId`] rather than the raw
656/// wire reference. Media success and failure variants are emitted only after
657/// correlation against the current request generation.
658#[derive(Clone, Debug, Eq, PartialEq)]
659pub enum DeviceEventKind {
660    Registered(DeviceRegistration),
661    Disconnected {},
662    Capabilities {
663        capabilities: StationMediaCapabilities,
664    },
665    OffHook {
666        call_id: CallId,
667        line_instance: LineInstance,
668    },
669    OnHook {
670        call_id: CallId,
671        line_instance: LineInstance,
672    },
673    Digit {
674        call_id: CallId,
675        digit: Digit,
676    },
677    EnblocCall {
678        call_id: CallId,
679        line_instance: LineInstance,
680        number: String,
681    },
682    SoftKey {
683        /// Active call when the key is call-scoped.
684        call_id: Option<CallId>,
685        line_instance: LineInstance,
686        soft_key: SoftKey,
687    },
688    LineButton {
689        line_instance: LineInstance,
690        call_id: Option<CallId>,
691    },
692    HookFlash {
693        call_id: Option<CallId>,
694        line_instance: LineInstance,
695    },
696    FeatureButton {
697        instance: LineInstance,
698    },
699    DoNotDisturbButton {
700        instance: LineInstance,
701    },
702    MobilityButton {
703        instance: LineInstance,
704    },
705    /// Press of a configured voicemail button, resolved to an exact line and
706    /// addressable handset call before application dispatch.
707    VoicemailButton {
708        call_id: CallId,
709        line_instance: LineInstance,
710    },
711    ParkingLotButton {
712        /// Feature-button instance, distinct from the call line instance.
713        instance: LineInstance,
714        /// Active call associated with the press, when any.
715        call_id: Option<CallId>,
716        line_instance: LineInstance,
717    },
718    ParkingMenuSelection {
719        lot: String,
720        slot: u32,
721    },
722    PhoneServiceResponse {
723        response: PhoneServiceEvent,
724    },
725    ConferenceListAction {
726        action: ConferenceListAction,
727    },
728    /// The receive-channel acknowledgement matched the current request.
729    ReceiveChannelOpened {
730        call_id: CallId,
731        status: MediaStatus,
732        endpoint: MediaEndpoint,
733    },
734    MultimediaReceiveChannelOpened {
735        call_id: CallId,
736        codec: Codec,
737        endpoint: MediaEndpointAddress,
738        passthrough_party_id: PassthroughPartyId,
739    },
740    MultimediaReceiveChannelFailed {
741        call_id: CallId,
742        codec: Codec,
743        status: MediaStatus,
744        endpoint: MediaEndpointAddress,
745        passthrough_party_id: PassthroughPartyId,
746    },
747    MultimediaReceiveChannelTimedOut {
748        call_id: CallId,
749        codec: Codec,
750        passthrough_party_id: PassthroughPartyId,
751    },
752    MultimediaTransmitStarted {
753        call_id: CallId,
754        codec: Codec,
755        endpoint: MediaEndpointAddress,
756        passthrough_party_id: PassthroughPartyId,
757    },
758    MultimediaTransmitFailed {
759        call_id: CallId,
760        codec: Codec,
761        status: MediaStatus,
762        endpoint: MediaEndpointAddress,
763        passthrough_party_id: PassthroughPartyId,
764    },
765    MultimediaTransmitTimedOut {
766        call_id: CallId,
767        codec: Codec,
768        passthrough_party_id: PassthroughPartyId,
769    },
770    /// The transmit half of a coupled outbound media transaction became open
771    /// when the station acknowledged the matching receive request.
772    ///
773    /// The adjacent StartMediaTransmission request may omit its separate
774    /// acknowledgement. This event is
775    /// deliberately distinct from [`DeviceEventKind::TransmitChannelStarted`]: callers
776    /// can preserve the acknowledgement relationship while settling
777    /// both halves of the one coupled transaction exactly once.
778    TransmitChannelImplied {
779        call_id: CallId,
780        endpoint: MediaEndpoint,
781    },
782    /// The transmit-channel acknowledgement matched the current request.
783    TransmitChannelStarted {
784        call_id: CallId,
785        status: MediaStatus,
786        endpoint: MediaEndpoint,
787    },
788    HandsetAcknowledgementTimedOut {
789        call_id: CallId,
790        acknowledgement: HandsetAcknowledgement,
791    },
792    MediaTransmissionFailed {
793        call_id: CallId,
794        status: MediaStatus,
795        endpoint: MediaEndpoint,
796    },
797    MulticastReceptionStarted {
798        conference_id: ConferenceId,
799        call_id: CallId,
800        route: MulticastMediaRoute,
801    },
802    MulticastReceptionFailed {
803        conference_id: ConferenceId,
804        call_id: CallId,
805        status: MediaStatus,
806    },
807    MulticastReceptionTimedOut {
808        conference_id: ConferenceId,
809        call_id: CallId,
810    },
811    MulticastTransmissionStarted {
812        conference_id: ConferenceId,
813        call_id: CallId,
814        route: MulticastMediaRoute,
815    },
816    MulticastTransmissionFailed {
817        conference_id: ConferenceId,
818        call_id: CallId,
819        status: MediaStatus,
820        address: IpAddr,
821        port: u16,
822    },
823    /// A requested statistics response was correlated and retained.
824    ConnectionStatisticsCollected {
825        snapshot: MediaStatisticsSnapshot,
826    },
827    Alarm {
828        severity: AlarmSeverity,
829        text: String,
830        parameters: Option<[u32; 2]>,
831    },
832    XmlAlarm {
833        telemetry: PhoneAlarmTelemetry,
834    },
835    LocationInformation {
836        telemetry: PhoneLocationTelemetry,
837    },
838    HeadsetStatusChanged {
839        enabled: bool,
840    },
841    MediaPathChanged {
842        path: crate::message::values::MediaPathId,
843        event: crate::message::values::MediaPathEvent,
844    },
845    /// A well-framed client message has no server-side behavior yet.
846    UnhandledMessage {
847        message: ClientMessage,
848    },
849}
850
851/// Station acknowledgement whose correlation deadline expired.
852#[derive(Clone, Copy, Debug, Eq, PartialEq)]
853pub enum HandsetAcknowledgement {
854    OpenReceiveChannel,
855    StartMediaTransmission,
856}
857
858/// One station-targeted operation submitted through [`ServerHandle`].
859///
860/// Construction does not consult live session state; target and call
861/// availability are checked when the running server dispatches the action.
862#[derive(Clone, Debug)]
863pub struct Command {
864    pub device_id: DeviceId,
865    pub action: CommandAction,
866}
867
868impl Command {
869    pub fn new(device_id: DeviceId, action: CommandAction) -> Self {
870        Self { device_id, action }
871    }
872}
873
874/// Operation applied to the target station session in command-queue order.
875///
876/// Call-scoped actions resolve the server-owned [`CallId`] to the current wire
877/// reference inside the session. Actions for stale calls are ignored, except
878/// that [`Self::CloseCall`] also records an early cancellation so it can retire
879/// an incoming offer that has not reached the session yet.
880///
881/// Outbound presentation normally progresses from [`Self::BeginCall`] through
882/// proceeding or ringing to [`Self::CommitOutboundCall`]. Media actions allocate
883/// a fresh request identity for each generation; close and stop actions retire
884/// that identity so a late acknowledgement cannot settle replacement media.
885#[derive(Clone, Debug)]
886pub enum CommandAction {
887    /// Create local station state and off-hook presentation for an outbound
888    /// call whose adapter-side identity is already allocated.
889    BeginCall {
890        line_instance: LineInstance,
891        call_id: CallId,
892        codec: Codec,
893    },
894    /// Put a held source call into transfer mode and create its consultation
895    /// call as the active off-hook presentation.
896    BeginTransfer {
897        source_call_id: CallId,
898        consultation_line_instance: LineInstance,
899        consultation_call_id: CallId,
900        codec: Codec,
901    },
902    SetCallInfo {
903        call_id: CallId,
904        info: CallInfo,
905    },
906    CommitOutboundCall {
907        call_id: CallId,
908        info: CallInfo,
909    },
910    PresentOutboundProceeding {
911        call_id: CallId,
912        info: CallInfo,
913    },
914    PresentOutboundRinging {
915        call_id: CallId,
916        info: CallInfo,
917    },
918    SetCallState {
919        call_id: CallId,
920        state: CallState,
921    },
922    SetCallSelected {
923        call_id: CallId,
924        selected: bool,
925    },
926    DisplayPrompt {
927        call_id: CallId,
928        timeout_seconds: u32,
929        text: String,
930    },
931    ClearPrompt {
932        call_id: CallId,
933    },
934    SetStatusMessage {
935        message: HandsetStatusMessage,
936        beep: bool,
937    },
938    SetMicrophoneMode {
939        enabled: bool,
940    },
941    SetRecordingStatus {
942        call_id: CallId,
943        active: bool,
944    },
945    ResetDevice {
946        reset_type: ResetType,
947    },
948    SetMwi {
949        line_instance: LineInstance,
950        enabled: bool,
951    },
952    /// Replace all three forwarding destinations displayed for a line.
953    ///
954    /// `None` clears that forwarding kind; the server retains the complete
955    /// triple for subsequent station queries.
956    SetForwardStatus {
957        line_instance: LineInstance,
958        forward_all: Option<String>,
959        forward_busy: Option<String>,
960        forward_no_answer: Option<String>,
961    },
962    SetFeatureStatus {
963        instance: LineInstance,
964        enabled: bool,
965    },
966    SetDoNotDisturbStatus {
967        instance: LineInstance,
968        mode: DoNotDisturbMode,
969        button_mode: DoNotDisturbButtonMode,
970    },
971    /// Install or remove the temporary line appearance owned by a mobility
972    /// button, rebuilding the station button template atomically.
973    SetMobilityAppearance {
974        mobility_instance: LineInstance,
975        appearance: Option<LineAppearance>,
976    },
977    SetBlfStatus {
978        instance: LineInstance,
979        number: String,
980        label: String,
981        state: BlfState,
982        caller: Option<BlfCallerInfo>,
983    },
984    ShowParkingMenu {
985        instance: LineInstance,
986        transaction_id: TransactionId,
987        lot: String,
988        calls: Vec<ParkingMenuEntry>,
989    },
990    ShowConferenceList {
991        call_id: CallId,
992        conference_id: ConferenceId,
993        participants: Vec<ConferenceListEntry>,
994    },
995    ShowConferenceParticipantActions {
996        call_id: CallId,
997        conference_id: ConferenceId,
998        participant: ConferenceListEntry,
999        removable: bool,
1000        demotable: bool,
1001    },
1002    ShowTextService {
1003        line_instance: LineInstance,
1004        call_reference: CallReference,
1005        transaction_id: TransactionId,
1006        priority: PhoneServicePriority,
1007        document: CiscoIpPhoneText,
1008    },
1009    /// Send an input-service form whose response is emitted as
1010    /// [`DeviceEventKind::PhoneServiceResponse`].
1011    ShowInputService {
1012        line_instance: LineInstance,
1013        call_reference: CallReference,
1014        application_id: ApplicationId,
1015        transaction_id: TransactionId,
1016        priority: PhoneServicePriority,
1017        document: CiscoIpPhoneInput,
1018    },
1019    ExecutePhoneActions {
1020        line_instance: LineInstance,
1021        call_reference: CallReference,
1022        application_id: ApplicationId,
1023        transaction_id: TransactionId,
1024        priority: PhoneServicePriority,
1025        document: CiscoIpPhoneExecute,
1026    },
1027    ShowImageService {
1028        line_instance: LineInstance,
1029        call_reference: CallReference,
1030        application_id: ApplicationId,
1031        transaction_id: TransactionId,
1032        priority: PhoneServicePriority,
1033        document: PhoneImageDocument,
1034    },
1035    ShowStatusService {
1036        line_instance: LineInstance,
1037        call_reference: CallReference,
1038        application_id: ApplicationId,
1039        transaction_id: TransactionId,
1040        priority: PhoneServicePriority,
1041        document: PhoneStatusDocument,
1042    },
1043    SetBackgroundImage {
1044        transaction_id: TransactionId,
1045        document: CiscoIpPhoneSetBackground,
1046    },
1047    PreviewBackgroundImage {
1048        transaction_id: TransactionId,
1049        document: CiscoIpPhoneSetBackgroundPreview,
1050    },
1051    SetRingtone {
1052        transaction_id: TransactionId,
1053        document: CiscoIpPhoneSetRingTone,
1054    },
1055    StartTone {
1056        call_id: CallId,
1057        tone: Tone,
1058    },
1059    /// Start one or more conference announcements with an explicit participant
1060    /// hearing mask and playback mode.
1061    StartAnnouncement {
1062        conference_id: ConferenceId,
1063        announcements: Vec<AnnouncementEntry>,
1064        /// Marks the final request in an acknowledgement-delimited sequence.
1065        end_of_ack: bool,
1066        participant_ids: Vec<ParticipantId>,
1067        /// Bit mask selecting which listed participants hear the announcement.
1068        hearing_participant_mask: u32,
1069        /// Protocol playback-mode value retained for station interpretation.
1070        play_mode: u32,
1071    },
1072    StopAnnouncement {
1073        conference_id: ConferenceId,
1074    },
1075    AnnouncementFinish {
1076        conference_id: ConferenceId,
1077        play_status: u32,
1078    },
1079    StartRinging {
1080        call_id: CallId,
1081    },
1082    StopRinging {
1083        call_id: CallId,
1084    },
1085    /// Ask the station to allocate its receive channel and begin a correlated
1086    /// media transaction.
1087    OpenReceiveChannel {
1088        call_id: CallId,
1089        /// Optional RTP source restriction. `None` accepts media from any
1090        /// source and is encoded as the SCCP wildcard endpoint `0.0.0.0:0`.
1091        source: Option<MediaEndpoint>,
1092        codec: Codec,
1093        packet_ms: u32,
1094        max_frames_per_packet: u32,
1095        dtmf_mode: DtmfMode,
1096        audio_processing: AudioProcessingPolicy,
1097    },
1098    /// Requires a connected call and an exact advertised receive capability;
1099    /// replacing a live generation writes its close first.
1100    OpenMultimediaReceiveChannel {
1101        call_id: CallId,
1102        descriptor: MultimediaReceiveDescriptor,
1103    },
1104    CloseMultimediaReceiveChannel {
1105        call_id: CallId,
1106    },
1107    /// Requires a connected call and an exact advertised transmit capability;
1108    /// replacing a live generation writes its stop first.
1109    StartMultimediaTransmission {
1110        call_id: CallId,
1111        descriptor: MultimediaTransmitDescriptor,
1112    },
1113    StopMultimediaTransmission {
1114        call_id: CallId,
1115    },
1116    /// Limits the exact live station video encoder identified by its current
1117    /// passthrough token.
1118    SetMultimediaTransmitBitRate {
1119        call_id: CallId,
1120        passthrough_party_id: PassthroughPartyId,
1121        maximum_bit_rate: u32,
1122    },
1123    /// Reports a bit-rate change for the exact live station video encoder.
1124    NotifyMultimediaTransmitBitRate {
1125        call_id: CallId,
1126        passthrough_party_id: PassthroughPartyId,
1127        maximum_bit_rate: u32,
1128    },
1129    /// Applies typed feedback to the exact live station video encoder.
1130    ControlMultimediaTransmission {
1131        call_id: CallId,
1132        passthrough_party_id: PassthroughPartyId,
1133        control: MultimediaTransmitControl,
1134    },
1135    /// Open both directions of an outbound media path in one session-writer
1136    /// transaction. The two SCCP frames are written ORC then SMT without a
1137    /// command-queue or acknowledgement boundary between them.
1138    OpenOutboundMedia {
1139        call_id: CallId,
1140        source: Option<MediaEndpoint>,
1141        endpoint: MediaEndpoint,
1142        codec: Codec,
1143        packet_ms: u32,
1144        max_frames_per_packet: u32,
1145        dtmf_mode: DtmfMode,
1146        audio_processing: AudioProcessingPolicy,
1147        traffic_class: MediaTrafficClass,
1148    },
1149    /// Close the station receive leg and retire its pending acknowledgement.
1150    CloseReceiveChannel {
1151        call_id: CallId,
1152    },
1153    StartMedia {
1154        call_id: CallId,
1155        endpoint: MediaEndpoint,
1156        dtmf_mode: DtmfMode,
1157        audio_processing: AudioProcessingPolicy,
1158        traffic_class: MediaTrafficClass,
1159    },
1160    StartMulticastReception {
1161        conference_id: ConferenceId,
1162        call_id: CallId,
1163        route: MulticastMediaRoute,
1164        echo_cancellation: EchoCancellation,
1165        g723_bitrate: G723BitRate,
1166    },
1167    StopMulticastReception {
1168        conference_id: ConferenceId,
1169        call_id: CallId,
1170    },
1171    StartMulticastTransmission {
1172        conference_id: ConferenceId,
1173        call_id: CallId,
1174        route: MulticastMediaRoute,
1175        precedence: u32,
1176        silence_suppression: SilenceSuppression,
1177        max_frames_per_packet: u32,
1178        g723_bitrate: G723BitRate,
1179    },
1180    StopMulticastTransmission {
1181        conference_id: ConferenceId,
1182        call_id: CallId,
1183    },
1184    /// Stop the station transmit leg and retire its pending acknowledgement.
1185    StopMedia {
1186        call_id: CallId,
1187    },
1188    /// Tear down station media and presentation, request final statistics when
1189    /// applicable, and retire the call identity.
1190    CloseCall {
1191        call_id: CallId,
1192    },
1193    DisconnectDevice {},
1194}
1195
1196/// Failure returned by server construction, command submission, session I/O,
1197/// or stateful command validation.
1198///
1199/// Queue-admission failures do not imply that an earlier command failed.
1200/// [`Self::CommandWrite`] and [`Self::CommandAcknowledgementTimeout`] are
1201/// specific to [`ServerHandle::send_confirmed`]; protocol-level media outcomes
1202/// instead arrive through [`Event`].
1203#[derive(Debug, Error)]
1204pub enum ServerError {
1205    #[error("failed to bind SCCP server: {0}")]
1206    Bind(#[source] std::io::Error),
1207    #[error("SCCP server I/O failed: {0}")]
1208    Io(#[from] std::io::Error),
1209    #[error("SCCP protocol error: {0}")]
1210    Protocol(#[from] CodecError),
1211    #[error("invalid SCCP server configuration: {0}")]
1212    InvalidConfig(String),
1213    #[error("phone XML error: {0}")]
1214    PhoneXml(#[from] PhoneXmlError),
1215    #[error("device {0} is not connected")]
1216    DeviceNotConnected(DeviceId),
1217    #[error("call {0:?} does not exist")]
1218    UnknownCall(CallId),
1219    #[error("call {call_id:?} cannot {operation} while in state {state:?}")]
1220    InvalidCallTransaction {
1221        call_id: CallId,
1222        operation: &'static str,
1223        state: CallState,
1224    },
1225    #[error("SCCP server has stopped")]
1226    Stopped,
1227    #[error("SCCP server command queue is full")]
1228    CommandQueueFull,
1229    #[error("SCCP command could not be written to the device: {0}")]
1230    CommandWrite(String),
1231    #[error("SCCP command writer acknowledgement timed out")]
1232    CommandAcknowledgementTimeout,
1233    #[error("SCCP media request identity space is exhausted")]
1234    MediaRequestIdentityExhausted,
1235    #[error("SCCP station session generation space is exhausted")]
1236    SessionGenerationExhausted,
1237    #[error("invalid multicast media policy: {0}")]
1238    InvalidMulticastMedia(&'static str),
1239    #[error("station does not advertise the requested multicast codec")]
1240    UnsupportedMulticastCodec,
1241    #[error("invalid multimedia receive policy: {0}")]
1242    InvalidMultimediaReceive(&'static str),
1243    #[error("station does not advertise the requested video receive capability")]
1244    UnsupportedMultimediaReceive,
1245    #[error("invalid multimedia transmit policy: {0}")]
1246    InvalidMultimediaTransmit(&'static str),
1247    #[error("station does not advertise the requested video transmit capability")]
1248    UnsupportedMultimediaTransmit,
1249    #[error("invalid multimedia transmit control: {0}")]
1250    InvalidMultimediaTransmitControl(&'static str),
1251    #[error(
1252        "call {call_id:?} has no open multimedia transmit stream with passthrough token {passthrough_party_id}"
1253    )]
1254    StaleMultimediaTransmitControl {
1255        call_id: CallId,
1256        passthrough_party_id: PassthroughPartyId,
1257    },
1258    #[error("{message} is a control/service-node message, not a station command")]
1259    InvalidStationCommand { message: &'static str },
1260}
1261
1262impl ServerError {
1263    const fn is_nonfatal_command_rejection(&self) -> bool {
1264        matches!(
1265            self,
1266            Self::InvalidCallTransaction { .. }
1267                | Self::InvalidStationCommand { .. }
1268                | Self::InvalidMulticastMedia(_)
1269                | Self::UnsupportedMulticastCodec
1270                | Self::InvalidMultimediaReceive(_)
1271                | Self::UnsupportedMultimediaReceive
1272                | Self::InvalidMultimediaTransmit(_)
1273                | Self::UnsupportedMultimediaTransmit
1274                | Self::InvalidMultimediaTransmitControl(_)
1275                | Self::StaleMultimediaTransmitControl { .. }
1276        )
1277    }
1278}
1279
1280/// Cloneable command and management endpoint for a running [`Server`].
1281///
1282/// The handle does not drive I/O itself: [`Server::run`] must remain active.
1283/// Clones share call-ID allocation, retained media statistics, and the bounded
1284/// command queue. Dropping the last handle closes that queue and lets the run
1285/// loop perform its normal session shutdown.
1286#[derive(Clone, Debug)]
1287pub struct ServerHandle {
1288    command_tx: mpsc::Sender<ServerCommand>,
1289    next_call_id: Arc<AtomicU64>,
1290    latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
1291    call_answer_order: Arc<RwLock<CallSelectionOrder>>,
1292}
1293
1294/// The station definitions changed by one atomic server reconfiguration.
1295///
1296/// Only connected devices in `changed` or `removed` are disconnected. Added
1297/// devices have no session to disrupt, while definitions absent from every
1298/// list keep their live session and calls.
1299#[derive(Clone, Debug, Default, Eq, PartialEq)]
1300pub struct ReconfigureResult {
1301    pub added: Vec<DeviceId>,
1302    pub changed: Vec<DeviceId>,
1303    pub removed: Vec<DeviceId>,
1304}
1305
1306impl ReconfigureResult {
1307    pub fn is_unchanged(&self) -> bool {
1308        self.added.is_empty() && self.changed.is_empty() && self.removed.is_empty()
1309    }
1310
1311    fn disconnected_devices(&self) -> impl Iterator<Item = &DeviceId> {
1312        self.changed.iter().chain(&self.removed)
1313    }
1314}
1315
1316impl ServerHandle {
1317    /// Applies to future answer requests that omit their call reference.
1318    /// Explicit references and existing session calls are not rewritten.
1319    pub fn set_call_answer_order(&self, order: CallSelectionOrder) {
1320        *self
1321            .call_answer_order
1322            .write()
1323            .expect("SCCP call-answer-order lock poisoned") = order;
1324    }
1325
1326    /// Return the latest fully correlated statistics response for a device.
1327    ///
1328    /// Snapshots survive call and session teardown until a newer response for
1329    /// that device replaces them or the server is dropped.
1330    pub fn latest_media_statistics(&self, device_id: &DeviceId) -> Option<MediaStatisticsSnapshot> {
1331        self.latest_media_statistics
1332            .read()
1333            .expect("SCCP media-statistics lock poisoned")
1334            .get(device_id)
1335            .cloned()
1336    }
1337
1338    /// Clone every retained per-device snapshot, releasing the internal lock before a caller
1339    /// sorts, filters, or formats management output.
1340    pub fn media_statistics(&self) -> Vec<(DeviceId, MediaStatisticsSnapshot)> {
1341        self.latest_media_statistics
1342            .read()
1343            .expect("SCCP media-statistics lock poisoned")
1344            .iter()
1345            .map(|(device_id, snapshot)| (device_id.clone(), snapshot.clone()))
1346            .collect()
1347    }
1348
1349    /// Enqueue a station command, waiting for capacity in the server queue.
1350    ///
1351    /// Success confirms queue admission only. The command may subsequently be
1352    /// discarded if the target session retired, and any station response is
1353    /// reported separately through [`Event`]. Use [`Self::send_confirmed`] when
1354    /// adapter resource lifetime depends on completion of the stream write.
1355    pub async fn send(&self, command: Command) -> Result<(), ServerError> {
1356        self.command_tx
1357            .send(ServerCommand::Public(Box::new(command)))
1358            .await
1359            .map_err(|_| ServerError::Stopped)
1360    }
1361
1362    /// Send a command and wait until its complete encoded frame has been
1363    /// written to the registered device's TCP stream.
1364    ///
1365    /// This is intentionally stronger than [`Self::send`], whose completion
1366    /// only means the command entered the server queue. Lifecycle-sensitive
1367    /// callers use this boundary before releasing resources that protect the
1368    /// command's on-device operation.
1369    pub async fn send_confirmed(&self, command: Command) -> Result<(), ServerError> {
1370        let expires_at = Instant::now() + ORDERING_ACKNOWLEDGEMENT_TIMEOUT;
1371        tokio::time::timeout_at(expires_at, async {
1372            let (written_tx, written_rx) = oneshot::channel();
1373            self.command_tx
1374                .send(ServerCommand::Confirmed {
1375                    command: Box::new(command),
1376                    written: written_tx,
1377                    expires_at,
1378                })
1379                .await
1380                .map_err(|_| ServerError::Stopped)?;
1381            written_rx
1382                .await
1383                .map_err(|_| ServerError::Stopped)?
1384                .map_err(ServerError::CommandWrite)
1385        })
1386        .await
1387        .map_err(|_| ServerError::CommandAcknowledgementTimeout)?
1388    }
1389
1390    /// Enqueue a command without yielding, preserving the ordering of
1391    /// synchronous channel-driver callbacks such as call followed by hangup.
1392    pub fn try_send(&self, command: Command) -> Result<(), ServerError> {
1393        self.command_tx
1394            .try_send(ServerCommand::Public(Box::new(command)))
1395            .map_err(|error| match error {
1396                mpsc::error::TrySendError::Full(_) => ServerError::CommandQueueFull,
1397                mpsc::error::TrySendError::Closed(_) => ServerError::Stopped,
1398            })
1399    }
1400
1401    /// Allocate a call ID and enqueue an ordinarily ringing incoming offer.
1402    ///
1403    /// The returned identity is stable across all later commands and handset
1404    /// events for the offer. A failed enqueue still consumes the reserved ID.
1405    pub async fn offer_incoming_call(
1406        &self,
1407        device_id: DeviceId,
1408        line_instance: LineInstance,
1409        info: CallInfo,
1410    ) -> Result<CallId, ServerError> {
1411        let call_id = self.reserve_call_id();
1412        self.offer_incoming_call_with_id(device_id, line_instance, call_id, info)
1413            .await?;
1414        Ok(call_id)
1415    }
1416
1417    /// Reserve a call ID before exposing a call to a protocol session.
1418    ///
1419    /// Channel-driver adapters use this to install all private channel state
1420    /// before the handset can answer the subsequent offer.
1421    pub fn reserve_call_id(&self) -> CallId {
1422        CallId(self.next_call_id.fetch_add(1, Ordering::Relaxed))
1423    }
1424
1425    /// Offer an incoming call using an ID previously returned by
1426    /// [`Self::reserve_call_id`].
1427    pub async fn offer_incoming_call_with_id(
1428        &self,
1429        device_id: DeviceId,
1430        line_instance: LineInstance,
1431        call_id: CallId,
1432        info: CallInfo,
1433    ) -> Result<(), ServerError> {
1434        self.offer_incoming_call_with_id_and_ring(device_id, line_instance, call_id, info, true)
1435            .await
1436    }
1437
1438    pub async fn offer_incoming_call_with_id_and_ring(
1439        &self,
1440        device_id: DeviceId,
1441        line_instance: LineInstance,
1442        call_id: CallId,
1443        info: CallInfo,
1444        audible_ring: bool,
1445    ) -> Result<(), ServerError> {
1446        self.offer_incoming_call_with_id_and_ringer(
1447            device_id,
1448            line_instance,
1449            call_id,
1450            info,
1451            audible_ring.then_some(IncomingRing::default()),
1452        )
1453        .await
1454    }
1455
1456    /// Enqueue an incoming offer with explicit audible presentation.
1457    ///
1458    /// `None` creates a silent offer. `Some` applies the supplied ring mode and
1459    /// duration before selecting the incoming-call soft-key state.
1460    pub async fn offer_incoming_call_with_id_and_ringer(
1461        &self,
1462        device_id: DeviceId,
1463        line_instance: LineInstance,
1464        call_id: CallId,
1465        info: CallInfo,
1466        ringer: Option<IncomingRing>,
1467    ) -> Result<(), ServerError> {
1468        self.command_tx
1469            .send(ServerCommand::OfferIncoming {
1470                device_id,
1471                line_instance,
1472                call_id,
1473                info,
1474                ringer,
1475            })
1476            .await
1477            .map_err(|_| ServerError::Stopped)?;
1478        Ok(())
1479    }
1480
1481    /// Enqueue an incoming offer without yielding. Channel drivers should use
1482    /// this from their synchronous call callback so a following hangup cannot
1483    /// overtake the offer.
1484    pub fn try_offer_incoming_call_with_id(
1485        &self,
1486        device_id: DeviceId,
1487        line_instance: LineInstance,
1488        call_id: CallId,
1489        info: CallInfo,
1490    ) -> Result<(), ServerError> {
1491        self.try_offer_incoming_call_with_id_and_ring(device_id, line_instance, call_id, info, true)
1492    }
1493
1494    /// Non-blocking form of [`Self::offer_incoming_call_with_id_and_ring`].
1495    ///
1496    /// Returns [`ServerError::CommandQueueFull`] without changing session state
1497    /// when immediate queue capacity is unavailable.
1498    pub fn try_offer_incoming_call_with_id_and_ring(
1499        &self,
1500        device_id: DeviceId,
1501        line_instance: LineInstance,
1502        call_id: CallId,
1503        info: CallInfo,
1504        audible_ring: bool,
1505    ) -> Result<(), ServerError> {
1506        self.try_offer_incoming_call_with_id_and_ringer(
1507            device_id,
1508            line_instance,
1509            call_id,
1510            info,
1511            audible_ring.then_some(IncomingRing::default()),
1512        )
1513    }
1514
1515    pub fn try_offer_incoming_call_with_id_and_ringer(
1516        &self,
1517        device_id: DeviceId,
1518        line_instance: LineInstance,
1519        call_id: CallId,
1520        info: CallInfo,
1521        ringer: Option<IncomingRing>,
1522    ) -> Result<(), ServerError> {
1523        self.command_tx
1524            .try_send(ServerCommand::OfferIncoming {
1525                device_id,
1526                line_instance,
1527                call_id,
1528                info,
1529                ringer,
1530            })
1531            .map_err(|error| match error {
1532                mpsc::error::TrySendError::Full(_) => ServerError::CommandQueueFull,
1533                mpsc::error::TrySendError::Closed(_) => ServerError::Stopped,
1534            })
1535    }
1536
1537    /// Request orderly server shutdown.
1538    ///
1539    /// Success means the request entered the queue. The owner must still await
1540    /// the [`Server::run`] future to know that it stopped accepting streams and
1541    /// issued disconnects to every registered session.
1542    pub async fn shutdown(&self) -> Result<(), ServerError> {
1543        self.command_tx
1544            .send(ServerCommand::Shutdown)
1545            .await
1546            .map_err(|_| ServerError::Stopped)
1547    }
1548
1549    /// Atomically replace the configured station definitions. Only connected
1550    /// stations whose definition changed or was removed are asked to register
1551    /// again; unchanged live sessions and calls are preserved. Success means
1552    /// the replacement was committed and disconnect requests were queued, not
1553    /// that every affected transport has already closed.
1554    pub async fn reconfigure(
1555        &self,
1556        definitions: impl IntoIterator<Item = DeviceDefinition>,
1557    ) -> Result<ReconfigureResult, ServerError> {
1558        self.reconfigure_affected(definitions, []).await
1559    }
1560
1561    /// Atomically replaces station definitions and reconnects the explicit
1562    /// set in addition to stations whose wire definition changed. This lets a
1563    /// higher-level configuration owner apply line or global policy changes
1564    /// whose effects are not represented in [`DeviceDefinition`].
1565    pub async fn reconfigure_affected(
1566        &self,
1567        definitions: impl IntoIterator<Item = DeviceDefinition>,
1568        affected: impl IntoIterator<Item = DeviceId>,
1569    ) -> Result<ReconfigureResult, ServerError> {
1570        let mut by_id = HashMap::new();
1571        for definition in definitions {
1572            definition.validate()?;
1573            by_id.insert(definition.id.clone(), definition);
1574        }
1575        let (applied_tx, applied_rx) = oneshot::channel();
1576        self.command_tx
1577            .send(ServerCommand::Reconfigure {
1578                definitions: by_id,
1579                affected: affected.into_iter().collect(),
1580                applied: applied_tx,
1581            })
1582            .await
1583            .map_err(|_| ServerError::Stopped)?;
1584        applied_rx.await.map_err(|_| ServerError::Stopped)
1585    }
1586
1587    /// Commits station definitions and unknown-device admission as one server
1588    /// transaction before any affected session is disconnected.
1589    pub async fn reconfigure_station_policy(
1590        &self,
1591        definitions: impl IntoIterator<Item = DeviceDefinition>,
1592        affected: impl IntoIterator<Item = DeviceId>,
1593        anonymous_hotline: Option<AnonymousHotlineDefinition>,
1594    ) -> Result<ReconfigureResult, ServerError> {
1595        let mut by_id = HashMap::new();
1596        for definition in definitions {
1597            definition.validate()?;
1598            by_id.insert(definition.id.clone(), definition);
1599        }
1600        let (applied_tx, applied_rx) = oneshot::channel();
1601        self.command_tx
1602            .send(ServerCommand::ReconfigureStationPolicy {
1603                definitions: by_id,
1604                affected: affected.into_iter().collect(),
1605                anonymous_hotline,
1606                applied: applied_tx,
1607            })
1608            .await
1609            .map_err(|_| ServerError::Stopped)?;
1610        applied_rx.await.map_err(|_| ServerError::Stopped)
1611    }
1612
1613    /// Replace the unknown-device guest template for future registrations.
1614    /// A changed policy disconnects only sessions that were admitted through
1615    /// the previous anonymous template; configured sessions are untouched. The
1616    /// returned count is the number of such sessions asked to disconnect.
1617    pub async fn reconfigure_anonymous_hotline(
1618        &self,
1619        definition: Option<AnonymousHotlineDefinition>,
1620    ) -> Result<usize, ServerError> {
1621        let (applied_tx, applied_rx) = oneshot::channel();
1622        self.command_tx
1623            .send(ServerCommand::ReconfigureAnonymousHotline {
1624                definition,
1625                applied: applied_tx,
1626            })
1627            .await
1628            .map_err(|_| ServerError::Stopped)?;
1629        applied_rx.await.map_err(|_| ServerError::Stopped)
1630    }
1631}
1632
1633/// Stateful owner of station admission, registration, command dispatch, and
1634/// event correlation.
1635///
1636/// Construction is inert: callers must poll [`Self::run`]. The server owns its
1637/// listener or injected-ingress receiver and all registered session routing;
1638/// integration code normally retains only the returned [`ServerHandle`] and
1639/// event receiver after spawning the run future. Dropping the `Server` future
1640/// directly is abrupt, so normal shutdown should use [`ServerHandle::shutdown`]
1641/// and then await `run`.
1642#[derive(Debug)]
1643pub struct Server {
1644    listener: Option<TcpListener>,
1645    accepted_rx: mpsc::Receiver<AcceptedStation>,
1646    config: Arc<ServerConfig>,
1647    anonymous_hotline: Arc<RwLock<Option<AnonymousHotlineDefinition>>>,
1648    definitions: Arc<RwLock<HashMap<DeviceId, DeviceDefinition>>>,
1649    sessions: Sessions,
1650    event_tx: mpsc::Sender<Event>,
1651    command_rx: mpsc::Receiver<ServerCommand>,
1652    next_generation: Arc<AtomicU64>,
1653    next_statistics_generation: Arc<AtomicU64>,
1654    next_call_id: Arc<AtomicU64>,
1655    latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
1656    call_answer_order: Arc<RwLock<CallSelectionOrder>>,
1657}
1658
1659type Sessions = Arc<Mutex<HashMap<DeviceId, SessionSender>>>;
1660type CommandWriteConfirmation = oneshot::Sender<Result<(), String>>;
1661
1662#[derive(Clone, Debug)]
1663struct SessionSender {
1664    generation: SessionGeneration,
1665    anonymous_hotline: bool,
1666    tx: mpsc::Sender<SessionCommand>,
1667}
1668
1669#[derive(Debug)]
1670enum ServerCommand {
1671    Public(Box<Command>),
1672    Confirmed {
1673        command: Box<Command>,
1674        written: CommandWriteConfirmation,
1675        expires_at: Instant,
1676    },
1677    OfferIncoming {
1678        device_id: DeviceId,
1679        line_instance: LineInstance,
1680        call_id: CallId,
1681        info: CallInfo,
1682        ringer: Option<IncomingRing>,
1683    },
1684    Reconfigure {
1685        definitions: HashMap<DeviceId, DeviceDefinition>,
1686        affected: HashSet<DeviceId>,
1687        applied: oneshot::Sender<ReconfigureResult>,
1688    },
1689    ReconfigureStationPolicy {
1690        definitions: HashMap<DeviceId, DeviceDefinition>,
1691        affected: HashSet<DeviceId>,
1692        anonymous_hotline: Option<AnonymousHotlineDefinition>,
1693        applied: oneshot::Sender<ReconfigureResult>,
1694    },
1695    ReconfigureAnonymousHotline {
1696        definition: Option<AnonymousHotlineDefinition>,
1697        applied: oneshot::Sender<usize>,
1698    },
1699    Shutdown,
1700}
1701
1702#[derive(Debug)]
1703enum AnonymousHotlineUpdate {
1704    Preserve,
1705    Replace(Option<AnonymousHotlineDefinition>),
1706}
1707
1708#[derive(Debug)]
1709enum SessionCommand {
1710    Public(Box<Command>),
1711    Confirmed {
1712        command: Box<Command>,
1713        written: CommandWriteConfirmation,
1714        expires_at: Instant,
1715    },
1716    OfferIncoming {
1717        line_instance: LineInstance,
1718        call_id: CallId,
1719        info: Box<CallInfo>,
1720        ringer: Option<IncomingRing>,
1721    },
1722    Disconnect,
1723}
1724
1725#[derive(Clone, Debug)]
1726struct SessionCall {
1727    call_id: CallId,
1728    wire_reference: u32,
1729    line_instance: u32,
1730    media: CallMedia,
1731    video_receive: VideoReceive,
1732    video_transmit: VideoTransmit,
1733    state: CallState,
1734    history_disposition: CallHistoryDisposition,
1735    dialed_number: String,
1736    statistics_directory_number: String,
1737    transfer_role: Option<SessionTransferRole>,
1738}
1739
1740#[derive(Clone, Debug, Default)]
1741struct VideoReceive {
1742    generation: u64,
1743    leg: Option<VideoReceiveLeg>,
1744}
1745
1746#[derive(Clone, Debug)]
1747struct VideoReceiveLeg {
1748    request: MediaRequestIdentity,
1749    conference_id: ConferenceId,
1750    codec: Codec,
1751    requested_address_type: IpAddressType,
1752    state: MediaChannelState,
1753    deadline: Option<Instant>,
1754}
1755
1756#[derive(Debug)]
1757struct ExpiredVideoReceive {
1758    call_id: CallId,
1759    codec: Codec,
1760    passthrough_party_id: PassthroughPartyId,
1761    close: ServerMessage,
1762}
1763
1764#[derive(Clone, Debug, Default)]
1765struct VideoTransmit {
1766    generation: u64,
1767    leg: Option<VideoTransmitLeg>,
1768}
1769
1770#[derive(Clone, Debug)]
1771struct VideoTransmitLeg {
1772    request: MediaRequestIdentity,
1773    conference_id: ConferenceId,
1774    codec: Codec,
1775    address_type: IpAddressType,
1776    state: MediaChannelState,
1777    deadline: Option<Instant>,
1778}
1779
1780#[derive(Debug)]
1781struct ExpiredVideoTransmit {
1782    call_id: CallId,
1783    codec: Codec,
1784    passthrough_party_id: PassthroughPartyId,
1785    stop: ServerMessage,
1786}
1787
1788#[derive(Clone, Debug)]
1789struct CallMedia {
1790    generation: u64,
1791    codec: Codec,
1792    packet_ms: u32,
1793    max_frames_per_packet: u32,
1794    receive: MediaLeg,
1795    transmit: MediaLeg,
1796    /// Exact StartMediaTransmission endpoint paired with an outstanding
1797    /// OpenReceiveChannel in one outbound NAT compatibility transaction.
1798    /// A successful matching receive acknowledgement settles both halves.
1799    coupled_transmit_endpoint: Option<MediaEndpoint>,
1800    requested: bool,
1801}
1802
1803impl CallMedia {
1804    fn new(codec: Codec) -> Self {
1805        Self {
1806            generation: 0,
1807            codec,
1808            packet_ms: DEFAULT_AUDIO_PACKET_MS,
1809            max_frames_per_packet: DEFAULT_AUDIO_MAX_FRAMES_PER_PACKET,
1810            receive: MediaLeg::default(),
1811            transmit: MediaLeg::default(),
1812            coupled_transmit_endpoint: None,
1813            requested: false,
1814        }
1815    }
1816}
1817
1818#[derive(Clone, Debug, Default)]
1819struct MediaLeg {
1820    request: Option<MediaRequestIdentity>,
1821    telephone_event_payload: u8,
1822    peer: Option<MediaEndpoint>,
1823    state: MediaChannelState,
1824    deadline: Option<Instant>,
1825}
1826
1827#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1828enum SessionTransferRole {
1829    Source { consultation_call_id: CallId },
1830    Consultation { source_call_id: CallId },
1831}
1832
1833#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1834enum MediaChannelState {
1835    #[default]
1836    Closed,
1837    Opening,
1838    Open,
1839}
1840
1841impl MediaChannelState {
1842    const fn is_open(self) -> bool {
1843        matches!(self, Self::Open)
1844    }
1845}
1846
1847fn validate_server_config(config: &ServerConfig) -> Result<(), ServerError> {
1848    if digit_character(config.dial_terminator).is_none() {
1849        return Err(ServerError::InvalidConfig(
1850            "dial terminator must be one DTMF character".into(),
1851        ));
1852    }
1853    if !(-840..=840).contains(&config.timezone_offset_minutes) {
1854        return Err(ServerError::InvalidConfig(
1855            "timezone offset must be between -840 and 840 minutes".into(),
1856        ));
1857    }
1858    if config.keepalive_seconds < 5 || config.secondary_keepalive_seconds < 5 {
1859        return Err(ServerError::InvalidConfig(
1860            "primary and secondary keepalive intervals must be at least 5 seconds".into(),
1861        ));
1862    }
1863    if config.advertised_address.is_unspecified()
1864        || config.advertised_address.is_multicast()
1865        || config
1866            .advertised_ipv6_address
1867            .is_some_and(|address| address.is_unspecified() || address.is_multicast())
1868    {
1869        return Err(ServerError::InvalidConfig(
1870            "advertised fallback addresses must be unicast".into(),
1871        ));
1872    }
1873    if !(MIN_REGISTRATION_BACKOFF..=MAX_REGISTRATION_BACKOFF)
1874        .contains(&config.registration_tokens.backoff)
1875    {
1876        return Err(ServerError::InvalidConfig(
1877            "registration-token backoff must be between 30 and 86400 seconds".into(),
1878        ));
1879    }
1880    if config.registration_tokens.server_priority == 0 {
1881        return Err(ServerError::InvalidConfig(
1882            "server priority must be nonzero".into(),
1883        ));
1884    }
1885    if config.signaling_servers.len() > crate::message::MAX_SIGNALING_SERVERS {
1886        return Err(ServerError::InvalidConfig(format!(
1887            "at most {} signaling servers may be advertised",
1888            crate::message::MAX_SIGNALING_SERVERS
1889        )));
1890    }
1891    let mut priorities = HashSet::new();
1892    for server in &config.signaling_servers {
1893        if server.priority == 0 || !priorities.insert(server.priority) {
1894            return Err(ServerError::InvalidConfig(
1895                "signaling server priorities must be nonzero and unique".into(),
1896            ));
1897        }
1898        if server.name.is_empty()
1899            || server.name.len() >= 48
1900            || server.name.chars().any(char::is_control)
1901            || server.address.is_unspecified()
1902            || server.address.is_multicast()
1903            || server.clear_port.is_none() && server.secure_port.is_none()
1904        {
1905            return Err(ServerError::InvalidConfig(
1906                "each signaling server requires a name, unicast address, and at least one port"
1907                    .into(),
1908            ));
1909        }
1910    }
1911    if !config.signaling_servers.is_empty()
1912        && !priorities.contains(&config.registration_tokens.server_priority)
1913    {
1914        return Err(ServerError::InvalidConfig(
1915            "the local server priority must occur in the advertised server list".into(),
1916        ));
1917    }
1918    config
1919        .signaling_qos
1920        .validate()
1921        .map_err(|error| ServerError::InvalidConfig(error.to_string()))
1922}
1923
1924impl Server {
1925    /// Bind the configured plain TCP endpoint and construct a server.
1926    ///
1927    /// The returned tuple contains the inert server, its cloneable command
1928    /// handle, and the sole event receiver. Call [`Self::local_addr`] after
1929    /// construction when `config.bind` used port zero, then spawn or await
1930    /// [`Self::run`]. This constructor classifies every accepted connection as
1931    /// [`StationTransport::Clear`]. Use [`Self::with_ingress`] when transport
1932    /// negotiation or multiple listeners are owned elsewhere.
1933    pub async fn bind(
1934        config: ServerConfig,
1935        definitions: impl IntoIterator<Item = DeviceDefinition>,
1936    ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>), ServerError> {
1937        validate_server_config(&config)?;
1938        let listener = TcpListener::bind(config.bind)
1939            .await
1940            .map_err(ServerError::Bind)?;
1941        if let Ok(local) = listener.local_addr() {
1942            match SignalingSocket::capture(&listener, local) {
1943                Ok(socket) => report_socket_qos(None, local, socket.apply(config.signaling_qos)),
1944                Err(error) => {
1945                    warn!(%local, %error, "unable to retain signaling listener QoS control")
1946                }
1947            }
1948        }
1949        let (server, handle, events, _) = Self::build(config, definitions, Some(listener))?;
1950        Ok((server, handle, events))
1951    }
1952
1953    /// Construct a server whose ready station streams are supplied externally.
1954    ///
1955    /// The additional [`ServerIngress`] value is cloned by clear and secure
1956    /// listener tasks. Each task completes its transport setup, preserves the
1957    /// accepted peer and local socket addresses, and submits the stream with an
1958    /// accurate [`StationTransport`] classification. The returned server has no
1959    /// bound listener, so [`Self::local_addr`] is unavailable.
1960    pub fn with_ingress(
1961        config: ServerConfig,
1962        definitions: impl IntoIterator<Item = DeviceDefinition>,
1963    ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>, ServerIngress), ServerError> {
1964        Self::build(config, definitions, None)
1965    }
1966
1967    fn build(
1968        config: ServerConfig,
1969        definitions: impl IntoIterator<Item = DeviceDefinition>,
1970        listener: Option<TcpListener>,
1971    ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>, ServerIngress), ServerError> {
1972        validate_server_config(&config)?;
1973        let mut by_id = HashMap::new();
1974        for definition in definitions {
1975            definition.validate()?;
1976            by_id.insert(definition.id.clone(), definition);
1977        }
1978        let (event_tx, event_rx) = mpsc::channel(EVENT_CAPACITY);
1979        let (command_tx, command_rx) = mpsc::channel(COMMAND_CAPACITY);
1980        let (ingress, accepted_rx) =
1981            ServerIngress::channel(SESSION_ACCEPT_CAPACITY, config.signaling_qos);
1982        let next_call_id = Arc::new(AtomicU64::new(1));
1983        let latest_media_statistics = Arc::new(RwLock::new(HashMap::new()));
1984        let call_answer_order = Arc::new(RwLock::new(config.call_answer_order));
1985        let anonymous_hotline = Arc::new(RwLock::new(config.anonymous_hotline.clone()));
1986        let handle = ServerHandle {
1987            command_tx,
1988            next_call_id: Arc::clone(&next_call_id),
1989            latest_media_statistics: Arc::clone(&latest_media_statistics),
1990            call_answer_order: Arc::clone(&call_answer_order),
1991        };
1992        Ok((
1993            Self {
1994                listener,
1995                accepted_rx,
1996                config: Arc::new(config),
1997                anonymous_hotline,
1998                definitions: Arc::new(RwLock::new(by_id)),
1999                sessions: Arc::new(Mutex::new(HashMap::new())),
2000                event_tx,
2001                command_rx,
2002                next_generation: Arc::new(AtomicU64::new(1)),
2003                next_statistics_generation: Arc::new(AtomicU64::new(1)),
2004                next_call_id,
2005                latest_media_statistics,
2006                call_answer_order,
2007            },
2008            handle,
2009            event_rx,
2010            ingress,
2011        ))
2012    }
2013
2014    /// Return the concrete address owned by [`Self::bind`].
2015    ///
2016    /// This exposes an operating-system-assigned port when the requested bind
2017    /// address used port zero. Servers created by [`Self::with_ingress`] return
2018    /// [`ServerError::InvalidConfig`] because listener addresses belong to the
2019    /// external transport owner.
2020    pub fn local_addr(&self) -> Result<SocketAddr, ServerError> {
2021        self.listener
2022            .as_ref()
2023            .ok_or_else(|| ServerError::InvalidConfig("server has no bound listener".into()))?
2024            .local_addr()
2025            .map_err(ServerError::Io)
2026    }
2027
2028    /// Drive admission, command dispatch, reconfiguration, and shutdown.
2029    ///
2030    /// This consuming future must be polled exactly once. It accepts plain
2031    /// sockets owned by [`Self::bind`] and streams submitted through
2032    /// [`ServerIngress`], starts an independent session task for each, and
2033    /// serializes server-wide commands. It returns normally after an explicit
2034    /// shutdown request or after every [`ServerHandle`] is dropped; before
2035    /// returning it asks each registered session to disconnect. Listener or
2036    /// server-level I/O failures are returned as [`ServerError`], while an
2037    /// individual session failure is emitted as [`Event::SessionError`].
2038    pub async fn run(mut self) -> Result<(), ServerError> {
2039        if let Some(listener) = &self.listener {
2040            info!(bind = %listener.local_addr()?, "SCCP server listening");
2041        }
2042        loop {
2043            tokio::select! {
2044                accepted = accept_clear(self.listener.as_ref(), self.config.signaling_qos) => {
2045                    self.start_session(accepted?);
2046                }
2047                accepted = self.accepted_rx.recv(), if !self.accepted_rx.is_closed() => {
2048                    if let Some(accepted) = accepted {
2049                        self.start_session(accepted);
2050                    }
2051                }
2052                command = self.command_rx.recv() => {
2053                    match command {
2054                        Some(ServerCommand::Public(command)) => {
2055                            if let Err(error) = self.dispatch_public(*command).await {
2056                                warn!(%error, "discarding SCCP command for a retired session");
2057                            }
2058                        }
2059                        Some(ServerCommand::Confirmed { command, written, expires_at }) => {
2060                            self.dispatch_confirmed(command, written, expires_at).await;
2061                        }
2062                        Some(ServerCommand::OfferIncoming { device_id, line_instance, call_id, info, ringer }) => {
2063                            if let Err(error) = self.dispatch(&device_id, SessionCommand::OfferIncoming { line_instance, call_id, info: Box::new(info), ringer }).await {
2064                                warn!(%error, "discarding incoming offer for a retired session");
2065                            }
2066                        }
2067                        Some(ServerCommand::Reconfigure { definitions, affected, applied }) => {
2068                            let result = self
2069                                .apply_station_policy(
2070                                    definitions,
2071                                    affected,
2072                                    AnonymousHotlineUpdate::Preserve,
2073                                )
2074                                .await;
2075                            let _ = applied.send(result);
2076                        }
2077                        Some(ServerCommand::ReconfigureStationPolicy {
2078                            definitions,
2079                            affected,
2080                            anonymous_hotline,
2081                            applied,
2082                        }) => {
2083                            let result = self
2084                                .apply_station_policy(
2085                                    definitions,
2086                                    affected,
2087                                    AnonymousHotlineUpdate::Replace(anonymous_hotline),
2088                                )
2089                                .await;
2090                            let _ = applied.send(result);
2091                        }
2092                        Some(ServerCommand::ReconfigureAnonymousHotline { definition, applied }) => {
2093                            let sessions = self.sessions.lock().await;
2094                            let changed = {
2095                                let mut current = self
2096                                    .anonymous_hotline
2097                                    .write()
2098                                    .expect("SCCP anonymous-hotline lock poisoned");
2099                                if *current == definition {
2100                                    false
2101                                } else {
2102                                    *current = definition;
2103                                    true
2104                                }
2105                            };
2106                            let affected = if changed {
2107                                sessions
2108                                    .values()
2109                                    .filter(|session| session.anonymous_hotline)
2110                                    .cloned()
2111                                    .collect::<Vec<_>>()
2112                            } else {
2113                                Vec::new()
2114                            };
2115                            drop(sessions);
2116                            let count = affected.len();
2117                            for session in affected {
2118                                let _ = session.tx.send(SessionCommand::Disconnect).await;
2119                            }
2120                            let _ = applied.send(count);
2121                        }
2122                        Some(ServerCommand::Shutdown) | None => {
2123                            let sessions: Vec<_> = self.sessions.lock().await.values().cloned().collect();
2124                            for session in sessions { let _ = session.tx.send(SessionCommand::Disconnect).await; }
2125                            return Ok(());
2126                        }
2127                    }
2128                }
2129            }
2130        }
2131    }
2132
2133    fn start_session(&self, accepted: AcceptedStation) {
2134        let AcceptedStation {
2135            stream,
2136            peer,
2137            local,
2138            transport,
2139            socket_qos,
2140        } = accepted;
2141        let context = SessionContext {
2142            peer,
2143            local,
2144            transport,
2145            socket_qos,
2146            config: Arc::clone(&self.config),
2147            definitions: Arc::clone(&self.definitions),
2148            anonymous_hotline: Arc::clone(&self.anonymous_hotline),
2149            sessions: Arc::clone(&self.sessions),
2150            event_tx: self.event_tx.clone(),
2151            next_generation: Arc::clone(&self.next_generation),
2152            next_statistics_generation: Arc::clone(&self.next_statistics_generation),
2153            next_call_id: Arc::clone(&self.next_call_id),
2154            latest_media_statistics: Arc::clone(&self.latest_media_statistics),
2155            call_answer_order: Arc::clone(&self.call_answer_order),
2156        };
2157        let error_tx = self.event_tx.clone();
2158        tokio::spawn(async move {
2159            match run_session(stream, context).await {
2160                Ok(()) => debug!(%peer, "SCCP session ended cleanly"),
2161                Err(error) => {
2162                    warn!(%peer, %error, "SCCP session ended with an error");
2163                    let _ = error_tx
2164                        .send(Event::SessionError {
2165                            peer,
2166                            error: error.to_string(),
2167                        })
2168                        .await;
2169                }
2170            }
2171        });
2172    }
2173
2174    async fn dispatch_public(&self, command: Command) -> Result<(), ServerError> {
2175        let device_id = command.device_id.clone();
2176        self.dispatch(&device_id, SessionCommand::Public(Box::new(command)))
2177            .await
2178    }
2179
2180    async fn dispatch_confirmed(
2181        &self,
2182        command: Box<Command>,
2183        written: CommandWriteConfirmation,
2184        expires_at: Instant,
2185    ) {
2186        let device_id = command.device_id.clone();
2187        if confirmed_command_expired(&written, expires_at) {
2188            reject_expired_confirmed_command(written);
2189            return;
2190        }
2191        let tx = self
2192            .sessions
2193            .lock()
2194            .await
2195            .get(&device_id)
2196            .map(|session| session.tx.clone());
2197        let Some(tx) = tx else {
2198            let _ = written.send(Err(ServerError::DeviceNotConnected(device_id).to_string()));
2199            return;
2200        };
2201        if let Err(error) = tx
2202            .send(SessionCommand::Confirmed {
2203                command,
2204                written,
2205                expires_at,
2206            })
2207            .await
2208        {
2209            let SessionCommand::Confirmed { written, .. } = error.0 else {
2210                unreachable!("confirmed dispatch returned a different command variant")
2211            };
2212            let _ = written.send(Err(ServerError::DeviceNotConnected(device_id).to_string()));
2213        }
2214    }
2215
2216    async fn dispatch(
2217        &self,
2218        device_id: &DeviceId,
2219        command: SessionCommand,
2220    ) -> Result<(), ServerError> {
2221        let tx = self
2222            .sessions
2223            .lock()
2224            .await
2225            .get(device_id)
2226            .map(|s| s.tx.clone())
2227            .ok_or_else(|| ServerError::DeviceNotConnected(device_id.clone()))?;
2228        tx.send(command)
2229            .await
2230            .map_err(|_| ServerError::DeviceNotConnected(device_id.clone()))
2231    }
2232
2233    async fn apply_station_policy(
2234        &self,
2235        definitions: HashMap<DeviceId, DeviceDefinition>,
2236        affected: HashSet<DeviceId>,
2237        anonymous_hotline: AnonymousHotlineUpdate,
2238    ) -> ReconfigureResult {
2239        // Registration takes the session and definition locks in the same
2240        // order, so it sees either the complete current policy or the complete
2241        // candidate policy.
2242        let sessions = self.sessions.lock().await;
2243        let result = {
2244            let mut current = self
2245                .definitions
2246                .write()
2247                .expect("SCCP definitions lock poisoned");
2248            let result = reconfigure_result(&current, &definitions, &affected);
2249            *current = definitions;
2250            result
2251        };
2252        let anonymous_changed = match anonymous_hotline {
2253            AnonymousHotlineUpdate::Preserve => false,
2254            AnonymousHotlineUpdate::Replace(next) => {
2255                let mut current = self
2256                    .anonymous_hotline
2257                    .write()
2258                    .expect("SCCP anonymous-hotline lock poisoned");
2259                if *current == next {
2260                    false
2261                } else {
2262                    *current = next;
2263                    true
2264                }
2265            }
2266        };
2267        let affected_devices = result
2268            .disconnected_devices()
2269            .chain(affected.iter())
2270            .cloned()
2271            .collect::<HashSet<_>>();
2272        let affected_sessions = sessions
2273            .iter()
2274            .filter(|(device, session)| {
2275                affected_devices.contains(*device)
2276                    || (anonymous_changed && session.anonymous_hotline)
2277            })
2278            .map(|(_, session)| session.clone())
2279            .collect::<Vec<_>>();
2280        drop(sessions);
2281        for session in affected_sessions {
2282            let _ = session.tx.send(SessionCommand::Disconnect).await;
2283        }
2284        result
2285    }
2286}
2287
2288fn confirmed_command_expired(written: &CommandWriteConfirmation, expires_at: Instant) -> bool {
2289    written.is_closed() || Instant::now() >= expires_at
2290}
2291
2292fn reject_expired_confirmed_command(written: CommandWriteConfirmation) {
2293    let _ = written.send(Err(ServerError::CommandAcknowledgementTimeout.to_string()));
2294}
2295
2296fn prepare_session_command(
2297    command: SessionCommand,
2298) -> Option<(SessionCommand, Option<CommandWriteConfirmation>)> {
2299    match command {
2300        SessionCommand::Confirmed {
2301            command,
2302            written,
2303            expires_at,
2304        } => {
2305            if confirmed_command_expired(&written, expires_at) {
2306                reject_expired_confirmed_command(written);
2307                None
2308            } else {
2309                Some((SessionCommand::Public(command), Some(written)))
2310            }
2311        }
2312        command => Some((command, None)),
2313    }
2314}
2315
2316fn reconfigure_result(
2317    current: &HashMap<DeviceId, DeviceDefinition>,
2318    next: &HashMap<DeviceId, DeviceDefinition>,
2319    affected: &HashSet<DeviceId>,
2320) -> ReconfigureResult {
2321    let mut result = ReconfigureResult::default();
2322    for (device, definition) in next {
2323        match current.get(device) {
2324            None => result.added.push(device.clone()),
2325            Some(previous) if previous != definition => result.changed.push(device.clone()),
2326            Some(_) => {}
2327        }
2328    }
2329    let explicitly_changed: Vec<_> = affected
2330        .iter()
2331        .filter(|device| {
2332            current.contains_key(*device)
2333                && next.contains_key(*device)
2334                && !result.changed.contains(*device)
2335        })
2336        .cloned()
2337        .collect();
2338    result.changed.extend(explicitly_changed);
2339    result.removed.extend(
2340        current
2341            .keys()
2342            .filter(|device| !next.contains_key(*device))
2343            .cloned(),
2344    );
2345    result.added.sort();
2346    result.changed.sort();
2347    result.removed.sort();
2348    result
2349}
2350
2351fn command_call_id(command: &Command) -> Option<CallId> {
2352    match &command.action {
2353        CommandAction::BeginCall { call_id, .. }
2354        | CommandAction::SetCallInfo { call_id, .. }
2355        | CommandAction::CommitOutboundCall { call_id, .. }
2356        | CommandAction::PresentOutboundProceeding { call_id, .. }
2357        | CommandAction::PresentOutboundRinging { call_id, .. }
2358        | CommandAction::SetCallState { call_id, .. }
2359        | CommandAction::SetCallSelected { call_id, .. }
2360        | CommandAction::DisplayPrompt { call_id, .. }
2361        | CommandAction::ClearPrompt { call_id, .. }
2362        | CommandAction::SetRecordingStatus { call_id, .. }
2363        | CommandAction::ShowConferenceParticipantActions { call_id, .. }
2364        | CommandAction::StartTone { call_id, .. }
2365        | CommandAction::StartRinging { call_id, .. }
2366        | CommandAction::StopRinging { call_id, .. }
2367        | CommandAction::OpenReceiveChannel { call_id, .. }
2368        | CommandAction::OpenMultimediaReceiveChannel { call_id, .. }
2369        | CommandAction::CloseMultimediaReceiveChannel { call_id, .. }
2370        | CommandAction::StartMultimediaTransmission { call_id, .. }
2371        | CommandAction::StopMultimediaTransmission { call_id, .. }
2372        | CommandAction::SetMultimediaTransmitBitRate { call_id, .. }
2373        | CommandAction::NotifyMultimediaTransmitBitRate { call_id, .. }
2374        | CommandAction::ControlMultimediaTransmission { call_id, .. }
2375        | CommandAction::OpenOutboundMedia { call_id, .. }
2376        | CommandAction::CloseReceiveChannel { call_id, .. }
2377        | CommandAction::StartMedia { call_id, .. }
2378        | CommandAction::StartMulticastReception { call_id, .. }
2379        | CommandAction::StopMulticastReception { call_id, .. }
2380        | CommandAction::StartMulticastTransmission { call_id, .. }
2381        | CommandAction::StopMulticastTransmission { call_id, .. }
2382        | CommandAction::StopMedia { call_id, .. }
2383        | CommandAction::CloseCall { call_id, .. } => Some(*call_id),
2384        CommandAction::BeginTransfer { source_call_id, .. } => Some(*source_call_id),
2385        CommandAction::SetMwi { .. }
2386        | CommandAction::SetStatusMessage { .. }
2387        | CommandAction::SetMicrophoneMode { .. }
2388        | CommandAction::ResetDevice { .. }
2389        | CommandAction::SetForwardStatus { .. }
2390        | CommandAction::SetFeatureStatus { .. }
2391        | CommandAction::SetDoNotDisturbStatus { .. }
2392        | CommandAction::SetMobilityAppearance { .. }
2393        | CommandAction::SetBlfStatus { .. }
2394        | CommandAction::ShowParkingMenu { .. }
2395        | CommandAction::ShowConferenceList { .. }
2396        | CommandAction::ShowTextService { .. }
2397        | CommandAction::ShowInputService { .. }
2398        | CommandAction::ExecutePhoneActions { .. }
2399        | CommandAction::ShowImageService { .. }
2400        | CommandAction::ShowStatusService { .. }
2401        | CommandAction::SetBackgroundImage { .. }
2402        | CommandAction::PreviewBackgroundImage { .. }
2403        | CommandAction::SetRingtone { .. }
2404        | CommandAction::StartAnnouncement { .. }
2405        | CommandAction::StopAnnouncement { .. }
2406        | CommandAction::AnnouncementFinish { .. }
2407        | CommandAction::DisconnectDevice { .. } => None,
2408    }
2409}
2410
2411async fn accept_clear(
2412    listener: Option<&TcpListener>,
2413    signaling_qos: SignalingQos,
2414) -> Result<AcceptedStation, ServerError> {
2415    let Some(listener) = listener else {
2416        return std::future::pending().await;
2417    };
2418    let (stream, peer) = listener.accept().await?;
2419    stream.set_nodelay(true)?;
2420    let local = stream.local_addr()?;
2421    let socket_qos = match SignalingSocket::capture(&stream, local) {
2422        Ok(socket) => {
2423            report_socket_qos(None, peer, socket.apply(signaling_qos));
2424            Some(Box::new(socket) as Box<dyn StationSocketQos>)
2425        }
2426        Err(error) => {
2427            warn!(%peer, %error, "unable to retain signaling socket QoS control");
2428            None
2429        }
2430    };
2431    Ok(AcceptedStation {
2432        stream: Box::new(stream),
2433        peer,
2434        local,
2435        transport: StationTransport::Clear,
2436        socket_qos,
2437    })
2438}
2439
2440fn report_socket_qos(device_id: Option<&DeviceId>, endpoint: SocketAddr, report: SocketQosReport) {
2441    for failure in report.failures() {
2442        match device_id {
2443            Some(device_id) => {
2444                warn!(%device_id, %endpoint, %failure, "signaling socket marking unavailable")
2445            }
2446            None => warn!(%endpoint, %failure, "signaling socket marking unavailable"),
2447        }
2448    }
2449}
2450
2451fn allocate_session_generation(
2452    next_generation: &AtomicU64,
2453) -> Result<SessionGeneration, ServerError> {
2454    let generation = next_generation
2455        .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
2456            SessionGeneration::new(current).and_then(|_| current.checked_add(1))
2457        })
2458        .map_err(|_| ServerError::SessionGenerationExhausted)?;
2459    SessionGeneration::new(generation).ok_or(ServerError::SessionGenerationExhausted)
2460}
2461
2462const fn transport_allowed(
2463    requirement: StationTransportRequirement,
2464    transport: StationTransport,
2465) -> bool {
2466    matches!(
2467        (requirement, transport),
2468        (StationTransportRequirement::Either, _)
2469            | (StationTransportRequirement::Clear, StationTransport::Clear)
2470            | (
2471                StationTransportRequirement::Secure,
2472                StationTransport::Secure
2473            )
2474    )
2475}
2476
2477#[derive(Debug)]
2478struct SessionContext {
2479    peer: SocketAddr,
2480    local: SocketAddr,
2481    transport: StationTransport,
2482    socket_qos: Option<Box<dyn StationSocketQos>>,
2483    config: Arc<ServerConfig>,
2484    definitions: Arc<RwLock<HashMap<DeviceId, DeviceDefinition>>>,
2485    anonymous_hotline: Arc<RwLock<Option<AnonymousHotlineDefinition>>>,
2486    sessions: Sessions,
2487    event_tx: mpsc::Sender<Event>,
2488    next_generation: Arc<AtomicU64>,
2489    next_statistics_generation: Arc<AtomicU64>,
2490    next_call_id: Arc<AtomicU64>,
2491    latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
2492    call_answer_order: Arc<RwLock<CallSelectionOrder>>,
2493}
2494
2495#[derive(Debug)]
2496struct SessionState {
2497    device: DeviceDefinition,
2498    registration: DeviceRegistration,
2499    features: PhoneFeatures,
2500    generation: SessionGeneration,
2501    calls_by_id: HashMap<CallId, SessionCall>,
2502    calls_by_wire: HashMap<u32, CallId>,
2503    media_capabilities: StationMediaCapabilities,
2504    next_media_token: Option<MediaRequestToken>,
2505    next_multicast_generation: u64,
2506    multicast: HashMap<MulticastKey, MulticastSession>,
2507    pending_connection_statistics: HashMap<u32, PendingConnectionStatistics>,
2508    statistics_references: HashSet<u32>,
2509    cancelled_calls: HashSet<CallId>,
2510    last_number_by_line: HashMap<u32, String>,
2511    forwarding_by_line: HashMap<u32, SessionForwarding>,
2512    feature_states: HashMap<u32, SessionFeatureState>,
2513    mwi_by_line: HashMap<u32, bool>,
2514    mobility_appearances: HashMap<u32, LineAppearance>,
2515    active_key_mode: KeyMode,
2516    active_call_id: Option<CallId>,
2517    pending_parking_menu: Option<PendingParkingMenu>,
2518    persistent_status_message: bool,
2519    headset_enabled: bool,
2520    media_path_states:
2521        HashMap<crate::message::values::MediaPathId, crate::message::values::MediaPathEvent>,
2522    pending_media_path_release: Option<PendingMediaPathRelease>,
2523}
2524
2525#[derive(Clone, Copy, Debug)]
2526struct PendingMediaPathRelease {
2527    call_id: CallId,
2528    path: crate::message::values::MediaPathId,
2529    deadline: Instant,
2530}
2531
2532#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2533struct MulticastKey {
2534    conference_id: ConferenceId,
2535    call_id: CallId,
2536}
2537
2538#[derive(Clone, Debug)]
2539struct MulticastSession {
2540    wire_call_reference: u32,
2541    receive: Option<MulticastReceive>,
2542    transmit: Option<MulticastTransmit>,
2543}
2544
2545#[derive(Clone, Debug)]
2546struct MulticastReceive {
2547    request: MediaRequestIdentity,
2548    route: MulticastMediaRoute,
2549    state: MulticastReceiveState,
2550}
2551
2552#[derive(Clone, Debug)]
2553enum MulticastReceiveState {
2554    AwaitingAcknowledgement { deadline: Instant },
2555    Open,
2556}
2557
2558#[derive(Clone, Debug)]
2559struct MulticastTransmit {
2560    request: MediaRequestIdentity,
2561    route: MulticastMediaRoute,
2562}
2563
2564impl SessionState {
2565    fn station_context(&self) -> StationSessionContext {
2566        StationSessionContext::new(self.registration.protocol, self.features)
2567    }
2568}
2569
2570#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2571struct SessionFeatureState {
2572    button_type: ButtonType,
2573    state: u32,
2574}
2575
2576#[derive(Clone, Debug)]
2577struct PendingConnectionStatistics {
2578    session_generation: SessionGeneration,
2579    request_generation: u64,
2580    call_id: CallId,
2581    line_instance: u32,
2582    codec: Codec,
2583    packet_ms: u32,
2584    max_frames_per_packet: u32,
2585    receive_peer: Option<MediaEndpoint>,
2586    transmit_peer: Option<MediaEndpoint>,
2587    directory_number: String,
2588    processing: StatisticsProcessing,
2589    expires_at: Instant,
2590}
2591
2592#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2593struct PendingParkingMenu {
2594    instance: u32,
2595    transaction_id: u32,
2596}
2597
2598#[derive(Clone, Debug, Default)]
2599struct SessionForwarding {
2600    all: Option<String>,
2601    busy: Option<String>,
2602    no_answer: Option<String>,
2603}
2604
2605async fn run_session(
2606    mut stream: Box<dyn StationIo>,
2607    context: SessionContext,
2608) -> Result<(), ServerError> {
2609    let (session_tx, mut session_rx) = mpsc::channel(SESSION_COMMAND_CAPACITY);
2610    let mut decoder = FrameDecoder::new();
2611    let mut read_buffer = [0_u8; 4096];
2612    let mut state: Option<SessionState> = None;
2613    let mut last_keepalive = Instant::now();
2614    let mut session_deadlines = tokio::time::interval(Duration::from_millis(100));
2615    session_deadlines.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
2616    let keepalive_seconds = if context.config.registration_tokens.server_priority == 1 {
2617        context.config.keepalive_seconds
2618    } else {
2619        context.config.secondary_keepalive_seconds
2620    };
2621    let keepalive_timeout = Duration::from_secs(u64::from(keepalive_seconds) * 3);
2622
2623    loop {
2624        tokio::select! {
2625            read = stream.read(&mut read_buffer) => {
2626                let count = read?;
2627                if count == 0 { break; }
2628                for frame in decoder.push(&read_buffer[..count])? {
2629                    let decode_protocol = state
2630                        .as_ref()
2631                        .map_or(ProtocolVersion::V3, |state| state.registration.protocol);
2632                    let message_id = frame.message_id;
2633                    let message = match ClientMessage::decode_with_version(frame, decode_protocol) {
2634                        Ok(message) => message,
2635                        Err(error) if message_id != crate::message::id::REGISTER => {
2636                            let device_id = state.as_ref().map(|state| state.device.id.clone());
2637                            warn!(peer = %context.peer, message_id = format_args!("0x{message_id:04x}"), %error, "ignoring malformed SCCP application message");
2638                            let _ = context.event_tx.send(Event::ProtocolWarning {
2639                                peer: context.peer,
2640                                device_id,
2641                                message_id,
2642                                error: error.to_string(),
2643                            }).await;
2644                            continue;
2645                        }
2646                        Err(error) => return Err(error.into()),
2647                    };
2648                    if let ClientMessage::Register(registration) = &message {
2649                        if state.is_some() {
2650                            return Err(ServerError::Protocol(CodecError::InvalidDefinition("duplicate REGISTER on one TCP session".into())));
2651                        }
2652                        let mut sessions = context.sessions.lock().await;
2653                        let configured = context
2654                            .definitions
2655                            .read()
2656                            .expect("SCCP definitions lock poisoned")
2657                            .get(&registration.device_id)
2658                            .cloned();
2659                        let anonymous_hotline = configured.is_none();
2660                        let definition = configured.or_else(|| {
2661                                context
2662                                    .anonymous_hotline
2663                                    .read()
2664                                    .expect("SCCP anonymous-hotline lock poisoned")
2665                                    .as_ref()
2666                                    .map(|hotline| {
2667                                    hotline.device_definition(registration.device_id.clone())
2668                                })
2669                            });
2670                        let Some(definition) = definition else {
2671                            drop(sessions);
2672                            send_message(&mut stream, &ServerMessage::RegisterReject { reason: "Device not configured".into() }, ProtocolVersion::V17).await?;
2673                            return Ok(());
2674                        };
2675                        if !transport_allowed(definition.transport, context.transport) {
2676                            drop(sessions);
2677                            send_message(
2678                                &mut stream,
2679                                &ServerMessage::RegisterReject {
2680                                    reason: "Device transport not permitted".into(),
2681                                },
2682                                ProtocolVersion::V17,
2683                            )
2684                            .await?;
2685                            return Ok(());
2686                        }
2687                        let protocol = ProtocolVersion::negotiate(registration.advertised_protocol)?;
2688                        if canonical_ip_address(context.peer.ip()).is_ipv6()
2689                            && protocol < ProtocolVersion::V17
2690                        {
2691                            drop(sessions);
2692                            send_message(
2693                                &mut stream,
2694                                &ServerMessage::RegisterReject {
2695                                    reason: "IPv6 requires protocol v17".into(),
2696                                },
2697                                protocol,
2698                            )
2699                            .await?;
2700                            return Ok(());
2701                        }
2702                        let features = registration.features;
2703                        let generation = allocate_session_generation(&context.next_generation)?;
2704                        if let Some(socket_qos) = &context.socket_qos {
2705                            let signaling_qos = definition
2706                                .signaling_qos
2707                                .unwrap_or(context.config.signaling_qos);
2708                            report_socket_qos(
2709                                Some(&registration.device_id),
2710                                context.peer,
2711                                socket_qos.apply(signaling_qos),
2712                            );
2713                        }
2714                        let device_registration = DeviceRegistration {
2715                            id: registration.device_id.clone(), peer: context.peer,
2716                            transport: context.transport,
2717                            reported_address: registration.reported_address,
2718                            reported_ipv6_address: registration.reported_ipv6_address,
2719                            device_type: registration.device_type,
2720                            protocol, firmware: registration.firmware.clone(),
2721                        };
2722                        let previous = sessions.insert(
2723                            registration.device_id.clone(), SessionSender {
2724                                generation,
2725                                anonymous_hotline,
2726                                tx: session_tx.clone(),
2727                            }
2728                        );
2729                        drop(sessions);
2730                        if let Some(previous) = previous { let _ = previous.tx.send(SessionCommand::Disconnect).await; }
2731                        send_message(
2732                            &mut stream,
2733                            &ServerMessage::RegisterAck {
2734                                keepalive_seconds: context.config.keepalive_seconds,
2735                                secondary_keepalive_seconds: context.config.secondary_keepalive_seconds,
2736                                protocol,
2737                                features: PhoneFeatures::empty(),
2738                                date_template: context.config.date_template.clone(),
2739                            },
2740                            protocol,
2741                        )
2742                        .await?;
2743                        send_message(&mut stream, &ServerMessage::CapabilitiesRequest, protocol).await?;
2744                        context.event_tx.send(Event::device(
2745                            registration.device_id.clone(),
2746                            generation,
2747                            DeviceEventKind::Registered(device_registration.clone()),
2748                        )).await.map_err(|_| ServerError::Stopped)?;
2749                        info!(device_id = %registration.device_id, %protocol, peer = %context.peer, "SCCP device registered");
2750                        state = Some(SessionState { device: definition, registration: device_registration, features, generation, calls_by_id: HashMap::new(), calls_by_wire: HashMap::new(), media_capabilities: StationMediaCapabilities::default(), next_media_token: MediaRequestToken::new(1), next_multicast_generation: 0, multicast: HashMap::new(), pending_connection_statistics: HashMap::new(), statistics_references: HashSet::new(), cancelled_calls: HashSet::new(), last_number_by_line: HashMap::new(), forwarding_by_line: HashMap::new(), feature_states: HashMap::new(), mwi_by_line: HashMap::new(), mobility_appearances: HashMap::new(), active_key_mode: KeyMode::OnHook, active_call_id: None, pending_parking_menu: None, persistent_status_message: false, headset_enabled: false, media_path_states: HashMap::new(), pending_media_path_release: None });
2751                        last_keepalive = Instant::now();
2752                    } else if let Some(state) = state.as_mut() {
2753                        if matches!(message, ClientMessage::KeepAlive) { last_keepalive = Instant::now(); }
2754                        handle_client_message(&mut stream, state, message, &context).await?;
2755                    } else {
2756                        handle_pre_registration_message(&mut stream, message, &context).await?;
2757                    }
2758                }
2759            }
2760            command = session_rx.recv() => {
2761                match command {
2762                    Some(command) => {
2763                        let Some(state) = state.as_mut() else { continue };
2764                        let Some((command, written)) = prepare_session_command(command) else {
2765                            continue;
2766                        };
2767                        match handle_session_command(&mut stream, state, command, &context).await {
2768                            Ok(disconnect) => {
2769                                if let Some(written) = written {
2770                                    let _ = written.send(Ok(()));
2771                                }
2772                                if disconnect { break; }
2773                            }
2774                            Err(error) => {
2775                                if let Some(written) = written {
2776                                    let _ = written.send(Err(error.to_string()));
2777                                }
2778                                if error.is_nonfatal_command_rejection() {
2779                                    warn!(
2780                                        device_id = %state.device.id,
2781                                        %error,
2782                                        "rejected invalid SCCP station command"
2783                                    );
2784                                    continue;
2785                                }
2786                                return Err(error);
2787                            }
2788                        }
2789                    }
2790                    None => break,
2791                }
2792            }
2793            _ = session_deadlines.tick(), if state.is_some() => {
2794                if let Some(state) = state.as_mut() {
2795                    let now = Instant::now();
2796                    let timed_out = expire_handset_acknowledgements(
2797                        &mut state.calls_by_id,
2798                        now,
2799                    );
2800                    for (call_id, acknowledgement) in timed_out {
2801                        context
2802                            .event_tx
2803                            .send(Event::device(
2804                                state.device.id.clone(),
2805                                state.generation,
2806                                DeviceEventKind::HandsetAcknowledgementTimedOut {
2807                                call_id,
2808                                acknowledgement,
2809                            }))
2810                            .await
2811                            .map_err(|_| ServerError::Stopped)?;
2812                    }
2813                    for (key, stop) in expire_multicast_reception_acknowledgements(state, now) {
2814                        send_message(&mut stream, &stop, state.registration.protocol).await?;
2815                        context
2816                            .event_tx
2817                            .send(Event::device(
2818                                state.device.id.clone(),
2819                                state.generation,
2820                                DeviceEventKind::MulticastReceptionTimedOut {
2821                                    conference_id: key.conference_id,
2822                                    call_id: key.call_id,
2823                                },
2824                            ))
2825                            .await
2826                            .map_err(|_| ServerError::Stopped)?;
2827                    }
2828                    for expired in expire_multimedia_receive_acknowledgements(state, now) {
2829                        send_message(
2830                            &mut stream,
2831                            &expired.close,
2832                            state.registration.protocol,
2833                        )
2834                        .await?;
2835                        context
2836                            .event_tx
2837                            .send(Event::device(
2838                                state.device.id.clone(),
2839                                state.generation,
2840                                DeviceEventKind::MultimediaReceiveChannelTimedOut {
2841                                    call_id: expired.call_id,
2842                                    codec: expired.codec,
2843                                    passthrough_party_id: expired.passthrough_party_id,
2844                                },
2845                            ))
2846                            .await
2847                            .map_err(|_| ServerError::Stopped)?;
2848                    }
2849                    for expired in expire_multimedia_transmit_acknowledgements(state, now) {
2850                        send_message(
2851                            &mut stream,
2852                            &expired.stop,
2853                            state.registration.protocol,
2854                        )
2855                        .await?;
2856                        context
2857                            .event_tx
2858                            .send(Event::device(
2859                                state.device.id.clone(),
2860                                state.generation,
2861                                DeviceEventKind::MultimediaTransmitTimedOut {
2862                                    call_id: expired.call_id,
2863                                    codec: expired.codec,
2864                                    passthrough_party_id: expired.passthrough_party_id,
2865                                },
2866                            ))
2867                            .await
2868                            .map_err(|_| ServerError::Stopped)?;
2869                    }
2870                    if let Some(pending) = state
2871                        .pending_media_path_release
2872                        .filter(|pending| pending.deadline <= now)
2873                    {
2874                        state.pending_media_path_release = None;
2875                        let still_released = state.media_path_states.get(&pending.path)
2876                            == Some(&crate::message::values::MediaPathEvent::Off)
2877                            && !has_active_media_path(state)
2878                            && active_media_path_call(state) == Some(pending.call_id);
2879                        if still_released
2880                            && let Some(call) = state.calls_by_id.get(&pending.call_id).cloned()
2881                        {
2882                            debug!(
2883                                device_id = %state.device.id,
2884                                call_id = ?call.call_id,
2885                                path = ?pending.path,
2886                                "completing unpaired media-path release as OnHook"
2887                            );
2888                            let line_instance = call.line_instance;
2889                            complete_on_hook(
2890                                &mut stream,
2891                                state,
2892                                &context,
2893                                call,
2894                                line_instance,
2895                            )
2896                            .await?;
2897                        }
2898                    }
2899                    prune_connection_statistics(
2900                        &mut state.pending_connection_statistics,
2901                        Instant::now(),
2902                    );
2903                }
2904            }
2905            _ = tokio::time::sleep_until(last_keepalive + keepalive_timeout), if state.is_some() => {
2906                warn!(peer = %context.peer, "SCCP keepalive timeout");
2907                break;
2908            }
2909        }
2910    }
2911
2912    if let Some(mut state) = state {
2913        drain_session_media(&mut stream, &mut state).await;
2914        let mut sessions = context.sessions.lock().await;
2915        let was_current = sessions
2916            .get(&state.device.id)
2917            .is_some_and(|entry| entry.generation == state.generation);
2918        if was_current {
2919            sessions.remove(&state.device.id);
2920        }
2921        drop(sessions);
2922        if was_current {
2923            let _ = context
2924                .event_tx
2925                .send(Event::device(
2926                    state.device.id,
2927                    state.generation,
2928                    DeviceEventKind::Disconnected {},
2929                ))
2930                .await;
2931        }
2932    }
2933    Ok(())
2934}
2935
2936fn expire_handset_acknowledgements(
2937    calls_by_id: &mut HashMap<CallId, SessionCall>,
2938    now: Instant,
2939) -> Vec<(CallId, HandsetAcknowledgement)> {
2940    let mut calls = calls_by_id.keys().copied().collect::<Vec<_>>();
2941    calls.sort_unstable_by_key(|call_id| call_id.0);
2942    let mut expired = Vec::new();
2943    for call_id in calls {
2944        let call = calls_by_id
2945            .get_mut(&call_id)
2946            .expect("call identifier came from session state");
2947        if call.media.receive.state == MediaChannelState::Opening
2948            && call
2949                .media
2950                .receive
2951                .deadline
2952                .is_some_and(|deadline| deadline <= now)
2953        {
2954            call.media.receive.state = MediaChannelState::Closed;
2955            call.media.receive.deadline = None;
2956            call.media.receive.peer = None;
2957            if call.media.coupled_transmit_endpoint.take().is_some() {
2958                call.media.transmit.state = MediaChannelState::Closed;
2959                call.media.transmit.deadline = None;
2960                call.media.transmit.peer = None;
2961            }
2962            expired.push((call_id, HandsetAcknowledgement::OpenReceiveChannel));
2963        }
2964        if call.media.transmit.state == MediaChannelState::Opening
2965            && call
2966                .media
2967                .transmit
2968                .deadline
2969                .is_some_and(|deadline| deadline <= now)
2970        {
2971            call.media.transmit.state = MediaChannelState::Closed;
2972            call.media.transmit.deadline = None;
2973            call.media.transmit.peer = None;
2974            expired.push((call_id, HandsetAcknowledgement::StartMediaTransmission));
2975        }
2976    }
2977    expired
2978}
2979
2980async fn handle_pre_registration_message(
2981    stream: &mut dyn StationIo,
2982    message: ClientMessage,
2983    context: &SessionContext,
2984) -> Result<(), ServerError> {
2985    match message {
2986        ClientMessage::KeepAlive => {
2987            send_message(stream, &ServerMessage::KeepAliveAck, ProtocolVersion::V3).await?;
2988        }
2989        ClientMessage::RegisterToken(token) => {
2990            let definition = context
2991                .definitions
2992                .read()
2993                .expect("SCCP definitions lock poisoned")
2994                .get(&token.device_id)
2995                .cloned();
2996            let configured = definition.is_some()
2997                || context
2998                    .anonymous_hotline
2999                    .read()
3000                    .expect("SCCP anonymous-hotline lock poisoned")
3001                    .is_some();
3002            let transport_permitted = definition.as_ref().is_none_or(|definition| {
3003                transport_allowed(definition.transport, context.transport)
3004            });
3005            let already_registered = context.sessions.lock().await.contains_key(&token.device_id);
3006            let accept = configured
3007                && transport_permitted
3008                && !already_registered
3009                && context.config.registration_tokens.accepts(&token.device_id);
3010            let response = if accept {
3011                ServerMessage::RegisterTokenAck
3012            } else {
3013                ServerMessage::RegisterTokenReject {
3014                    backoff_seconds: u32::try_from(
3015                        context.config.registration_tokens.backoff.as_secs(),
3016                    )
3017                    .unwrap_or(u32::MAX),
3018                }
3019            };
3020            send_message(stream, &response, ProtocolVersion::V17).await?;
3021        }
3022        ClientMessage::Alarm {
3023            severity,
3024            text,
3025            parameters,
3026        } => {
3027            debug!(peer = %context.peer, ?severity, %text, ?parameters, "pre-registration SCCP alarm");
3028        }
3029        ClientMessage::XmlAlarm(message) => match parse_phone_alarm(message.xml_bytes()) {
3030            Ok(telemetry) => {
3031                debug!(
3032                    peer = %context.peer,
3033                    payload_len = message.xml_bytes().len(),
3034                    summary = ?telemetry.summary(),
3035                    opaque = telemetry.is_opaque(),
3036                    "pre-registration SCCP XML alarm"
3037                );
3038            }
3039            Err(error) => {
3040                warn!(
3041                    peer = %context.peer,
3042                    payload_len = message.xml_bytes().len(),
3043                    %error,
3044                    "rejected pre-registration SCCP XML alarm"
3045                );
3046            }
3047        },
3048        ClientMessage::LocationInfo { xml } => match parse_phone_location(xml.as_bytes()) {
3049            Ok(telemetry) => {
3050                debug!(
3051                    peer = %context.peer,
3052                    payload_len = xml.len(),
3053                    summary = ?telemetry.summary(),
3054                    opaque = telemetry.is_opaque(),
3055                    "pre-registration SCCP location information"
3056                );
3057            }
3058            Err(error) => {
3059                warn!(
3060                    peer = %context.peer,
3061                    payload_len = xml.len(),
3062                    %error,
3063                    "rejected pre-registration SCCP location information"
3064                );
3065            }
3066        },
3067        ClientMessage::KnownOpaque(message) => {
3068            debug!(peer = %context.peer, message = ?message, "pre-registration deferred SCCP message");
3069        }
3070        ClientMessage::Unknown(message) => {
3071            warn!(peer = %context.peer, message = ?message, "pre-registration unknown SCCP message");
3072        }
3073        ClientMessage::Register(_)
3074        | ClientMessage::IpPort { .. }
3075        | ClientMessage::KeypadButton { .. }
3076        | ClientMessage::EnblocCall { .. }
3077        | ClientMessage::Stimulus { .. }
3078        | ClientMessage::OffHook { .. }
3079        | ClientMessage::OnHook { .. }
3080        | ClientMessage::OffHookWithCallingParty { .. }
3081        | ClientMessage::LineStatRequest { .. }
3082        | ClientMessage::ConfigStatRequest
3083        | ClientMessage::TimeDateRequest
3084        | ClientMessage::ButtonTemplateRequest
3085        | ClientMessage::VersionRequest
3086        | ClientMessage::CapabilitiesResponse(_)
3087        | ClientMessage::CapabilitiesUpdate(_)
3088        | ClientMessage::OpenMultimediaReceiveChannelAck(_)
3089        | ClientMessage::ServerRequest
3090        | ClientMessage::MulticastMediaReceptionAck { .. }
3091        | ClientMessage::OpenReceiveChannelAck { .. }
3092        | ClientMessage::SoftKeySetRequest
3093        | ClientMessage::SoftKeyTemplateRequest
3094        | ClientMessage::SoftKeyEvent { .. }
3095        | ClientMessage::Unregister { .. }
3096        | ClientMessage::HookFlash { .. }
3097        | ClientMessage::ForwardStatusRequest { .. }
3098        | ClientMessage::SpeedDialStatusRequest { .. }
3099        | ClientMessage::ConnectionStatisticsResponse(_)
3100        | ClientMessage::HeadsetStatus { .. }
3101        | ClientMessage::MediaResourceNotification(_)
3102        | ClientMessage::MediaPathEvent { .. }
3103        | ClientMessage::MediaPathCapability { .. }
3104        | ClientMessage::MediaTransmissionFailure { .. }
3105        | ClientMessage::RegisterAvailableLines { .. }
3106        | ClientMessage::ServiceUrlStatusRequest { .. }
3107        | ClientMessage::FeatureStatusRequest { .. }
3108        | ClientMessage::StartMediaTransmissionAck(_)
3109        | ClientMessage::StartMultimediaTransmissionAck(_)
3110        | ClientMessage::ExtensionDeviceCapabilities(_)
3111        | ClientMessage::DeviceToUserData(_)
3112        | ClientMessage::DeviceToUserDataResponse(_)
3113        | ClientMessage::DeviceToUserDataV1(_)
3114        | ClientMessage::DeviceToUserDataResponseV1(_)
3115        | ClientMessage::PortResponse(_)
3116        | ClientMessage::SubscriptionStatusRequest(_)
3117        | ClientMessage::SubscribeDtmfPayloadResponse(_)
3118        | ClientMessage::UnsubscribeDtmfPayloadResponse(_)
3119        | ClientMessage::CallCountRequest { .. }
3120        | ClientMessage::CreateConferenceResponse(_)
3121        | ClientMessage::DeleteConferenceResponse { .. }
3122        | ClientMessage::ModifyConferenceResponse(_)
3123        | ClientMessage::AuditConferenceResponse(_)
3124        | ClientMessage::AddParticipantResponse(_)
3125        | ClientMessage::AuditParticipantResponse(_) => {
3126            warn!(peer = %context.peer, message = ?message, "ignoring SCCP message before registration");
3127        }
3128    }
3129    Ok(())
3130}
3131
3132async fn handle_client_message(
3133    stream: &mut dyn StationIo,
3134    state: &mut SessionState,
3135    message: ClientMessage,
3136    context: &SessionContext,
3137) -> Result<(), ServerError> {
3138    let protocol = state.registration.protocol;
3139    match message {
3140        ClientMessage::KeepAlive => {
3141            send_message(stream, &ServerMessage::KeepAliveAck, protocol).await?
3142        }
3143        ClientMessage::CapabilitiesResponse(capabilities) => {
3144            let capabilities = StationMediaCapabilities::from(capabilities);
3145            state.media_capabilities.clone_from(&capabilities);
3146            context
3147                .event_tx
3148                .send(Event::device(
3149                    state.device.id.clone(),
3150                    state.generation,
3151                    DeviceEventKind::Capabilities { capabilities },
3152                ))
3153                .await
3154                .map_err(|_| ServerError::Stopped)?;
3155        }
3156        ClientMessage::CapabilitiesUpdate(update) => {
3157            let capabilities = update.into_media_capabilities();
3158            state.media_capabilities.clone_from(&capabilities);
3159            context
3160                .event_tx
3161                .send(Event::device(
3162                    state.device.id.clone(),
3163                    state.generation,
3164                    DeviceEventKind::Capabilities { capabilities },
3165                ))
3166                .await
3167                .map_err(|_| ServerError::Stopped)?;
3168        }
3169        ClientMessage::ConfigStatRequest => {
3170            send_station_ui_message(
3171                stream,
3172                state,
3173                &ServerMessage::ConfigStatus(crate::message::ConfigurationStatus {
3174                    device_name: state.device.id.as_str().to_owned(),
3175                    station_user_id: 0,
3176                    station_instance: 1,
3177                    user_name: state.device.description.clone(),
3178                    server_name: context.config.server_name.clone(),
3179                    line_count: state.device.line_count() as u32,
3180                    speed_dial_count: 0,
3181                }),
3182            )
3183            .await?;
3184        }
3185        ClientMessage::LineStatRequest { line_instance } => {
3186            if let Some(message) = line_status(&state.device, line_instance) {
3187                send_station_ui_message(stream, state, &message).await?;
3188            }
3189        }
3190        ClientMessage::ButtonTemplateRequest => {
3191            send_message(
3192                stream,
3193                &ServerMessage::ButtonTemplate {
3194                    buttons: button_template(&state.device),
3195                },
3196                protocol,
3197            )
3198            .await?;
3199        }
3200        ClientMessage::VersionRequest => {
3201            send_message(
3202                stream,
3203                &ServerMessage::Version {
3204                    firmware: context.config.firmware_version.clone(),
3205                },
3206                protocol,
3207            )
3208            .await?;
3209        }
3210        ClientMessage::ServerRequest => {
3211            send_message(
3212                stream,
3213                &ServerMessage::ServerResponse {
3214                    servers: server_response_endpoints(context, protocol)?,
3215                },
3216                protocol,
3217            )
3218            .await?;
3219        }
3220        ClientMessage::TimeDateRequest => {
3221            send_message(
3222                stream,
3223                &time_date_message(context.config.timezone_offset_minutes),
3224                protocol,
3225            )
3226            .await?
3227        }
3228        ClientMessage::SoftKeyTemplateRequest => {
3229            send_message(
3230                stream,
3231                &ServerMessage::SoftKeyTemplate {
3232                    actions: state.device.soft_keys.template_actions(),
3233                },
3234                protocol,
3235            )
3236            .await?
3237        }
3238        ClientMessage::SoftKeySetRequest => {
3239            send_message(
3240                stream,
3241                &ServerMessage::SoftKeySet {
3242                    profile: state.device.soft_keys.clone(),
3243                },
3244                protocol,
3245            )
3246            .await?
3247        }
3248        ClientMessage::ForwardStatusRequest { line_instance } => {
3249            let forwarding = state
3250                .forwarding_by_line
3251                .get(&line_instance)
3252                .cloned()
3253                .unwrap_or_default();
3254            send_message(
3255                stream,
3256                &ServerMessage::ForwardStatus {
3257                    line_instance,
3258                    forward_all: forwarding.all,
3259                    forward_busy: forwarding.busy,
3260                    forward_no_answer: forwarding.no_answer,
3261                },
3262                protocol,
3263            )
3264            .await?;
3265        }
3266        ClientMessage::SpeedDialStatusRequest {
3267            speed_dial_instance,
3268        } => {
3269            send_station_ui_message(
3270                stream,
3271                state,
3272                &speed_dial_status(&state.device, speed_dial_instance),
3273            )
3274            .await?;
3275        }
3276        ClientMessage::FeatureStatusRequest {
3277            index,
3278            capabilities,
3279        } => {
3280            if let Some(mut message) = feature_status(&state.device, index, capabilities) {
3281                if let ServerMessage::FeatureStatus {
3282                    button_type,
3283                    state: feature_state,
3284                    ..
3285                } = &mut message
3286                    && let Some(cached) = state.feature_states.get(&index)
3287                {
3288                    *button_type = cached.button_type;
3289                    *feature_state = cached.state;
3290                }
3291                send_station_ui_message(stream, state, &message).await?;
3292            }
3293        }
3294        ClientMessage::ServiceUrlStatusRequest { index } => {
3295            if let Some(message) = service_url_status(&state.device, index) {
3296                send_station_ui_message(stream, state, &message).await?;
3297            }
3298        }
3299        ClientMessage::SubscriptionStatusRequest(request) => {
3300            send_message(
3301                stream,
3302                &ServerMessage::SubscriptionStatus {
3303                    transaction_id: request.transaction_id,
3304                    feature_id: request.feature_id,
3305                    timer_seconds: 0,
3306                    cause: SubscriptionCause::RouteFailure,
3307                },
3308                protocol,
3309            )
3310            .await?;
3311        }
3312        ClientMessage::RegisterAvailableLines { .. } => {
3313            debug!(device_id = %state.device.id, "phone finished registering available lines");
3314        }
3315        ClientMessage::OffHook {
3316            line_instance,
3317            call_reference,
3318        } => {
3319            if let Some(active_call) = find_call(state, call_reference)
3320                && !matches!(
3321                    active_call.state,
3322                    CallState::RingIn | CallState::CallWaiting | CallState::OnHook
3323                )
3324            {
3325                debug!(
3326                    device_id = %state.device.id,
3327                    call_id = ?active_call.call_id,
3328                    call_state = ?active_call.state,
3329                    line_instance,
3330                    call_reference,
3331                    "ignoring duplicate OffHook while a call is already active"
3332                );
3333                return Ok(());
3334            }
3335            let line = normalize_line(state, line_instance);
3336            let answer = find_answer_call(
3337                state,
3338                call_reference,
3339                line_instance,
3340                *context
3341                    .call_answer_order
3342                    .read()
3343                    .expect("SCCP call-answer-order lock poisoned"),
3344            )
3345            .cloned();
3346            let answering = answer.is_some();
3347            let call = answer.unwrap_or_else(|| {
3348                ensure_phone_call(state, call_reference, line, &context.next_call_id)
3349            });
3350            if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3351                stored.state = CallState::OffHook;
3352            }
3353            state.active_call_id = Some(call.call_id);
3354            if answering {
3355                begin_answer_ui(stream, &call, protocol).await?;
3356            } else {
3357                state.active_key_mode = KeyMode::OffHook;
3358                begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3359            }
3360            context
3361                .event_tx
3362                .send(Event::device(
3363                    state.device.id.clone(),
3364                    state.generation,
3365                    DeviceEventKind::OffHook {
3366                        call_id: call.call_id,
3367                        line_instance: LineInstance::new(line),
3368                    },
3369                ))
3370                .await
3371                .map_err(|_| ServerError::Stopped)?;
3372        }
3373        ClientMessage::OnHook {
3374            line_instance,
3375            call_reference,
3376        } => {
3377            state.pending_media_path_release = None;
3378            if let Some(call) = find_call(state, call_reference).cloned() {
3379                let line = if line_instance == 0 {
3380                    call.line_instance
3381                } else {
3382                    line_instance
3383                };
3384                complete_on_hook(stream, state, context, call, line).await?;
3385            }
3386        }
3387        ClientMessage::HookFlash {
3388            line_instance,
3389            call_reference,
3390        } => {
3391            let line_instance = normalize_line(state, line_instance);
3392            let call_id = find_call(state, call_reference).map(|call| call.call_id);
3393            context
3394                .event_tx
3395                .send(Event::device(
3396                    state.device.id.clone(),
3397                    state.generation,
3398                    DeviceEventKind::HookFlash {
3399                        call_id,
3400                        line_instance: LineInstance::new(line_instance),
3401                    },
3402                ))
3403                .await
3404                .map_err(|_| ServerError::Stopped)?;
3405        }
3406        ClientMessage::KeypadButton {
3407            button,
3408            call_reference,
3409            ..
3410        } => {
3411            if let Some(call) = find_call(state, call_reference) {
3412                if matches!(button, Digit::Unknown(_)) {
3413                    return Ok(());
3414                }
3415                let call = call.clone();
3416                if matches!(
3417                    call.state,
3418                    CallState::Connected
3419                        | CallState::Hold
3420                        | CallState::HoldYellow
3421                        | CallState::HoldRed
3422                ) && call.media.transmit.state.is_open()
3423                    && call.media.transmit.telephone_event_payload != 0
3424                {
3425                    // The handset sends connected-call digits in RTP when a
3426                    // telephone-event payload was negotiated. Forwarding the
3427                    // signaling copy would produce duplicate DTMF in the PBX.
3428                    return Ok(());
3429                }
3430                let collecting = matches!(call.state, CallState::OffHook | CallState::Transfer);
3431                if collecting && state.active_key_mode != KeyMode::DigitsFollowing {
3432                    state.active_key_mode = KeyMode::DigitsFollowing;
3433                    send_message(
3434                        stream,
3435                        &ServerMessage::StopTone {
3436                            line_instance: call.line_instance,
3437                            call_reference: call.wire_reference,
3438                        },
3439                        protocol,
3440                    )
3441                    .await?;
3442                    send_message(
3443                        stream,
3444                        &ServerMessage::SelectSoftKeys {
3445                            line_instance: call.line_instance,
3446                            call_reference: call.wire_reference,
3447                            set: KeyMode::DigitsFollowing,
3448                            valid_mask: state.device.soft_keys.valid_mask(KeyMode::DigitsFollowing),
3449                        },
3450                        protocol,
3451                    )
3452                    .await?;
3453                }
3454                if collecting && let Some(character) = digit_character(button) {
3455                    let number = if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3456                        stored.dialed_number.push(character);
3457                        stored.dialed_number.clone()
3458                    } else {
3459                        String::new()
3460                    };
3461                    if button == context.config.dial_terminator {
3462                        remember_last_number(state, call.line_instance, &number, &context.config);
3463                    }
3464                }
3465                context
3466                    .event_tx
3467                    .send(Event::device(
3468                        state.device.id.clone(),
3469                        state.generation,
3470                        DeviceEventKind::Digit {
3471                            call_id: call.call_id,
3472                            digit: button,
3473                        },
3474                    ))
3475                    .await
3476                    .map_err(|_| ServerError::Stopped)?;
3477            }
3478        }
3479        ClientMessage::EnblocCall {
3480            called_party,
3481            line_instance,
3482            ..
3483        } => {
3484            let line = normalize_line(state, line_instance);
3485            let existing = state
3486                .calls_by_id
3487                .values()
3488                .find(|call| call.line_instance == line && call.state != CallState::OnHook)
3489                .cloned();
3490            let created = existing.is_none();
3491            let call = existing
3492                .unwrap_or_else(|| ensure_phone_call(state, 0, line, &context.next_call_id));
3493            if created {
3494                state.active_key_mode = KeyMode::OffHook;
3495                begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3496                context
3497                    .event_tx
3498                    .send(Event::device(
3499                        state.device.id.clone(),
3500                        state.generation,
3501                        DeviceEventKind::OffHook {
3502                            call_id: call.call_id,
3503                            line_instance: LineInstance::new(line),
3504                        },
3505                    ))
3506                    .await
3507                    .map_err(|_| ServerError::Stopped)?;
3508            }
3509            if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3510                stored.dialed_number.clone_from(&called_party);
3511            }
3512            remember_last_number(state, call.line_instance, &called_party, &context.config);
3513            context
3514                .event_tx
3515                .send(Event::device(
3516                    state.device.id.clone(),
3517                    state.generation,
3518                    DeviceEventKind::EnblocCall {
3519                        call_id: call.call_id,
3520                        line_instance: LineInstance::new(line),
3521                        number: called_party,
3522                    },
3523                ))
3524                .await
3525                .map_err(|_| ServerError::Stopped)?;
3526        }
3527        ClientMessage::SoftKeyEvent {
3528            event,
3529            line_instance,
3530            call_reference,
3531        } => {
3532            let received_soft_key = SoftKey::from(event);
3533            if !state
3534                .device
3535                .soft_keys
3536                .allows(state.active_key_mode, received_soft_key)
3537            {
3538                debug!(
3539                    device_id = %state.device.id,
3540                    mode = state.active_key_mode.wire_value(),
3541                    event,
3542                    "ignoring unavailable soft-key event"
3543                );
3544                return Ok(());
3545            }
3546            let line = normalize_line(state, line_instance);
3547            let mut soft_key = received_soft_key;
3548            let ringing_call = find_answer_call(
3549                state,
3550                call_reference,
3551                line_instance,
3552                *context
3553                    .call_answer_order
3554                    .read()
3555                    .expect("SCCP call-answer-order lock poisoned"),
3556            );
3557            let mut call_id = if matches!(soft_key, SoftKey::Answer | SoftKey::NewCall)
3558                && let Some(call) = ringing_call
3559            {
3560                soft_key = SoftKey::Answer;
3561                Some(call.call_id)
3562            } else {
3563                find_call(state, call_reference).map(|call| call.call_id)
3564            };
3565            if soft_key == SoftKey::MeetMe
3566                && call_id.is_some_and(|call_id| {
3567                    state
3568                        .calls_by_id
3569                        .get(&call_id)
3570                        .is_some_and(|call| call.state != CallState::OffHook)
3571                })
3572            {
3573                call_id = None;
3574            }
3575            if matches!(
3576                soft_key,
3577                SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3578            ) && call_id.is_some_and(|call_id| {
3579                state
3580                    .calls_by_id
3581                    .get(&call_id)
3582                    .is_some_and(|call| call.state == CallState::OnHook)
3583            }) {
3584                call_id = None;
3585            }
3586            if soft_key == SoftKey::Redial {
3587                begin_redial(stream, state, context, line, call_id).await?;
3588                return Ok(());
3589            }
3590            if call_id.is_none()
3591                && matches!(
3592                    soft_key,
3593                    SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3594                )
3595            {
3596                let call = if soft_key == SoftKey::MeetMe {
3597                    reserve_phone_call(state, line, &context.next_call_id)
3598                } else {
3599                    ensure_phone_call(state, 0, line, &context.next_call_id)
3600                };
3601                state.active_call_id = Some(call.call_id);
3602                state.active_key_mode = KeyMode::OffHook;
3603                begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3604                context
3605                    .event_tx
3606                    .send(Event::device(
3607                        state.device.id.clone(),
3608                        state.generation,
3609                        DeviceEventKind::OffHook {
3610                            call_id: call.call_id,
3611                            line_instance: LineInstance::new(line),
3612                        },
3613                    ))
3614                    .await
3615                    .map_err(|_| ServerError::Stopped)?;
3616                call_id = Some(call.call_id);
3617            }
3618            if soft_key == SoftKey::Backspace
3619                && let Some(call_id) = call_id
3620                && let Some(call) = state.calls_by_id.get_mut(&call_id)
3621            {
3622                call.dialed_number.pop();
3623                let call = call.clone();
3624                send_message(
3625                    stream,
3626                    &ServerMessage::BackspaceResponse {
3627                        line_instance: call.line_instance,
3628                        call_reference: call.wire_reference,
3629                    },
3630                    protocol,
3631                )
3632                .await?;
3633            }
3634            if soft_key == SoftKey::Dial
3635                && let Some(call) = call_id.and_then(|call_id| state.calls_by_id.get(&call_id))
3636            {
3637                let line_instance = call.line_instance;
3638                let number = call.dialed_number.clone();
3639                remember_last_number(state, line_instance, &number, &context.config);
3640            }
3641            if soft_key == SoftKey::Answer
3642                && let Some(call) = call_id.and_then(|call_id| state.calls_by_id.get_mut(&call_id))
3643            {
3644                call.state = CallState::OffHook;
3645                let call = call.clone();
3646                state.active_call_id = Some(call.call_id);
3647                begin_answer_ui(stream, &call, protocol).await?;
3648            }
3649            context
3650                .event_tx
3651                .send(Event::device(
3652                    state.device.id.clone(),
3653                    state.generation,
3654                    DeviceEventKind::SoftKey {
3655                        call_id,
3656                        line_instance: LineInstance::new(line),
3657                        soft_key,
3658                    },
3659                ))
3660                .await
3661                .map_err(|_| ServerError::Stopped)?;
3662        }
3663        ClientMessage::Stimulus {
3664            stimulus,
3665            instance,
3666            call_reference,
3667            ..
3668        } => {
3669            let mut call_id = find_call(state, call_reference).map(|call| call.call_id);
3670            if stimulus == Stimulus::MeetMeConference
3671                && call_id.is_some_and(|call_id| {
3672                    state
3673                        .calls_by_id
3674                        .get(&call_id)
3675                        .is_some_and(|call| call.state != CallState::OffHook)
3676                })
3677            {
3678                call_id = None;
3679            }
3680            if matches!(
3681                stimulus,
3682                Stimulus::Line
3683                    | Stimulus::NewCall
3684                    | Stimulus::CallPickup
3685                    | Stimulus::GroupCallPickup
3686            ) && call_id.is_some_and(|call_id| {
3687                state
3688                    .calls_by_id
3689                    .get(&call_id)
3690                    .is_some_and(|call| call.state == CallState::OnHook)
3691            }) {
3692                call_id = None;
3693            }
3694            if stimulus == Stimulus::Line {
3695                let line = normalize_line(state, instance);
3696                if call_id.is_none() {
3697                    let call = ensure_phone_call(state, 0, line, &context.next_call_id);
3698                    state.active_key_mode = KeyMode::OffHook;
3699                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3700                        .await?;
3701                    context
3702                        .event_tx
3703                        .send(Event::device(
3704                            state.device.id.clone(),
3705                            state.generation,
3706                            DeviceEventKind::OffHook {
3707                                call_id: call.call_id,
3708                                line_instance: LineInstance::new(line),
3709                            },
3710                        ))
3711                        .await
3712                        .map_err(|_| ServerError::Stopped)?;
3713                } else {
3714                    context
3715                        .event_tx
3716                        .send(Event::device(
3717                            state.device.id.clone(),
3718                            state.generation,
3719                            DeviceEventKind::LineButton {
3720                                line_instance: LineInstance::new(line),
3721                                call_id,
3722                            },
3723                        ))
3724                        .await
3725                        .map_err(|_| ServerError::Stopped)?;
3726                }
3727            } else if stimulus == Stimulus::ParkingLot {
3728                let configured = state.device.buttons.iter().any(|button| {
3729                    matches!(
3730                        button,
3731                        ButtonDefinition::Feature(feature)
3732                            if feature.instance == instance
3733                                && feature.feature == ButtonType::ParkingLot
3734                    )
3735                });
3736                if !configured {
3737                    debug!(
3738                        device_id = %state.device.id,
3739                        instance,
3740                        "ignoring unconfigured parking-lot button stimulus"
3741                    );
3742                    return Ok(());
3743                }
3744                let line_instance = call_id
3745                    .and_then(|call_id| state.calls_by_id.get(&call_id))
3746                    .map_or_else(|| normalize_line(state, 0), |call| call.line_instance);
3747                context
3748                    .event_tx
3749                    .send(Event::device(
3750                        state.device.id.clone(),
3751                        state.generation,
3752                        DeviceEventKind::ParkingLotButton {
3753                            instance: LineInstance::new(instance),
3754                            call_id,
3755                            line_instance: LineInstance::new(line_instance),
3756                        },
3757                    ))
3758                    .await
3759                    .map_err(|_| ServerError::Stopped)?;
3760            } else if stimulus == Stimulus::Privacy {
3761                let configured = state.device.buttons.iter().any(|button| {
3762                    matches!(
3763                        button,
3764                        ButtonDefinition::Feature(feature)
3765                            if feature.instance == instance
3766                                && feature.feature == ButtonType::Feature
3767                    )
3768                });
3769                if !configured {
3770                    debug!(
3771                        device_id = %state.device.id,
3772                        instance,
3773                        "ignoring unconfigured generic feature-button stimulus"
3774                    );
3775                    return Ok(());
3776                }
3777                context
3778                    .event_tx
3779                    .send(Event::device(
3780                        state.device.id.clone(),
3781                        state.generation,
3782                        DeviceEventKind::FeatureButton {
3783                            instance: LineInstance::new(instance),
3784                        },
3785                    ))
3786                    .await
3787                    .map_err(|_| ServerError::Stopped)?;
3788            } else if stimulus == Stimulus::DoNotDisturb {
3789                let configured = state.device.buttons.iter().any(|button| {
3790                    matches!(
3791                        button,
3792                        ButtonDefinition::Feature(feature)
3793                            if feature.instance == instance
3794                                && feature.feature == ButtonType::DoNotDisturb
3795                    )
3796                });
3797                if !configured {
3798                    debug!(
3799                        device_id = %state.device.id,
3800                        instance,
3801                        "ignoring unconfigured do-not-disturb button stimulus"
3802                    );
3803                    return Ok(());
3804                }
3805                context
3806                    .event_tx
3807                    .send(Event::device(
3808                        state.device.id.clone(),
3809                        state.generation,
3810                        DeviceEventKind::DoNotDisturbButton {
3811                            instance: LineInstance::new(instance),
3812                        },
3813                    ))
3814                    .await
3815                    .map_err(|_| ServerError::Stopped)?;
3816            } else if stimulus == Stimulus::Mobility {
3817                let configured = state.device.buttons.iter().any(|button| {
3818                    matches!(
3819                        button,
3820                        ButtonDefinition::Feature(feature)
3821                            if feature.instance == instance
3822                                && feature.feature == ButtonType::Mobility
3823                    )
3824                });
3825                if !configured {
3826                    debug!(
3827                        device_id = %state.device.id,
3828                        instance,
3829                        "ignoring unconfigured mobility button stimulus"
3830                    );
3831                    return Ok(());
3832                }
3833                context
3834                    .event_tx
3835                    .send(Event::device(
3836                        state.device.id.clone(),
3837                        state.generation,
3838                        DeviceEventKind::MobilityButton {
3839                            instance: LineInstance::new(instance),
3840                        },
3841                    ))
3842                    .await
3843                    .map_err(|_| ServerError::Stopped)?;
3844            } else if stimulus == Stimulus::Voicemail {
3845                let configured = state.device.buttons.iter().any(|button| {
3846                    matches!(
3847                        button,
3848                        ButtonDefinition::Feature(feature)
3849                            if feature.instance == instance
3850                                && feature.feature == ButtonType::Voicemail
3851                    )
3852                });
3853                if !configured {
3854                    debug!(
3855                        device_id = %state.device.id,
3856                        instance,
3857                        "ignoring unconfigured voicemail button stimulus"
3858                    );
3859                    return Ok(());
3860                }
3861                let line = call_id
3862                    .and_then(|call_id| state.calls_by_id.get(&call_id))
3863                    .map_or_else(
3864                        || normalize_line(state, instance),
3865                        |call| call.line_instance,
3866                    );
3867                let call = call_id
3868                    .and_then(|call_id| state.calls_by_id.get(&call_id).cloned())
3869                    .unwrap_or_else(|| ensure_phone_call(state, 0, line, &context.next_call_id));
3870                if call_id.is_none() {
3871                    state.active_call_id = Some(call.call_id);
3872                    state.active_key_mode = KeyMode::OffHook;
3873                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3874                        .await?;
3875                    context
3876                        .event_tx
3877                        .send(Event::device(
3878                            state.device.id.clone(),
3879                            state.generation,
3880                            DeviceEventKind::OffHook {
3881                                call_id: call.call_id,
3882                                line_instance: LineInstance::new(line),
3883                            },
3884                        ))
3885                        .await
3886                        .map_err(|_| ServerError::Stopped)?;
3887                }
3888                context
3889                    .event_tx
3890                    .send(Event::device(
3891                        state.device.id.clone(),
3892                        state.generation,
3893                        DeviceEventKind::VoicemailButton {
3894                            call_id: call.call_id,
3895                            line_instance: LineInstance::new(line),
3896                        },
3897                    ))
3898                    .await
3899                    .map_err(|_| ServerError::Stopped)?;
3900            } else {
3901                let line = normalize_line(state, instance);
3902                let Some(soft_key) = stimulus_soft_key(stimulus) else {
3903                    debug!(
3904                        device_id = %state.device.id,
3905                        stimulus = stimulus.wire_value(),
3906                        "ignoring stimulus without a soft-key action mapping"
3907                    );
3908                    return Ok(());
3909                };
3910                if !state
3911                    .device
3912                    .soft_keys
3913                    .allows(state.active_key_mode, soft_key)
3914                {
3915                    debug!(
3916                        device_id = %state.device.id,
3917                        mode = state.active_key_mode.wire_value(),
3918                        stimulus = stimulus.wire_value(),
3919                        "ignoring unavailable soft-key stimulus"
3920                    );
3921                    return Ok(());
3922                }
3923                if soft_key == SoftKey::Redial {
3924                    begin_redial(stream, state, context, line, call_id).await?;
3925                    return Ok(());
3926                }
3927                if matches!(
3928                    soft_key,
3929                    SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3930                ) && call_id.is_none()
3931                {
3932                    let call = if soft_key == SoftKey::MeetMe {
3933                        reserve_phone_call(state, line, &context.next_call_id)
3934                    } else {
3935                        ensure_phone_call(state, 0, line, &context.next_call_id)
3936                    };
3937                    state.active_call_id = Some(call.call_id);
3938                    state.active_key_mode = KeyMode::OffHook;
3939                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3940                        .await?;
3941                    context
3942                        .event_tx
3943                        .send(Event::device(
3944                            state.device.id.clone(),
3945                            state.generation,
3946                            DeviceEventKind::OffHook {
3947                                call_id: call.call_id,
3948                                line_instance: LineInstance::new(line),
3949                            },
3950                        ))
3951                        .await
3952                        .map_err(|_| ServerError::Stopped)?;
3953                    call_id = Some(call.call_id);
3954                }
3955                context
3956                    .event_tx
3957                    .send(Event::device(
3958                        state.device.id.clone(),
3959                        state.generation,
3960                        DeviceEventKind::SoftKey {
3961                            call_id,
3962                            line_instance: LineInstance::new(line),
3963                            soft_key,
3964                        },
3965                    ))
3966                    .await
3967                    .map_err(|_| ServerError::Stopped)?;
3968            }
3969        }
3970        ClientMessage::MulticastMediaReceptionAck {
3971            status,
3972            passthrough_party_id,
3973            call_reference,
3974        } => {
3975            let Some(key) =
3976                find_multicast_receive_key(state, call_reference.get(), passthrough_party_id.get())
3977            else {
3978                debug!(
3979                    device_id = %state.device.id,
3980                    "ignored stale or mismatched multicast reception acknowledgement"
3981                );
3982                return Ok(());
3983            };
3984            if status == MediaStatus::Ok {
3985                let route = {
3986                    let receive = state
3987                        .multicast
3988                        .get_mut(&key)
3989                        .and_then(|session| session.receive.as_mut())
3990                        .expect("multicast key came from current receive state");
3991                    receive.state = MulticastReceiveState::Open;
3992                    receive.route
3993                };
3994                context
3995                    .event_tx
3996                    .send(Event::device(
3997                        state.device.id.clone(),
3998                        state.generation,
3999                        DeviceEventKind::MulticastReceptionStarted {
4000                            conference_id: key.conference_id,
4001                            call_id: key.call_id,
4002                            route,
4003                        },
4004                    ))
4005                    .await
4006                    .map_err(|_| ServerError::Stopped)?;
4007            } else {
4008                if let Some(stop) = take_multicast_stop(state, key, true) {
4009                    send_message(stream, &stop, protocol).await?;
4010                }
4011                context
4012                    .event_tx
4013                    .send(Event::device(
4014                        state.device.id.clone(),
4015                        state.generation,
4016                        DeviceEventKind::MulticastReceptionFailed {
4017                            conference_id: key.conference_id,
4018                            call_id: key.call_id,
4019                            status,
4020                        },
4021                    ))
4022                    .await
4023                    .map_err(|_| ServerError::Stopped)?;
4024            }
4025        }
4026        ClientMessage::OpenReceiveChannelAck {
4027            status,
4028            address,
4029            port,
4030            call_reference,
4031            passthrough_party_id,
4032        } => {
4033            if let Some(call_id) =
4034                find_receive_media_call_id(state, call_reference, passthrough_party_id)
4035            {
4036                let call = state
4037                    .calls_by_id
4038                    .get(&call_id)
4039                    .expect("media call identifier came from session state")
4040                    .clone();
4041                if call.media.receive.state != MediaChannelState::Opening {
4042                    debug!(
4043                        device_id = %state.device.id,
4044                        call_id = ?call.call_id,
4045                        state = ?call.media.receive.state,
4046                        "ignored stale receive-channel acknowledgement"
4047                    );
4048                    return Ok(());
4049                }
4050                let endpoint = MediaEndpoint {
4051                    address,
4052                    rtp_port: port,
4053                    rtcp_port: port.saturating_add(1),
4054                    codec: call.media.codec,
4055                    packet_ms: call.media.packet_ms,
4056                    max_frames_per_packet: call.media.max_frames_per_packet,
4057                    telephone_event_payload: call.media.receive.telephone_event_payload,
4058                };
4059                let stored = state
4060                    .calls_by_id
4061                    .get_mut(&call_id)
4062                    .expect("media call identifier came from session state");
4063                let implied_transmit = if status == MediaStatus::Ok {
4064                    stored.media.receive.state = MediaChannelState::Open;
4065                    stored.media.receive.peer = Some(endpoint);
4066                    if let Some(endpoint) = stored.media.coupled_transmit_endpoint.take() {
4067                        stored.media.transmit.state = MediaChannelState::Open;
4068                        stored.media.transmit.peer = Some(endpoint);
4069                        stored.media.transmit.deadline = None;
4070                        Some(endpoint)
4071                    } else {
4072                        None
4073                    }
4074                } else {
4075                    stored.media.receive.state = MediaChannelState::Closed;
4076                    stored.media.receive.peer = None;
4077                    if stored.media.coupled_transmit_endpoint.take().is_some() {
4078                        stored.media.transmit.state = MediaChannelState::Closed;
4079                        stored.media.transmit.peer = None;
4080                        stored.media.transmit.deadline = None;
4081                    }
4082                    None
4083                };
4084                stored.media.receive.deadline = None;
4085                context
4086                    .event_tx
4087                    .send(Event::device(
4088                        state.device.id.clone(),
4089                        state.generation,
4090                        DeviceEventKind::ReceiveChannelOpened {
4091                            call_id: call.call_id,
4092                            status,
4093                            endpoint,
4094                        },
4095                    ))
4096                    .await
4097                    .map_err(|_| ServerError::Stopped)?;
4098                if let Some(endpoint) = implied_transmit {
4099                    context
4100                        .event_tx
4101                        .send(Event::device(
4102                            state.device.id.clone(),
4103                            state.generation,
4104                            DeviceEventKind::TransmitChannelImplied {
4105                                call_id: call.call_id,
4106                                endpoint,
4107                            },
4108                        ))
4109                        .await
4110                        .map_err(|_| ServerError::Stopped)?;
4111                }
4112            }
4113        }
4114        ClientMessage::StartMediaTransmissionAck(ack) => {
4115            if let Some(call_id) = find_transmit_media_call_id(
4116                state,
4117                ack.conference_id,
4118                ack.call_reference,
4119                ack.passthrough_party_id,
4120            ) {
4121                let call = state
4122                    .calls_by_id
4123                    .get(&call_id)
4124                    .expect("media call identifier came from session state")
4125                    .clone();
4126                if call.media.transmit.state != MediaChannelState::Opening {
4127                    debug!(
4128                        device_id = %state.device.id,
4129                        call_id = ?call.call_id,
4130                        state = ?call.media.transmit.state,
4131                        "ignored stale transmit-channel acknowledgement"
4132                    );
4133                    return Ok(());
4134                }
4135                let endpoint = MediaEndpoint {
4136                    address: ack.address,
4137                    rtp_port: ack.port,
4138                    rtcp_port: ack.port.saturating_add(1),
4139                    codec: call.media.codec,
4140                    packet_ms: call.media.packet_ms,
4141                    max_frames_per_packet: call.media.max_frames_per_packet,
4142                    telephone_event_payload: call.media.transmit.telephone_event_payload,
4143                };
4144                let stored = state
4145                    .calls_by_id
4146                    .get_mut(&call_id)
4147                    .expect("media call identifier came from session state");
4148                let coupled = stored.media.coupled_transmit_endpoint.take().is_some();
4149                if ack.status == MediaStatus::Ok {
4150                    stored.media.transmit.state = MediaChannelState::Open;
4151                    stored.media.transmit.peer = Some(endpoint);
4152                } else {
4153                    stored.media.transmit.state = MediaChannelState::Closed;
4154                    stored.media.transmit.peer = None;
4155                    if coupled {
4156                        stored.media.receive.state = MediaChannelState::Closed;
4157                        stored.media.receive.deadline = None;
4158                        stored.media.receive.peer = None;
4159                    }
4160                }
4161                stored.media.transmit.deadline = None;
4162                context
4163                    .event_tx
4164                    .send(Event::device(
4165                        state.device.id.clone(),
4166                        state.generation,
4167                        DeviceEventKind::TransmitChannelStarted {
4168                            call_id: call.call_id,
4169                            status: ack.status,
4170                            endpoint,
4171                        },
4172                    ))
4173                    .await
4174                    .map_err(|_| ServerError::Stopped)?;
4175            }
4176        }
4177        ClientMessage::Alarm {
4178            severity,
4179            text,
4180            parameters,
4181        } => {
4182            context
4183                .event_tx
4184                .send(Event::device(
4185                    state.device.id.clone(),
4186                    state.generation,
4187                    DeviceEventKind::Alarm {
4188                        severity,
4189                        text,
4190                        parameters,
4191                    },
4192                ))
4193                .await
4194                .map_err(|_| ServerError::Stopped)?;
4195        }
4196        ClientMessage::XmlAlarm(message) => match parse_phone_alarm(message.xml_bytes()) {
4197            Ok(telemetry) => {
4198                context
4199                    .event_tx
4200                    .send(Event::device(
4201                        state.device.id.clone(),
4202                        state.generation,
4203                        DeviceEventKind::XmlAlarm { telemetry },
4204                    ))
4205                    .await
4206                    .map_err(|_| ServerError::Stopped)?;
4207            }
4208            Err(error) => {
4209                warn!(
4210                    device_id = %state.device.id,
4211                    payload_len = message.xml_bytes().len(),
4212                    %error,
4213                    "rejected SCCP XML alarm"
4214                );
4215            }
4216        },
4217        ClientMessage::LocationInfo { xml } => match parse_phone_location(xml.as_bytes()) {
4218            Ok(telemetry) => {
4219                context
4220                    .event_tx
4221                    .send(Event::device(
4222                        state.device.id.clone(),
4223                        state.generation,
4224                        DeviceEventKind::LocationInformation { telemetry },
4225                    ))
4226                    .await
4227                    .map_err(|_| ServerError::Stopped)?;
4228            }
4229            Err(error) => {
4230                warn!(
4231                    device_id = %state.device.id,
4232                    payload_len = xml.len(),
4233                    %error,
4234                    "rejected SCCP location information"
4235                );
4236            }
4237        },
4238        ClientMessage::Unregister { .. } => {
4239            send_message(stream, &ServerMessage::UnregisterAck, protocol).await?;
4240        }
4241        ClientMessage::CallCountRequest { .. } => {
4242            send_message(stream, &ServerMessage::CallCountResponse, protocol).await?;
4243        }
4244        ClientMessage::ConnectionStatisticsResponse(statistics) => {
4245            collect_connection_statistics(state, statistics, context).await?;
4246        }
4247        ClientMessage::MediaTransmissionFailure {
4248            conference_id,
4249            passthrough_party_id,
4250            address,
4251            port,
4252            call_reference,
4253            status,
4254        } => {
4255            if let Some(key) = find_multicast_transmit_key(
4256                state,
4257                conference_id,
4258                call_reference,
4259                passthrough_party_id,
4260                address,
4261                port,
4262            ) {
4263                if let Some(stop) = take_multicast_stop(state, key, false) {
4264                    send_message(stream, &stop, protocol).await?;
4265                }
4266                context
4267                    .event_tx
4268                    .send(Event::device(
4269                        state.device.id.clone(),
4270                        state.generation,
4271                        DeviceEventKind::MulticastTransmissionFailed {
4272                            conference_id: key.conference_id,
4273                            call_id: key.call_id,
4274                            status,
4275                            address,
4276                            port,
4277                        },
4278                    ))
4279                    .await
4280                    .map_err(|_| ServerError::Stopped)?;
4281                return Ok(());
4282            }
4283            let Some(call_id) = find_transmit_media_call_id(
4284                state,
4285                conference_id,
4286                call_reference,
4287                passthrough_party_id,
4288            ) else {
4289                return Ok(());
4290            };
4291            let call = state
4292                .calls_by_id
4293                .get(&call_id)
4294                .expect("media call identifier came from session state")
4295                .clone();
4296            let Some(endpoint) = call.media.transmit.peer else {
4297                return Ok(());
4298            };
4299            if call.media.transmit.state != MediaChannelState::Open
4300                || (conference_id != 0 && conference_id != call.wire_reference)
4301                || endpoint.address != address
4302                || endpoint.rtp_port != port
4303            {
4304                debug!(
4305                    device_id = %state.device.id,
4306                    call_id = ?call.call_id,
4307                    "ignored stale or mismatched media-transmission failure"
4308                );
4309                return Ok(());
4310            }
4311            let stored = state
4312                .calls_by_id
4313                .get_mut(&call_id)
4314                .expect("media call identifier came from session state");
4315            stored.media.transmit.state = MediaChannelState::Closed;
4316            stored.media.transmit.peer = None;
4317            context
4318                .event_tx
4319                .send(Event::device(
4320                    state.device.id.clone(),
4321                    state.generation,
4322                    DeviceEventKind::MediaTransmissionFailed {
4323                        call_id,
4324                        status,
4325                        endpoint,
4326                    },
4327                ))
4328                .await
4329                .map_err(|_| ServerError::Stopped)?;
4330        }
4331        ClientMessage::HeadsetStatus { enabled } => {
4332            if state.headset_enabled != enabled {
4333                state.headset_enabled = enabled;
4334                context
4335                    .event_tx
4336                    .send(Event::device(
4337                        state.device.id.clone(),
4338                        state.generation,
4339                        DeviceEventKind::HeadsetStatusChanged { enabled },
4340                    ))
4341                    .await
4342                    .map_err(|_| ServerError::Stopped)?;
4343            }
4344        }
4345        ClientMessage::MediaPathEvent {
4346            path,
4347            event: media_path_event,
4348        } => {
4349            if state.media_path_states.get(&path) != Some(&media_path_event) {
4350                state.media_path_states.insert(path, media_path_event);
4351                if media_path_event == crate::message::values::MediaPathEvent::On {
4352                    state.pending_media_path_release = None;
4353                } else if media_path_event == crate::message::values::MediaPathEvent::Off
4354                    && is_local_audio_path(path)
4355                    && !has_active_media_path(state)
4356                    && let Some(call_id) = active_media_path_call(state)
4357                {
4358                    state.pending_media_path_release = Some(PendingMediaPathRelease {
4359                        call_id,
4360                        path,
4361                        deadline: Instant::now() + MEDIA_PATH_RELEASE_GRACE,
4362                    });
4363                }
4364                context
4365                    .event_tx
4366                    .send(Event::device(
4367                        state.device.id.clone(),
4368                        state.generation,
4369                        DeviceEventKind::MediaPathChanged {
4370                            path,
4371                            event: media_path_event,
4372                        },
4373                    ))
4374                    .await
4375                    .map_err(|_| ServerError::Stopped)?;
4376            }
4377        }
4378        ClientMessage::MediaPathCapability { .. } => {}
4379        message @ (ClientMessage::IpPort { .. }
4380        | ClientMessage::OffHookWithCallingParty { .. }
4381        | ClientMessage::MediaResourceNotification(_)
4382        | ClientMessage::SubscribeDtmfPayloadResponse(_)
4383        | ClientMessage::UnsubscribeDtmfPayloadResponse(_)
4384        | ClientMessage::PortResponse(_)) => {
4385            debug!(device_id = %state.device.id, message = ?message, "consumed SCCP telemetry");
4386        }
4387        ClientMessage::DeviceToUserData(message) => {
4388            handle_phone_service_message(
4389                state,
4390                context,
4391                crate::message::id::DEVICE_TO_USER_DATA,
4392                PhoneServiceMessageKind::Data,
4393                PhoneServiceRouting {
4394                    application_id: ApplicationId::new(message.application_id),
4395                    line_instance: LineInstance::new(message.line_instance),
4396                    call_reference: CallReference::new(message.call_reference),
4397                    transaction_id: TransactionId::new(message.transaction_id),
4398                },
4399                None,
4400                &message.data,
4401            )
4402            .await?;
4403        }
4404        ClientMessage::DeviceToUserDataResponse(message) => {
4405            handle_phone_service_message(
4406                state,
4407                context,
4408                crate::message::id::DEVICE_TO_USER_DATA_RESPONSE,
4409                PhoneServiceMessageKind::Response,
4410                PhoneServiceRouting {
4411                    application_id: ApplicationId::new(message.application_id),
4412                    line_instance: LineInstance::new(message.line_instance),
4413                    call_reference: CallReference::new(message.call_reference),
4414                    transaction_id: TransactionId::new(message.transaction_id),
4415                },
4416                None,
4417                &message.data,
4418            )
4419            .await?;
4420        }
4421        ClientMessage::DeviceToUserDataV1(message) => {
4422            handle_phone_service_message(
4423                state,
4424                context,
4425                crate::message::id::DEVICE_TO_USER_DATA_V1,
4426                PhoneServiceMessageKind::Data,
4427                PhoneServiceRouting {
4428                    application_id: ApplicationId::new(message.application_id),
4429                    line_instance: LineInstance::new(message.line_instance),
4430                    call_reference: CallReference::new(message.call_reference),
4431                    transaction_id: TransactionId::new(message.transaction_id),
4432                },
4433                Some(PhoneServiceExtendedRouting {
4434                    sequence_flag: message.sequence_flag,
4435                    display_priority: message.display_priority,
4436                    conference_id: message.conference_id,
4437                    application_instance_id: message.application_instance_id,
4438                    routing: message.routing,
4439                }),
4440                &message.data,
4441            )
4442            .await?;
4443        }
4444        ClientMessage::DeviceToUserDataResponseV1(message) => {
4445            handle_phone_service_message(
4446                state,
4447                context,
4448                crate::message::id::DEVICE_TO_USER_DATA_RESPONSE_V1,
4449                PhoneServiceMessageKind::Response,
4450                PhoneServiceRouting {
4451                    application_id: ApplicationId::new(message.application_id),
4452                    line_instance: LineInstance::new(message.line_instance),
4453                    call_reference: CallReference::new(message.call_reference),
4454                    transaction_id: TransactionId::new(message.transaction_id),
4455                },
4456                Some(PhoneServiceExtendedRouting {
4457                    sequence_flag: message.sequence_flag,
4458                    display_priority: message.display_priority,
4459                    conference_id: message.conference_id,
4460                    application_instance_id: message.application_instance_id,
4461                    routing: message.routing,
4462                }),
4463                &message.data,
4464            )
4465            .await?;
4466        }
4467        ClientMessage::OpenMultimediaReceiveChannelAck(ack) => {
4468            let Some(call_id) = state.calls_by_wire.get(&ack.call_reference.get()).copied() else {
4469                debug!(device_id = %state.device.id, "ignored video receive acknowledgement for an unknown call");
4470                return Ok(());
4471            };
4472            let Some((request, codec, requested_address_type)) =
4473                state.calls_by_id.get(&call_id).and_then(|call| {
4474                    call.video_receive.leg.as_ref().and_then(|leg| {
4475                        (leg.state == MediaChannelState::Opening
4476                            && leg.request.token().get() == ack.passthrough_party_id.get())
4477                        .then_some((leg.request, leg.codec, leg.requested_address_type))
4478                    })
4479                })
4480            else {
4481                debug!(device_id = %state.device.id, ?call_id, "ignored stale video receive acknowledgement");
4482                return Ok(());
4483            };
4484
4485            let event = if ack.status == MediaStatus::Ok {
4486                if !endpoint_is_usable(ack.endpoint)
4487                    || !address_matches_type(ack.endpoint.address, requested_address_type)
4488                {
4489                    debug!(device_id = %state.device.id, ?call_id, "ignored unusable video receive endpoint");
4490                    return Ok(());
4491                }
4492                let leg = state
4493                    .calls_by_id
4494                    .get_mut(&call_id)
4495                    .and_then(|call| call.video_receive.leg.as_mut())
4496                    .expect("correlated video receive leg remains present");
4497                debug_assert_eq!(leg.request, request);
4498                leg.state = MediaChannelState::Open;
4499                leg.deadline = None;
4500                DeviceEventKind::MultimediaReceiveChannelOpened {
4501                    call_id,
4502                    codec,
4503                    endpoint: ack.endpoint,
4504                    passthrough_party_id: ack.passthrough_party_id,
4505                }
4506            } else {
4507                let close = take_multimedia_receive_close(state, call_id)
4508                    .expect("correlated video receive leg remains present");
4509                send_message(stream, &close, protocol).await?;
4510                DeviceEventKind::MultimediaReceiveChannelFailed {
4511                    call_id,
4512                    codec,
4513                    status: ack.status,
4514                    endpoint: ack.endpoint,
4515                    passthrough_party_id: ack.passthrough_party_id,
4516                }
4517            };
4518            context
4519                .event_tx
4520                .send(Event::device(
4521                    state.device.id.clone(),
4522                    state.generation,
4523                    event,
4524                ))
4525                .await
4526                .map_err(|_| ServerError::Stopped)?;
4527        }
4528        ClientMessage::StartMultimediaTransmissionAck(ack) => {
4529            let Some(call_id) = state.calls_by_wire.get(&ack.call_reference.get()).copied() else {
4530                debug!(device_id = %state.device.id, "ignored video transmit acknowledgement for an unknown call");
4531                return Ok(());
4532            };
4533            let Some((request, codec, address_type)) =
4534                state.calls_by_id.get(&call_id).and_then(|call| {
4535                    call.video_transmit.leg.as_ref().and_then(|leg| {
4536                        (leg.state == MediaChannelState::Opening
4537                            && leg.request.token().get() == ack.passthrough_party_id.get()
4538                            && leg.conference_id == ack.conference_id)
4539                            .then_some((leg.request, leg.codec, leg.address_type))
4540                    })
4541                })
4542            else {
4543                debug!(device_id = %state.device.id, ?call_id, "ignored stale video transmit acknowledgement");
4544                return Ok(());
4545            };
4546
4547            let event = if ack.status == MediaStatus::Ok {
4548                if !endpoint_is_usable(ack.endpoint)
4549                    || !address_matches_type(ack.endpoint.address, address_type)
4550                {
4551                    debug!(device_id = %state.device.id, ?call_id, "ignored unusable video transmit endpoint");
4552                    return Ok(());
4553                }
4554                let leg = state
4555                    .calls_by_id
4556                    .get_mut(&call_id)
4557                    .and_then(|call| call.video_transmit.leg.as_mut())
4558                    .expect("correlated video transmit leg remains present");
4559                debug_assert_eq!(leg.request, request);
4560                leg.state = MediaChannelState::Open;
4561                leg.deadline = None;
4562                DeviceEventKind::MultimediaTransmitStarted {
4563                    call_id,
4564                    codec,
4565                    endpoint: ack.endpoint,
4566                    passthrough_party_id: ack.passthrough_party_id,
4567                }
4568            } else {
4569                let stop = take_multimedia_transmit_stop(state, call_id)
4570                    .expect("correlated video transmit leg remains present");
4571                send_message(stream, &stop, protocol).await?;
4572                DeviceEventKind::MultimediaTransmitFailed {
4573                    call_id,
4574                    codec,
4575                    status: ack.status,
4576                    endpoint: ack.endpoint,
4577                    passthrough_party_id: ack.passthrough_party_id,
4578                }
4579            };
4580            context
4581                .event_tx
4582                .send(Event::device(
4583                    state.device.id.clone(),
4584                    state.generation,
4585                    event,
4586                ))
4587                .await
4588                .map_err(|_| ServerError::Stopped)?;
4589        }
4590        message @ (ClientMessage::ExtensionDeviceCapabilities(_)
4591        | ClientMessage::CreateConferenceResponse(_)
4592        | ClientMessage::DeleteConferenceResponse { .. }
4593        | ClientMessage::ModifyConferenceResponse(_)
4594        | ClientMessage::AuditConferenceResponse(_)
4595        | ClientMessage::AddParticipantResponse(_)
4596        | ClientMessage::AuditParticipantResponse(_)) => {
4597            debug!(device_id = %state.device.id, message = ?message, "deferred SCCP application message");
4598            context
4599                .event_tx
4600                .send(Event::device(
4601                    state.device.id.clone(),
4602                    state.generation,
4603                    DeviceEventKind::UnhandledMessage { message },
4604                ))
4605                .await
4606                .map_err(|_| ServerError::Stopped)?;
4607        }
4608        ClientMessage::KnownOpaque(message) => {
4609            let message = ClientMessage::KnownOpaque(message);
4610            debug!(device_id = %state.device.id, message = ?message, "unhandled SCCP message");
4611            context
4612                .event_tx
4613                .send(Event::device(
4614                    state.device.id.clone(),
4615                    state.generation,
4616                    DeviceEventKind::UnhandledMessage { message },
4617                ))
4618                .await
4619                .map_err(|_| ServerError::Stopped)?;
4620        }
4621        ClientMessage::Unknown(message) => {
4622            let message = ClientMessage::Unknown(message);
4623            warn!(device_id = %state.device.id, message = ?message, "unknown SCCP message");
4624            context
4625                .event_tx
4626                .send(Event::device(
4627                    state.device.id.clone(),
4628                    state.generation,
4629                    DeviceEventKind::UnhandledMessage { message },
4630                ))
4631                .await
4632                .map_err(|_| ServerError::Stopped)?;
4633        }
4634        ClientMessage::Register(_) | ClientMessage::RegisterToken(_) => {
4635            warn!(device_id = %state.device.id, "ignoring registration message on registered session");
4636        }
4637    }
4638    Ok(())
4639}
4640
4641const fn is_local_audio_path(path: crate::message::values::MediaPathId) -> bool {
4642    matches!(
4643        path,
4644        crate::message::values::MediaPathId::Headset
4645            | crate::message::values::MediaPathId::Handset
4646            | crate::message::values::MediaPathId::Speaker
4647    )
4648}
4649
4650fn has_active_media_path(state: &SessionState) -> bool {
4651    state.media_path_states.iter().any(|(path, event)| {
4652        is_local_audio_path(*path) && *event == crate::message::values::MediaPathEvent::On
4653    })
4654}
4655
4656fn active_media_path_call(state: &SessionState) -> Option<CallId> {
4657    state.active_call_id.filter(|call_id| {
4658        state.calls_by_id.get(call_id).is_some_and(|call| {
4659            !matches!(
4660                call.state,
4661                CallState::OnHook
4662                    | CallState::RingIn
4663                    | CallState::CallWaiting
4664                    | CallState::Hold
4665                    | CallState::HoldYellow
4666                    | CallState::HoldRed
4667            )
4668        })
4669    })
4670}
4671
4672async fn complete_on_hook(
4673    stream: &mut dyn StationIo,
4674    state: &mut SessionState,
4675    context: &SessionContext,
4676    call: SessionCall,
4677    line_instance: u32,
4678) -> Result<(), ServerError> {
4679    state.pending_media_path_release = None;
4680    context
4681        .event_tx
4682        .send(Event::device(
4683            state.device.id.clone(),
4684            state.generation,
4685            DeviceEventKind::OnHook {
4686                call_id: call.call_id,
4687                line_instance: LineInstance::new(line_instance),
4688            },
4689        ))
4690        .await
4691        .map_err(|_| ServerError::Stopped)?;
4692    state.active_key_mode = KeyMode::OnHook;
4693    stop_call_multicast(stream, state, call.call_id, state.registration.protocol).await?;
4694    close_call_media_messages(stream, &call, state.registration.protocol).await?;
4695    close_call_messages(
4696        stream,
4697        &call,
4698        &state.device.soft_keys,
4699        state.registration.protocol,
4700        context.config.timezone_offset_minutes,
4701    )
4702    .await?;
4703    request_connection_statistics(stream, state, &call, context).await?;
4704    if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
4705        stored.state = CallState::OnHook;
4706        stored.media.receive.state = MediaChannelState::Closed;
4707        stored.media.receive.deadline = None;
4708        stored.media.transmit.state = MediaChannelState::Closed;
4709        stored.media.transmit.deadline = None;
4710        stored.media.coupled_transmit_endpoint = None;
4711        stored.video_receive.leg = None;
4712        stored.video_transmit.leg = None;
4713    }
4714    if state.active_call_id == Some(call.call_id) {
4715        state.active_call_id = None;
4716    }
4717    Ok(())
4718}
4719
4720fn button_template(device: &DeviceDefinition) -> Vec<ButtonTemplateEntry> {
4721    let mut buttons = Vec::with_capacity(56);
4722    let mut addon_buttons_remaining = None;
4723    for button in &device.buttons {
4724        if let ButtonDefinition::AddonModule(addon) = button {
4725            buttons.extend(std::iter::repeat_n(
4726                ButtonTemplateEntry {
4727                    instance: 0,
4728                    button_type: ButtonType::Unused,
4729                },
4730                addon_buttons_remaining.take().unwrap_or_default(),
4731            ));
4732            addon_buttons_remaining = addon.button_capacity();
4733            continue;
4734        }
4735        buttons.push(match button {
4736            ButtonDefinition::Line(appearance) => ButtonTemplateEntry {
4737                instance: appearance.instance,
4738                button_type: ButtonType::Line,
4739            },
4740            ButtonDefinition::SpeedDial(speed_dial) => ButtonTemplateEntry {
4741                instance: speed_dial.instance,
4742                button_type: ButtonType::SpeedDial,
4743            },
4744            ButtonDefinition::BlfSpeedDial(speed_dial) => ButtonTemplateEntry {
4745                instance: speed_dial.instance,
4746                button_type: ButtonType::BlfSpeedDial,
4747            },
4748            ButtonDefinition::Feature(feature) => ButtonTemplateEntry {
4749                instance: feature.instance,
4750                button_type: ButtonType::from(feature.feature.wire_value()),
4751            },
4752            ButtonDefinition::Service(service) => ButtonTemplateEntry {
4753                instance: service.instance,
4754                button_type: ButtonType::ServiceUrl,
4755            },
4756            ButtonDefinition::Unused => ButtonTemplateEntry {
4757                instance: 0,
4758                button_type: ButtonType::Unused,
4759            },
4760            ButtonDefinition::AddonModule(_) => unreachable!("addon marker handled above"),
4761        });
4762        if let Some(remaining) = &mut addon_buttons_remaining {
4763            *remaining = remaining.saturating_sub(1);
4764        }
4765    }
4766    buttons.extend(std::iter::repeat_n(
4767        ButtonTemplateEntry {
4768            instance: 0,
4769            button_type: ButtonType::Unused,
4770        },
4771        addon_buttons_remaining.unwrap_or_default(),
4772    ));
4773    buttons
4774}
4775
4776fn line_status(device: &DeviceDefinition, instance: u32) -> Option<ServerMessage> {
4777    device
4778        .line(instance)
4779        .map(|appearance| ServerMessage::LineStatus {
4780            instance: appearance.instance,
4781            number: appearance.line.number.clone(),
4782            display_name: appearance.display_label().to_owned(),
4783        })
4784}
4785
4786fn mobility_device_candidate(
4787    current: &DeviceDefinition,
4788    current_appearances: &HashMap<u32, LineAppearance>,
4789    next_appearances: &HashMap<u32, LineAppearance>,
4790) -> Result<DeviceDefinition, CodecError> {
4791    let mut candidate = current.clone();
4792    candidate.buttons.retain(|button| {
4793        !matches!(
4794            button,
4795            ButtonDefinition::Line(line)
4796                if current_appearances.values().any(|appearance| appearance == line)
4797        )
4798    });
4799    let mut index = 0;
4800    while index < candidate.buttons.len() {
4801        let mobility_instance = match &candidate.buttons[index] {
4802            ButtonDefinition::Feature(feature) if feature.feature == ButtonType::Mobility => {
4803                Some(feature.instance)
4804            }
4805            _ => None,
4806        };
4807        if let Some(appearance) = mobility_instance
4808            .and_then(|instance| next_appearances.get(&instance))
4809            .cloned()
4810        {
4811            candidate
4812                .buttons
4813                .insert(index + 1, ButtonDefinition::Line(appearance));
4814            index += 2;
4815        } else {
4816            index += 1;
4817        }
4818    }
4819    candidate.validate()?;
4820    Ok(candidate)
4821}
4822
4823fn speed_dial_status(device: &DeviceDefinition, instance: u32) -> ServerMessage {
4824    let speed_dial = device.buttons.iter().find_map(|button| match button {
4825        ButtonDefinition::SpeedDial(speed_dial) if speed_dial.instance == instance => {
4826            Some((&speed_dial.number, &speed_dial.display_name))
4827        }
4828        ButtonDefinition::BlfSpeedDial(speed_dial) if speed_dial.instance == instance => {
4829            Some((&speed_dial.number, &speed_dial.display_name))
4830        }
4831        _ => None,
4832    });
4833    ServerMessage::SpeedDialStatus {
4834        instance,
4835        number: speed_dial.map_or_else(String::new, |(number, _)| number.clone()),
4836        display_name: speed_dial.map_or_else(String::new, |(_, display_name)| display_name.clone()),
4837    }
4838}
4839
4840fn feature_status(
4841    device: &DeviceDefinition,
4842    instance: u32,
4843    capabilities: u32,
4844) -> Option<ServerMessage> {
4845    device.buttons.iter().find_map(|button| match button {
4846        ButtonDefinition::BlfSpeedDial(speed_dial)
4847            if capabilities == 1 && speed_dial.instance == instance =>
4848        {
4849            Some(ServerMessage::FeatureStatus {
4850                instance,
4851                button_type: ButtonType::BlfSpeedDial,
4852                label: speed_dial.display_name.clone(),
4853                state: BusyLampFieldState::UnknownState.wire_value(),
4854            })
4855        }
4856        ButtonDefinition::Feature(feature) if feature.instance == instance => {
4857            Some(ServerMessage::FeatureStatus {
4858                instance,
4859                button_type: ButtonType::from(feature.feature.wire_value()),
4860                label: feature.label.clone(),
4861                state: 0,
4862            })
4863        }
4864        _ => None,
4865    })
4866}
4867
4868fn feature_state_messages(
4869    device: &DeviceDefinition,
4870    instance: u32,
4871    enabled: bool,
4872) -> Option<[ServerMessage; 2]> {
4873    let feature = device.buttons.iter().find_map(|button| match button {
4874        ButtonDefinition::Feature(feature) if feature.instance == instance => Some(feature),
4875        _ => None,
4876    })?;
4877    Some([
4878        ServerMessage::FeatureStatus {
4879            instance,
4880            button_type: ButtonType::from(feature.feature.wire_value()),
4881            label: feature.label.clone(),
4882            state: u32::from(enabled),
4883        },
4884        ServerMessage::SetLamp {
4885            stimulus: feature.feature,
4886            instance,
4887            mode: if enabled { LampMode::On } else { LampMode::Off },
4888        },
4889    ])
4890}
4891
4892fn do_not_disturb_state_messages(
4893    device: &DeviceDefinition,
4894    instance: u32,
4895    mode: DoNotDisturbMode,
4896    button_mode: DoNotDisturbButtonMode,
4897    protocol: ProtocolVersion,
4898) -> Option<[ServerMessage; 2]> {
4899    let feature = device.buttons.iter().find_map(|button| match button {
4900        ButtonDefinition::Feature(feature)
4901            if feature.instance == instance && feature.feature == ButtonType::DoNotDisturb =>
4902        {
4903            Some(feature)
4904        }
4905        _ => None,
4906    })?;
4907    let exact_enabled = match button_mode {
4908        DoNotDisturbButtonMode::Cycle => mode != DoNotDisturbMode::Off,
4909        DoNotDisturbButtonMode::Silent => mode == DoNotDisturbMode::Silent,
4910        DoNotDisturbButtonMode::Reject => mode == DoNotDisturbMode::Reject,
4911    };
4912    let multi_state =
4913        button_mode == DoNotDisturbButtonMode::Cycle && protocol > ProtocolVersion::V15;
4914    let (button_type, state) = if multi_state {
4915        (
4916            ButtonType::MultiblinkFeature,
4917            match mode {
4918                DoNotDisturbMode::Off => 0x010000,
4919                DoNotDisturbMode::Reject => 0x020202,
4920                DoNotDisturbMode::Silent => 0x030302,
4921            },
4922        )
4923    } else {
4924        (ButtonType::DoNotDisturb, u32::from(exact_enabled))
4925    };
4926    let lamp = match (exact_enabled, mode) {
4927        (false, _) | (_, DoNotDisturbMode::Off) => LampMode::Off,
4928        (true, DoNotDisturbMode::Silent) => LampMode::Blink,
4929        (true, DoNotDisturbMode::Reject) => LampMode::On,
4930    };
4931    Some([
4932        ServerMessage::FeatureStatus {
4933            instance,
4934            button_type,
4935            label: feature.label.clone(),
4936            state,
4937        },
4938        ServerMessage::SetLamp {
4939            stimulus: feature.feature,
4940            instance,
4941            mode: lamp,
4942        },
4943    ])
4944}
4945
4946fn blf_status_messages(
4947    instance: u32,
4948    number: &str,
4949    label: &str,
4950    state: BlfState,
4951    caller: Option<&BlfCallerInfo>,
4952) -> [ServerMessage; 3] {
4953    let caller = caller.map(BlfCallerInfo::display).unwrap_or_default();
4954    let dynamic_label = if caller.is_empty() {
4955        label.to_owned()
4956    } else {
4957        format!("{label}: {caller}")
4958    };
4959    let icon = match state {
4960        BlfState::Idle => BusyLampFieldState::Idle,
4961        BlfState::Ringing => BusyLampFieldState::Alerting,
4962        BlfState::Busy | BlfState::Held => BusyLampFieldState::InUse,
4963        BlfState::Unavailable | BlfState::Unknown => BusyLampFieldState::UnknownState,
4964    };
4965    let lamp = match state {
4966        BlfState::Idle => LampMode::Off,
4967        BlfState::Ringing => LampMode::Blink,
4968        BlfState::Busy => LampMode::On,
4969        BlfState::Held => LampMode::Hold,
4970        BlfState::Unavailable => LampMode::Flash,
4971        BlfState::Unknown => LampMode::Wink,
4972    };
4973    [
4974        ServerMessage::SpeedDialStatus {
4975            instance,
4976            number: truncate_utf8(number, 23),
4977            display_name: truncate_utf8(&dynamic_label, 39),
4978        },
4979        ServerMessage::FeatureStatus {
4980            instance,
4981            button_type: ButtonType::BlfSpeedDial,
4982            label: truncate_utf8(&dynamic_label, 39),
4983            state: icon.wire_value(),
4984        },
4985        ServerMessage::SetLamp {
4986            stimulus: ButtonType::BlfSpeedDial,
4987            instance,
4988            mode: lamp,
4989        },
4990    ]
4991}
4992
4993fn hinted_ringing_notification(
4994    device: &DeviceDefinition,
4995    label: &str,
4996    caller: Option<&BlfCallerInfo>,
4997    state: BlfState,
4998) -> Option<HandsetStatusMessage> {
4999    if !device.ui.hinted_ringing_notification || state != BlfState::Ringing {
5000        return None;
5001    }
5002    let caller = caller.map(BlfCallerInfo::display).unwrap_or_default();
5003    let text = if caller.is_empty() {
5004        format!("{label} is ringing")
5005    } else {
5006        format!("{label} is ringing: {caller}")
5007    };
5008    Some(HandsetStatusMessage::Display {
5009        text: truncate_utf8(&text, 79),
5010        timeout_seconds: 5,
5011        priority: None,
5012    })
5013}
5014
5015fn truncate_utf8(value: &str, maximum_bytes: usize) -> String {
5016    if value.len() <= maximum_bytes {
5017        return value.to_owned();
5018    }
5019    let end = value
5020        .char_indices()
5021        .map(|(index, _)| index)
5022        .take_while(|index| *index <= maximum_bytes)
5023        .last()
5024        .unwrap_or(0);
5025    value[..end].to_owned()
5026}
5027
5028fn service_url_status(device: &DeviceDefinition, index: u32) -> Option<ServerMessage> {
5029    device.buttons.iter().find_map(|button| match button {
5030        ButtonDefinition::Service(service) if service.instance == index => {
5031            Some(ServerMessage::ServiceUrlStatus {
5032                index,
5033                url: service.url.clone(),
5034                label: service.label.clone(),
5035                extension_text: String::new(),
5036            })
5037        }
5038        _ => None,
5039    })
5040}
5041
5042const fn key_mode_for_call_state(state: CallState) -> KeyMode {
5043    match state {
5044        CallState::Connected => KeyMode::Connected,
5045        CallState::Hold | CallState::HoldYellow | CallState::HoldRed => KeyMode::OnHold,
5046        CallState::RingIn | CallState::CallWaiting => KeyMode::RingIn,
5047        CallState::OffHook
5048        | CallState::Busy
5049        | CallState::Congestion
5050        | CallState::InvalidNumber
5051        | CallState::IntercomOneWay => KeyMode::OffHook,
5052        CallState::Transfer => KeyMode::ConnectedTransfer,
5053        CallState::RingOut | CallState::Proceed => KeyMode::RingOut,
5054        CallState::RemoteMultiline => KeyMode::OnHookStealable,
5055        CallState::OnHook | CallState::Park | CallState::Unknown(_) => KeyMode::OnHook,
5056    }
5057}
5058
5059fn transfer_key_mode(call: &SessionCall, state: CallState) -> KeyMode {
5060    if matches!(
5061        call.transfer_role,
5062        Some(SessionTransferRole::Consultation { .. })
5063    ) && matches!(state, CallState::RingOut | CallState::Connected)
5064    {
5065        KeyMode::ConnectedTransfer
5066    } else {
5067        key_mode_for_call_state(state)
5068    }
5069}
5070
5071fn stimulus_soft_key(stimulus: Stimulus) -> Option<SoftKey> {
5072    Some(match stimulus {
5073        Stimulus::LastNumberRedial => SoftKey::Redial,
5074        Stimulus::Hold => SoftKey::Hold,
5075        Stimulus::Transfer => SoftKey::Transfer,
5076        Stimulus::ForwardAll => SoftKey::ForwardAll,
5077        Stimulus::ForwardBusy => SoftKey::ForwardBusy,
5078        Stimulus::ForwardNoAnswer => SoftKey::ForwardNoAnswer,
5079        Stimulus::Conference => SoftKey::Conference,
5080        Stimulus::MeetMeConference => SoftKey::MeetMe,
5081        Stimulus::CallPark => SoftKey::Park,
5082        Stimulus::CallPickup => SoftKey::Pickup,
5083        Stimulus::GroupCallPickup => SoftKey::GroupPickup,
5084        Stimulus::DoNotDisturb => SoftKey::DoNotDisturb,
5085        Stimulus::ConferenceList => SoftKey::ConferenceList,
5086        Stimulus::NewCall => SoftKey::NewCall,
5087        Stimulus::EndCall => SoftKey::EndCall,
5088        _ => return None,
5089    })
5090}
5091
5092fn parking_menu_xml(
5093    instance: u32,
5094    transaction_id: u32,
5095    lot: &str,
5096    calls: &[ParkingMenuEntry],
5097) -> Result<String, ServerError> {
5098    if calls.len() > PARKING_MENU_MAX_ITEMS {
5099        return Err(PhoneXmlError::LimitExceeded {
5100            kind: "parking menu",
5101            actual: calls.len(),
5102            maximum: PARKING_MENU_MAX_ITEMS,
5103        }
5104        .into());
5105    }
5106    let items = calls
5107        .iter()
5108        .map(|call| {
5109            let party = if !call.caller_name.trim().is_empty() {
5110                call.caller_name.trim()
5111            } else if !call.caller_number.trim().is_empty() {
5112                call.caller_number.trim()
5113            } else {
5114                "Unknown caller"
5115            };
5116            let connected = if !call.connected_name.trim().is_empty() {
5117                format!(" to {}", call.connected_name.trim())
5118            } else if !call.connected_number.trim().is_empty() {
5119                format!(" to {}", call.connected_number.trim())
5120            } else {
5121                String::new()
5122            };
5123            CiscoIpPhoneMenuItem {
5124                name: Some(format!("{}: {}{}", call.slot, party, connected)),
5125                url: Some(format!(
5126                    "UserCallData:{}:{instance}:0:{transaction_id}:retrieve/{}/{}",
5127                    PARKING_APPLICATION_ID,
5128                    utf8_percent_encode(lot, NON_ALPHANUMERIC),
5129                    call.slot,
5130                )),
5131            }
5132        })
5133        .collect();
5134    CiscoIpPhoneMenu::new(
5135        format!("Parked calls - {lot}"),
5136        if calls.is_empty() {
5137            "No parked calls"
5138        } else {
5139            "Select a call"
5140        },
5141        items,
5142    )?
5143    .to_xml_with_limit(2_000)
5144    .map_err(ServerError::from)
5145}
5146
5147fn text_service_messages(
5148    line_instance: LineInstance,
5149    call_reference: CallReference,
5150    transaction_id: TransactionId,
5151    priority: PhoneServicePriority,
5152    document: &CiscoIpPhoneText,
5153    protocol: ProtocolVersion,
5154) -> Result<Vec<ServerMessage>, ServerError> {
5155    if protocol <= ProtocolVersion::V17
5156        && document
5157            .text
5158            .as_deref()
5159            .is_some_and(|text| text.chars().count() > PHONE_TEXT_LEGACY_MAX_CHARS)
5160    {
5161        return Err(PhoneXmlError::InvalidField {
5162            field: "legacy phone text body",
5163            expected: "at most 1024 characters",
5164        }
5165        .into());
5166    }
5167    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5168        2_000
5169    } else {
5170        crate::phone::xml::PHONE_TEXT_MAX_BYTES
5171    };
5172    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5173    Ok(phone_service_document_messages(
5174        line_instance,
5175        call_reference,
5176        ApplicationId::new(PHONE_TEXT_APPLICATION_ID),
5177        transaction_id,
5178        priority,
5179        &xml,
5180    ))
5181}
5182
5183fn input_service_messages(
5184    line_instance: LineInstance,
5185    call_reference: CallReference,
5186    application_id: ApplicationId,
5187    transaction_id: TransactionId,
5188    priority: PhoneServicePriority,
5189    document: &CiscoIpPhoneInput,
5190    protocol: ProtocolVersion,
5191) -> Result<Vec<ServerMessage>, ServerError> {
5192    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5193        2_000
5194    } else {
5195        PHONE_INPUT_MAX_BYTES
5196    };
5197    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5198    Ok(phone_service_document_messages(
5199        line_instance,
5200        call_reference,
5201        application_id,
5202        transaction_id,
5203        priority,
5204        &xml,
5205    ))
5206}
5207
5208fn execute_phone_action_messages(
5209    line_instance: LineInstance,
5210    call_reference: CallReference,
5211    application_id: ApplicationId,
5212    transaction_id: TransactionId,
5213    priority: PhoneServicePriority,
5214    document: &CiscoIpPhoneExecute,
5215    protocol: ProtocolVersion,
5216) -> Result<Vec<ServerMessage>, ServerError> {
5217    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5218        2_000
5219    } else {
5220        PHONE_EXECUTE_MAX_BYTES
5221    };
5222    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5223    Ok(phone_service_document_messages(
5224        line_instance,
5225        call_reference,
5226        application_id,
5227        transaction_id,
5228        priority,
5229        &xml,
5230    ))
5231}
5232
5233fn image_service_messages(
5234    line_instance: LineInstance,
5235    call_reference: CallReference,
5236    application_id: ApplicationId,
5237    transaction_id: TransactionId,
5238    priority: PhoneServicePriority,
5239    document: &PhoneImageDocument,
5240    protocol: ProtocolVersion,
5241) -> Result<Vec<ServerMessage>, ServerError> {
5242    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5243        2_000
5244    } else {
5245        PHONE_IMAGE_MAX_BYTES
5246    };
5247    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5248    Ok(phone_service_document_messages(
5249        line_instance,
5250        call_reference,
5251        application_id,
5252        transaction_id,
5253        priority,
5254        &xml,
5255    ))
5256}
5257
5258fn status_service_messages(
5259    line_instance: LineInstance,
5260    call_reference: CallReference,
5261    application_id: ApplicationId,
5262    transaction_id: TransactionId,
5263    priority: PhoneServicePriority,
5264    document: &PhoneStatusDocument,
5265    protocol: ProtocolVersion,
5266) -> Result<Vec<ServerMessage>, ServerError> {
5267    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5268        2_000
5269    } else {
5270        PHONE_STATUS_MAX_BYTES
5271    };
5272    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5273    Ok(phone_service_document_messages(
5274        line_instance,
5275        call_reference,
5276        application_id,
5277        transaction_id,
5278        priority,
5279        &xml,
5280    ))
5281}
5282
5283fn background_control_message(
5284    transaction_id: TransactionId,
5285    document: &PhoneBackgroundControlDocument,
5286) -> Result<ServerMessage, ServerError> {
5287    let xml = document.to_xml()?.into_bytes();
5288    let [message] = phone_service_document_messages(
5289        LineInstance::new(0),
5290        CallReference::new(0),
5291        ApplicationId::new(PHONE_BACKGROUND_APPLICATION_ID),
5292        transaction_id,
5293        PhoneServicePriority::LOW,
5294        &xml,
5295    )
5296    .try_into()
5297    .map_err(|_| PhoneXmlError::InvalidField {
5298        field: "background control document",
5299        expected: "a single application-data frame",
5300    })?;
5301    Ok(message)
5302}
5303
5304fn ringtone_control_message(
5305    transaction_id: TransactionId,
5306    document: &CiscoIpPhoneSetRingTone,
5307) -> Result<ServerMessage, ServerError> {
5308    let xml = document.to_xml()?.into_bytes();
5309    let [message] = phone_service_document_messages(
5310        LineInstance::new(0),
5311        CallReference::new(0),
5312        ApplicationId::new(PHONE_RINGTONE_APPLICATION_ID),
5313        transaction_id,
5314        PhoneServicePriority::LOW,
5315        &xml,
5316    )
5317    .try_into()
5318    .map_err(|_| PhoneXmlError::InvalidField {
5319        field: "ringtone control document",
5320        expected: "a single application-data frame",
5321    })?;
5322    Ok(message)
5323}
5324
5325#[cfg(test)]
5326fn start_announcement_message(
5327    conference_id: ConferenceId,
5328    announcements: Vec<AnnouncementEntry>,
5329    end_of_ack: bool,
5330    participant_ids: Vec<ParticipantId>,
5331    hearing_participant_mask: u32,
5332    play_mode: u32,
5333) -> ServerMessage {
5334    ServerMessage::StartAnnouncement {
5335        announcements,
5336        end_of_ack: u32::from(end_of_ack),
5337        conference_id: conference_id.get(),
5338        matrix_conference_party_ids: participant_ids
5339            .into_iter()
5340            .map(ParticipantId::get)
5341            .collect(),
5342        hearing_conference_party_mask: hearing_participant_mask,
5343        play_mode,
5344    }
5345}
5346
5347fn phone_service_document_messages(
5348    line_instance: LineInstance,
5349    call_reference: CallReference,
5350    application_id: ApplicationId,
5351    transaction_id: TransactionId,
5352    priority: PhoneServicePriority,
5353    xml: &[u8],
5354) -> Vec<ServerMessage> {
5355    let chunks = xml.chunks(2_000);
5356    let chunk_count = chunks.len();
5357    chunks
5358        .enumerate()
5359        .map(|(index, data)| {
5360            let sequence_flag = if chunk_count == 1 || index + 1 == chunk_count {
5361                2
5362            } else if index == 0 {
5363                0
5364            } else {
5365                1
5366            };
5367            ServerMessage::UserToDeviceDataV1(UserDataV1Message {
5368                application_id: application_id.get(),
5369                line_instance: line_instance.get(),
5370                call_reference: call_reference.get(),
5371                transaction_id: transaction_id.get(),
5372                sequence_flag,
5373                display_priority: priority.wire(),
5374                conference_id: call_reference.get(),
5375                application_instance_id: application_id.get(),
5376                routing: 1,
5377                data: data.to_vec(),
5378            })
5379        })
5380        .collect()
5381}
5382
5383async fn handle_phone_service_message(
5384    state: &mut SessionState,
5385    context: &SessionContext,
5386    message_id: u32,
5387    kind: PhoneServiceMessageKind,
5388    routing: PhoneServiceRouting,
5389    extended: Option<PhoneServiceExtendedRouting>,
5390    data: &[u8],
5391) -> Result<(), ServerError> {
5392    let payload = match parse_phone_service_payload(data, kind) {
5393        Ok(payload) => payload,
5394        Err(error) => {
5395            warn!(
5396                device_id = %state.device.id,
5397                message_id = format_args!("0x{message_id:04x}"),
5398                %error,
5399                "ignoring malformed phone-service response"
5400            );
5401            context
5402                .event_tx
5403                .send(Event::ProtocolWarning {
5404                    peer: context.peer,
5405                    device_id: Some(state.device.id.clone()),
5406                    message_id,
5407                    error: error.to_string(),
5408                })
5409                .await
5410                .map_err(|_| ServerError::Stopped)?;
5411            return Ok(());
5412        }
5413    };
5414    let response = PhoneServiceEvent {
5415        kind,
5416        routing,
5417        extended,
5418        payload,
5419    };
5420
5421    if let Some((lot, slot)) = parking_menu_selection(state.pending_parking_menu, &response) {
5422        state.pending_parking_menu = None;
5423        context
5424            .event_tx
5425            .send(Event::device(
5426                state.device.id.clone(),
5427                state.generation,
5428                DeviceEventKind::ParkingMenuSelection { lot, slot },
5429            ))
5430            .await
5431            .map_err(|_| ServerError::Stopped)?;
5432    }
5433    if response.kind == PhoneServiceMessageKind::Data
5434        && response.routing.application_id.get() == ConferenceListAction::APPLICATION_ID
5435        && let PhoneServicePayload::Submission(submission) = &response.payload
5436        && let Some(action) = ConferenceListAction::from_route(&submission.route)
5437    {
5438        context
5439            .event_tx
5440            .send(Event::device(
5441                state.device.id.clone(),
5442                state.generation,
5443                DeviceEventKind::ConferenceListAction { action },
5444            ))
5445            .await
5446            .map_err(|_| ServerError::Stopped)?;
5447    }
5448    context
5449        .event_tx
5450        .send(Event::device(
5451            state.device.id.clone(),
5452            state.generation,
5453            DeviceEventKind::PhoneServiceResponse { response },
5454        ))
5455        .await
5456        .map_err(|_| ServerError::Stopped)
5457}
5458
5459fn parking_menu_selection(
5460    pending: Option<PendingParkingMenu>,
5461    response: &PhoneServiceEvent,
5462) -> Option<(String, u32)> {
5463    let pending = pending?;
5464    if response.kind != PhoneServiceMessageKind::Data
5465        || response.routing.application_id.get() != PARKING_APPLICATION_ID
5466        || response.routing.line_instance.get() != pending.instance
5467        || response.routing.call_reference.get() != 0
5468        || response.routing.transaction_id.get() != pending.transaction_id
5469        || response
5470            .extended
5471            .is_some_and(|extended| extended.application_instance_id != pending.instance)
5472    {
5473        return None;
5474    }
5475    let PhoneServicePayload::Submission(submission) = &response.payload else {
5476        return None;
5477    };
5478    let [action, lot, slot] = submission.route.as_slice() else {
5479        return None;
5480    };
5481    if action != "retrieve" || lot.is_empty() || !submission.values.is_empty() {
5482        return None;
5483    }
5484    let slot = slot.parse().ok()?;
5485    (slot != 0).then(|| (lot.clone(), slot))
5486}
5487
5488fn digit_character(digit: Digit) -> Option<char> {
5489    match digit {
5490        Digit::Number(number @ 0..=9) => Some(char::from(b'0' + number)),
5491        Digit::Star => Some('*'),
5492        Digit::Pound => Some('#'),
5493        Digit::A => Some('A'),
5494        Digit::B => Some('B'),
5495        Digit::C => Some('C'),
5496        Digit::D => Some('D'),
5497        Digit::Number(_) | Digit::Unknown(_) => None,
5498    }
5499}
5500
5501fn normalized_last_number(number: &str, config: &ServerConfig) -> Option<String> {
5502    let number = number.trim();
5503    let number = if config.record_dial_terminator {
5504        number
5505    } else {
5506        digit_character(config.dial_terminator)
5507            .map_or(number, |terminator| number.trim_end_matches(terminator))
5508    };
5509    (!number.is_empty()).then(|| number.to_owned())
5510}
5511
5512fn remember_last_number(
5513    state: &mut SessionState,
5514    line_instance: u32,
5515    number: &str,
5516    config: &ServerConfig,
5517) {
5518    if let Some(number) = normalized_last_number(number, config) {
5519        state.last_number_by_line.insert(line_instance, number);
5520    }
5521}
5522
5523async fn begin_redial(
5524    stream: &mut dyn StationIo,
5525    state: &mut SessionState,
5526    context: &SessionContext,
5527    line_instance: u32,
5528    existing_call_id: Option<CallId>,
5529) -> Result<(), ServerError> {
5530    if state.device.ui.placed_calls_redial_menu
5531        && placed_calls_menu_supported(state.registration.protocol)
5532    {
5533        let document = CiscoIpPhoneExecute::new(vec![CiscoIpPhoneExecuteItem::new(
5534            "Application:PlacedCalls",
5535        )?])?;
5536        for message in execute_phone_action_messages(
5537            LineInstance::new(line_instance),
5538            CallReference::new(0),
5539            ApplicationId::new(0),
5540            TransactionId::new(0),
5541            PhoneServicePriority::NORMAL,
5542            &document,
5543            state.registration.protocol,
5544        )? {
5545            send_message(stream, &message, state.registration.protocol).await?;
5546        }
5547        return Ok(());
5548    }
5549
5550    let Some(number) = state.last_number_by_line.get(&line_instance).cloned() else {
5551        return Ok(());
5552    };
5553    let existing = existing_call_id.and_then(|call_id| {
5554        state
5555            .calls_by_id
5556            .get(&call_id)
5557            .filter(|call| call.line_instance == line_instance && call.state != CallState::OnHook)
5558            .cloned()
5559    });
5560    let (call, created) = existing.map_or_else(
5561        || {
5562            (
5563                ensure_phone_call(state, 0, line_instance, &context.next_call_id),
5564                true,
5565            )
5566        },
5567        |call| (call, false),
5568    );
5569
5570    if created {
5571        state.active_key_mode = KeyMode::OffHook;
5572        begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
5573        context
5574            .event_tx
5575            .send(Event::device(
5576                state.device.id.clone(),
5577                state.generation,
5578                DeviceEventKind::OffHook {
5579                    call_id: call.call_id,
5580                    line_instance: LineInstance::new(line_instance),
5581                },
5582            ))
5583            .await
5584            .map_err(|_| ServerError::Stopped)?;
5585    }
5586    if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
5587        stored.dialed_number.clone_from(&number);
5588    }
5589    send_message(
5590        stream,
5591        &ServerMessage::DialedNumber {
5592            number: number.clone(),
5593            line_instance,
5594            call_reference: call.wire_reference,
5595        },
5596        state.registration.protocol,
5597    )
5598    .await?;
5599    context
5600        .event_tx
5601        .send(Event::device(
5602            state.device.id.clone(),
5603            state.generation,
5604            DeviceEventKind::EnblocCall {
5605                call_id: call.call_id,
5606                line_instance: LineInstance::new(line_instance),
5607                number,
5608            },
5609        ))
5610        .await
5611        .map_err(|_| ServerError::Stopped)?;
5612    Ok(())
5613}
5614
5615fn placed_calls_menu_supported(protocol: ProtocolVersion) -> bool {
5616    protocol >= ProtocolVersion::V8
5617}
5618
5619async fn handle_session_command(
5620    stream: &mut dyn StationIo,
5621    state: &mut SessionState,
5622    command: SessionCommand,
5623    context: &SessionContext,
5624) -> Result<bool, ServerError> {
5625    let config = &context.config;
5626    let protocol = state.registration.protocol;
5627    match command {
5628        SessionCommand::Confirmed { .. } => {
5629            unreachable!("confirmed commands are unwrapped by the session loop")
5630        }
5631        SessionCommand::Disconnect => {
5632            drain_session_media(stream, state).await;
5633            return Ok(true);
5634        }
5635        SessionCommand::OfferIncoming {
5636            line_instance,
5637            call_id,
5638            info,
5639            ringer,
5640        } => {
5641            if state.cancelled_calls.remove(&call_id) {
5642                debug!(device_id = %state.device.id, ?call_id, "discarding incoming call cancelled before it was offered");
5643                return Ok(false);
5644            }
5645            let line_instance = normalize_line(state, line_instance.get());
5646            let statistics_directory_number = statistics_directory_for_call_info(&info).to_owned();
5647            let caller = match (
5648                info.calling_name.trim().is_empty(),
5649                info.calling_number.trim().is_empty(),
5650            ) {
5651                (false, false) => format!("{} ({})", info.calling_name, info.calling_number),
5652                (false, true) => info.calling_name.clone(),
5653                (true, false) => info.calling_number.clone(),
5654                (true, true) => "Unknown number".to_owned(),
5655            };
5656            let incoming_state = if state.calls_by_id.values().any(|call| {
5657                matches!(
5658                    call.state,
5659                    CallState::Connected
5660                        | CallState::Hold
5661                        | CallState::HoldYellow
5662                        | CallState::HoldRed
5663                )
5664            }) {
5665                CallState::CallWaiting
5666            } else {
5667                CallState::RingIn
5668            };
5669            let call = insert_call(state, call_id, line_instance, Codec::Pcmu, incoming_state);
5670            if incoming_state == CallState::RingIn && state.active_call_id.is_none() {
5671                state.active_call_id = Some(call.call_id);
5672            }
5673            if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
5674                stored.statistics_directory_number = statistics_directory_number;
5675            }
5676            send_message(
5677                stream,
5678                &ServerMessage::ClearPrompt {
5679                    line_instance,
5680                    call_reference: call.wire_reference,
5681                },
5682                protocol,
5683            )
5684            .await?;
5685            send_message(
5686                stream,
5687                &ServerMessage::CallState {
5688                    state: incoming_state,
5689                    line_instance,
5690                    call_reference: call.wire_reference,
5691                },
5692                protocol,
5693            )
5694            .await?;
5695            send_station_ui_message(
5696                stream,
5697                state,
5698                &ServerMessage::CallInfo {
5699                    info: *info,
5700                    line_instance,
5701                    call_reference: call.wire_reference,
5702                },
5703            )
5704            .await?;
5705            send_message(
5706                stream,
5707                &ServerMessage::SetLamp {
5708                    stimulus: ButtonType::Line,
5709                    instance: line_instance,
5710                    mode: LampMode::Blink,
5711                },
5712                protocol,
5713            )
5714            .await?;
5715            if let Some(ringer) = incoming_ringer(ringer, incoming_state) {
5716                send_message(
5717                    stream,
5718                    &ServerMessage::SetRinger {
5719                        mode: ringer.mode,
5720                        duration: ringer.duration,
5721                        line_instance,
5722                        call_reference: call.wire_reference,
5723                    },
5724                    protocol,
5725                )
5726                .await?;
5727            }
5728            state.active_key_mode = KeyMode::RingIn;
5729            send_message(
5730                stream,
5731                &ServerMessage::SelectSoftKeys {
5732                    line_instance,
5733                    call_reference: call.wire_reference,
5734                    set: KeyMode::RingIn,
5735                    valid_mask: state.device.soft_keys.valid_mask(KeyMode::RingIn),
5736                },
5737                protocol,
5738            )
5739            .await?;
5740            send_station_ui_message(
5741                stream,
5742                state,
5743                &ServerMessage::DisplayPrompt {
5744                    timeout_seconds: 0,
5745                    text: format!("From {caller}"),
5746                    line_instance,
5747                    call_reference: call.wire_reference,
5748                },
5749            )
5750            .await?;
5751        }
5752        SessionCommand::Public(command) => {
5753            let command = *command;
5754            if let Some(call_id) = command_call_id(&command)
5755                && !matches!(
5756                    &command.action,
5757                    CommandAction::BeginCall { .. } | CommandAction::CloseCall { .. }
5758                )
5759                && !state.calls_by_id.contains_key(&call_id)
5760            {
5761                debug!(device_id = %state.device.id, ?call_id, command = ?command, "ignoring stale SCCP call command");
5762                return Ok(false);
5763            }
5764            let action = command.action;
5765            match action {
5766                CommandAction::DisconnectDevice { .. } => {
5767                    drain_session_media(stream, state).await;
5768                    return Ok(true);
5769                }
5770                CommandAction::BeginCall {
5771                    line_instance,
5772                    call_id,
5773                    codec,
5774                } => {
5775                    if state.calls_by_id.contains_key(&call_id) {
5776                        return Ok(false);
5777                    }
5778                    let line_instance = normalize_line(state, line_instance.get());
5779                    let call =
5780                        insert_call(state, call_id, line_instance, codec, CallState::OffHook);
5781                    state.active_call_id = Some(call.call_id);
5782                    state.active_key_mode = KeyMode::OffHook;
5783                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
5784                        .await?;
5785                }
5786                CommandAction::BeginTransfer {
5787                    source_call_id,
5788                    consultation_line_instance,
5789                    consultation_call_id,
5790                    codec,
5791                } => {
5792                    let consultation_line_instance = consultation_line_instance.get();
5793                    if state.calls_by_id.contains_key(&consultation_call_id) {
5794                        return Ok(false);
5795                    }
5796                    let source = require_call_mut(state, source_call_id)?;
5797                    if !matches!(
5798                        source.state,
5799                        CallState::Hold | CallState::HoldYellow | CallState::HoldRed
5800                    ) {
5801                        return Err(ServerError::InvalidCallTransaction {
5802                            call_id: source_call_id,
5803                            operation: "begin transfer",
5804                            state: source.state,
5805                        });
5806                    }
5807                    source.state = CallState::Transfer;
5808                    source.transfer_role = Some(SessionTransferRole::Source {
5809                        consultation_call_id,
5810                    });
5811                    let source = source.clone();
5812                    send_message(
5813                        stream,
5814                        &ServerMessage::CallState {
5815                            state: CallState::Transfer,
5816                            line_instance: source.line_instance,
5817                            call_reference: source.wire_reference,
5818                        },
5819                        protocol,
5820                    )
5821                    .await?;
5822                    send_station_ui_message(
5823                        stream,
5824                        state,
5825                        &ServerMessage::DisplayPrompt {
5826                            timeout_seconds: 0,
5827                            text: "Call Transfer".into(),
5828                            line_instance: source.line_instance,
5829                            call_reference: source.wire_reference,
5830                        },
5831                    )
5832                    .await?;
5833
5834                    let line_instance = normalize_line(state, consultation_line_instance);
5835                    let mut consultation = insert_call(
5836                        state,
5837                        consultation_call_id,
5838                        line_instance,
5839                        codec,
5840                        CallState::OffHook,
5841                    );
5842                    consultation.transfer_role =
5843                        Some(SessionTransferRole::Consultation { source_call_id });
5844                    state
5845                        .calls_by_id
5846                        .insert(consultation_call_id, consultation.clone());
5847                    state.active_call_id = Some(consultation.call_id);
5848                    state.active_key_mode = KeyMode::OffHookFeature;
5849                    begin_phone_call_ui_with_key_mode(
5850                        stream,
5851                        &consultation,
5852                        &state.device,
5853                        KeyMode::OffHookFeature,
5854                        state.station_context(),
5855                    )
5856                    .await?;
5857                    send_message(
5858                        stream,
5859                        &ServerMessage::SetLamp {
5860                            stimulus: ButtonType::Transfer,
5861                            instance: source.line_instance,
5862                            mode: LampMode::Flash,
5863                        },
5864                        protocol,
5865                    )
5866                    .await?;
5867                }
5868                CommandAction::SetCallSelected {
5869                    call_id, selected, ..
5870                } => {
5871                    let call = require_call(state, call_id)?.clone();
5872                    send_message(
5873                        stream,
5874                        &ServerMessage::CallSelectStatus {
5875                            status: u32::from(selected),
5876                            call_reference: call.wire_reference,
5877                            line_instance: call.line_instance,
5878                        },
5879                        protocol,
5880                    )
5881                    .await?;
5882                }
5883                CommandAction::SetMwi {
5884                    line_instance,
5885                    enabled,
5886                    ..
5887                } => {
5888                    let line_instance = line_instance.get();
5889                    state.mwi_by_line.insert(line_instance, enabled);
5890                    send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
5891                }
5892                CommandAction::SetForwardStatus {
5893                    line_instance,
5894                    forward_all,
5895                    forward_busy,
5896                    forward_no_answer,
5897                    ..
5898                } => {
5899                    let line_instance = line_instance.get();
5900                    state.forwarding_by_line.insert(
5901                        line_instance,
5902                        SessionForwarding {
5903                            all: forward_all.clone(),
5904                            busy: forward_busy.clone(),
5905                            no_answer: forward_no_answer.clone(),
5906                        },
5907                    );
5908                    send_message(
5909                        stream,
5910                        &ServerMessage::ForwardStatus {
5911                            line_instance,
5912                            forward_all,
5913                            forward_busy,
5914                            forward_no_answer,
5915                        },
5916                        protocol,
5917                    )
5918                    .await?;
5919                }
5920                CommandAction::SetFeatureStatus {
5921                    instance, enabled, ..
5922                } => {
5923                    let instance = instance.get();
5924                    if let Some(messages) = feature_state_messages(&state.device, instance, enabled)
5925                    {
5926                        let ServerMessage::FeatureStatus {
5927                            button_type,
5928                            state: feature_state,
5929                            ..
5930                        } = &messages[0]
5931                        else {
5932                            unreachable!("feature status starts with a feature-state message")
5933                        };
5934                        state.feature_states.insert(
5935                            instance,
5936                            SessionFeatureState {
5937                                button_type: *button_type,
5938                                state: *feature_state,
5939                            },
5940                        );
5941                        for message in messages {
5942                            send_station_ui_message(stream, state, &message).await?;
5943                        }
5944                    }
5945                }
5946                CommandAction::SetDoNotDisturbStatus {
5947                    instance,
5948                    mode,
5949                    button_mode,
5950                    ..
5951                } => {
5952                    let instance = instance.get();
5953                    if let Some(messages) = do_not_disturb_state_messages(
5954                        &state.device,
5955                        instance,
5956                        mode,
5957                        button_mode,
5958                        protocol,
5959                    ) {
5960                        let ServerMessage::FeatureStatus {
5961                            button_type,
5962                            state: feature_state,
5963                            ..
5964                        } = &messages[0]
5965                        else {
5966                            unreachable!("DND status starts with a feature-state message")
5967                        };
5968                        state.feature_states.insert(
5969                            instance,
5970                            SessionFeatureState {
5971                                button_type: *button_type,
5972                                state: *feature_state,
5973                            },
5974                        );
5975                        for message in messages {
5976                            send_station_ui_message(stream, state, &message).await?;
5977                        }
5978                    }
5979                }
5980                CommandAction::SetMobilityAppearance {
5981                    mobility_instance,
5982                    appearance,
5983                    ..
5984                } => {
5985                    let mobility_instance = mobility_instance.get();
5986                    let configured = state.device.buttons.iter().any(|button| {
5987                        matches!(
5988                            button,
5989                            ButtonDefinition::Feature(feature)
5990                                if feature.instance == mobility_instance
5991                                    && feature.feature == ButtonType::Mobility
5992                        )
5993                    });
5994                    if !configured {
5995                        return Err(CodecError::InvalidDefinition(format!(
5996                            "device {} has no mobility button instance {mobility_instance}",
5997                            state.device.id
5998                        ))
5999                        .into());
6000                    }
6001                    let previous = state.mobility_appearances.get(&mobility_instance).cloned();
6002                    let mut next_appearances = state.mobility_appearances.clone();
6003                    match &appearance {
6004                        Some(appearance) => {
6005                            next_appearances.insert(mobility_instance, appearance.clone());
6006                        }
6007                        None => {
6008                            next_appearances.remove(&mobility_instance);
6009                        }
6010                    }
6011                    let candidate = mobility_device_candidate(
6012                        &state.device,
6013                        &state.mobility_appearances,
6014                        &next_appearances,
6015                    )?;
6016
6017                    send_message(
6018                        stream,
6019                        &ServerMessage::ButtonTemplate {
6020                            buttons: button_template(&candidate),
6021                        },
6022                        protocol,
6023                    )
6024                    .await?;
6025                    if let Some(appearance) = &appearance {
6026                        if let Some(message) = line_status(&candidate, appearance.instance) {
6027                            send_station_ui_message(stream, state, &message).await?;
6028                        }
6029                    } else if let Some(previous) = &previous {
6030                        send_station_ui_message(
6031                            stream,
6032                            state,
6033                            &ServerMessage::LineStatus {
6034                                instance: previous.instance,
6035                                number: String::new(),
6036                                display_name: String::new(),
6037                            },
6038                        )
6039                        .await?;
6040                    }
6041                    state.device = candidate;
6042                    state.mobility_appearances = next_appearances;
6043                }
6044                CommandAction::SetBlfStatus {
6045                    instance,
6046                    number,
6047                    label,
6048                    state: blf_state,
6049                    caller,
6050                    ..
6051                } => {
6052                    let instance = instance.get();
6053                    for message in
6054                        blf_status_messages(instance, &number, &label, blf_state, caller.as_ref())
6055                    {
6056                        send_station_ui_message(stream, state, &message).await?;
6057                    }
6058                    if let Some(notification) = hinted_ringing_notification(
6059                        &state.device,
6060                        &label,
6061                        caller.as_ref(),
6062                        blf_state,
6063                    ) {
6064                        for message in status_message_frames(
6065                            notification,
6066                            state.registration.device_type,
6067                            &mut state.persistent_status_message,
6068                        ) {
6069                            send_station_ui_message(stream, state, &message).await?;
6070                        }
6071                    }
6072                }
6073                CommandAction::ShowParkingMenu {
6074                    instance,
6075                    transaction_id,
6076                    lot,
6077                    calls,
6078                    ..
6079                } => {
6080                    let instance = instance.get();
6081                    let transaction_id = transaction_id.get();
6082                    send_message(
6083                        stream,
6084                        &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6085                            application_id: PARKING_APPLICATION_ID,
6086                            line_instance: instance,
6087                            call_reference: 0,
6088                            transaction_id,
6089                            sequence_flag: 0,
6090                            display_priority: 2,
6091                            conference_id: 0,
6092                            application_instance_id: instance,
6093                            routing: 0,
6094                            data: parking_menu_xml(instance, transaction_id, &lot, &calls)?
6095                                .into_bytes(),
6096                        }),
6097                        protocol,
6098                    )
6099                    .await?;
6100                    state.pending_parking_menu = Some(PendingParkingMenu {
6101                        instance,
6102                        transaction_id,
6103                    });
6104                }
6105                CommandAction::ShowConferenceList {
6106                    call_id,
6107                    conference_id,
6108                    participants,
6109                    ..
6110                } => {
6111                    let call = require_call(state, call_id)?.clone();
6112                    let family = if protocol >= ProtocolVersion::V8 {
6113                        ConferenceMenuFamily::IconMenu
6114                    } else {
6115                        ConferenceMenuFamily::Menu
6116                    };
6117                    let data = ConferenceListDocument::new(conference_id, &participants, family)?
6118                        .to_xml()?
6119                        .into_bytes();
6120                    send_message(
6121                        stream,
6122                        &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6123                            application_id: ConferenceListAction::APPLICATION_ID,
6124                            line_instance: call.line_instance,
6125                            call_reference: call.wire_reference,
6126                            transaction_id: conference_id.get(),
6127                            sequence_flag: 0,
6128                            display_priority: 2,
6129                            conference_id: conference_id.get(),
6130                            application_instance_id: call.line_instance,
6131                            routing: 0,
6132                            data,
6133                        }),
6134                        protocol,
6135                    )
6136                    .await?;
6137                }
6138                CommandAction::ShowConferenceParticipantActions {
6139                    call_id,
6140                    conference_id,
6141                    participant,
6142                    removable,
6143                    demotable,
6144                    ..
6145                } => {
6146                    let call = require_call(state, call_id)?.clone();
6147                    let family = if protocol >= ProtocolVersion::V8 {
6148                        ConferenceMenuFamily::IconMenu
6149                    } else {
6150                        ConferenceMenuFamily::Menu
6151                    };
6152                    let data = ConferenceParticipantActionsDocument::new(
6153                        conference_id,
6154                        &participant,
6155                        removable,
6156                        demotable,
6157                        family,
6158                    )?
6159                    .to_xml()?
6160                    .into_bytes();
6161                    send_message(
6162                        stream,
6163                        &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6164                            application_id: ConferenceListAction::APPLICATION_ID,
6165                            line_instance: call.line_instance,
6166                            call_reference: call.wire_reference,
6167                            transaction_id: conference_id.get(),
6168                            sequence_flag: 0,
6169                            display_priority: 2,
6170                            conference_id: conference_id.get(),
6171                            application_instance_id: call.line_instance,
6172                            routing: 0,
6173                            data,
6174                        }),
6175                        protocol,
6176                    )
6177                    .await?;
6178                }
6179                CommandAction::ShowTextService {
6180                    line_instance,
6181                    call_reference,
6182                    transaction_id,
6183                    priority,
6184                    document,
6185                    ..
6186                } => {
6187                    for message in text_service_messages(
6188                        line_instance,
6189                        call_reference,
6190                        transaction_id,
6191                        priority,
6192                        &document,
6193                        protocol,
6194                    )? {
6195                        send_message(stream, &message, protocol).await?;
6196                    }
6197                }
6198                CommandAction::ShowInputService {
6199                    line_instance,
6200                    call_reference,
6201                    application_id,
6202                    transaction_id,
6203                    priority,
6204                    document,
6205                    ..
6206                } => {
6207                    for message in input_service_messages(
6208                        line_instance,
6209                        call_reference,
6210                        application_id,
6211                        transaction_id,
6212                        priority,
6213                        &document,
6214                        protocol,
6215                    )? {
6216                        send_message(stream, &message, protocol).await?;
6217                    }
6218                }
6219                CommandAction::ExecutePhoneActions {
6220                    line_instance,
6221                    call_reference,
6222                    application_id,
6223                    transaction_id,
6224                    priority,
6225                    document,
6226                    ..
6227                } => {
6228                    for message in execute_phone_action_messages(
6229                        line_instance,
6230                        call_reference,
6231                        application_id,
6232                        transaction_id,
6233                        priority,
6234                        &document,
6235                        protocol,
6236                    )? {
6237                        send_message(stream, &message, protocol).await?;
6238                    }
6239                }
6240                CommandAction::ShowImageService {
6241                    line_instance,
6242                    call_reference,
6243                    application_id,
6244                    transaction_id,
6245                    priority,
6246                    document,
6247                    ..
6248                } => {
6249                    for message in image_service_messages(
6250                        line_instance,
6251                        call_reference,
6252                        application_id,
6253                        transaction_id,
6254                        priority,
6255                        &document,
6256                        protocol,
6257                    )? {
6258                        send_message(stream, &message, protocol).await?;
6259                    }
6260                }
6261                CommandAction::ShowStatusService {
6262                    line_instance,
6263                    call_reference,
6264                    application_id,
6265                    transaction_id,
6266                    priority,
6267                    document,
6268                    ..
6269                } => {
6270                    for message in status_service_messages(
6271                        line_instance,
6272                        call_reference,
6273                        application_id,
6274                        transaction_id,
6275                        priority,
6276                        &document,
6277                        protocol,
6278                    )? {
6279                        send_message(stream, &message, protocol).await?;
6280                    }
6281                }
6282                CommandAction::SetBackgroundImage {
6283                    transaction_id,
6284                    document,
6285                    ..
6286                } => {
6287                    let message = background_control_message(
6288                        transaction_id,
6289                        &PhoneBackgroundControlDocument::Set(document),
6290                    )?;
6291                    send_message(stream, &message, protocol).await?;
6292                }
6293                CommandAction::PreviewBackgroundImage {
6294                    transaction_id,
6295                    document,
6296                    ..
6297                } => {
6298                    let message = background_control_message(
6299                        transaction_id,
6300                        &PhoneBackgroundControlDocument::Preview(document),
6301                    )?;
6302                    send_message(stream, &message, protocol).await?;
6303                }
6304                CommandAction::SetRingtone {
6305                    transaction_id,
6306                    document,
6307                    ..
6308                } => {
6309                    let message = ringtone_control_message(transaction_id, &document)?;
6310                    send_message(stream, &message, protocol).await?;
6311                }
6312                CommandAction::StartTone { call_id, tone, .. } => {
6313                    let call = require_call(state, call_id)?.clone();
6314                    let message = if tone == Tone::Silence {
6315                        ServerMessage::StopTone {
6316                            line_instance: call.line_instance,
6317                            call_reference: call.wire_reference,
6318                        }
6319                    } else {
6320                        ServerMessage::StartTone {
6321                            tone,
6322                            direction: ToneDirection::User,
6323                            line_instance: call.line_instance,
6324                            call_reference: call.wire_reference,
6325                        }
6326                    };
6327                    send_message(stream, &message, protocol).await?;
6328                }
6329                CommandAction::StartAnnouncement {
6330                    conference_id,
6331                    announcements,
6332                    end_of_ack,
6333                    participant_ids,
6334                    hearing_participant_mask,
6335                    play_mode,
6336                    ..
6337                } => {
6338                    let _ = (
6339                        conference_id,
6340                        announcements,
6341                        end_of_ack,
6342                        participant_ids,
6343                        hearing_participant_mask,
6344                        play_mode,
6345                    );
6346                    return Err(ServerError::InvalidStationCommand {
6347                        message: "StartAnnouncement",
6348                    });
6349                }
6350                CommandAction::StopAnnouncement { conference_id, .. } => {
6351                    let _ = conference_id;
6352                    return Err(ServerError::InvalidStationCommand {
6353                        message: "StopAnnouncement",
6354                    });
6355                }
6356                CommandAction::AnnouncementFinish {
6357                    conference_id,
6358                    play_status,
6359                    ..
6360                } => {
6361                    let _ = (conference_id, play_status);
6362                    return Err(ServerError::InvalidStationCommand {
6363                        message: "AnnouncementFinish",
6364                    });
6365                }
6366                CommandAction::SetCallInfo { call_id, info, .. } => {
6367                    let statistics_directory_number =
6368                        statistics_directory_for_call_info(&info).to_owned();
6369                    if let Some(stored) = state.calls_by_id.get_mut(&call_id) {
6370                        stored.statistics_directory_number = statistics_directory_number;
6371                    }
6372                    let call = require_call(state, call_id)?.clone();
6373                    send_station_ui_message(
6374                        stream,
6375                        state,
6376                        &ServerMessage::CallInfo {
6377                            info,
6378                            line_instance: call.line_instance,
6379                            call_reference: call.wire_reference,
6380                        },
6381                    )
6382                    .await?;
6383                }
6384                CommandAction::CommitOutboundCall { call_id, info, .. } => {
6385                    let statistics_directory_number =
6386                        statistics_directory_for_call_info(&info).to_owned();
6387                    let call = require_call_mut(state, call_id)?;
6388                    call.state = CallState::Proceed;
6389                    call.history_disposition =
6390                        updated_history_disposition(call.history_disposition, CallState::Proceed);
6391                    call.statistics_directory_number = statistics_directory_number;
6392                    let call = call.clone();
6393                    let number = digit_character(config.dial_terminator)
6394                        .and_then(|terminator| call.dialed_number.strip_suffix(terminator))
6395                        .unwrap_or(&call.dialed_number)
6396                        .to_owned();
6397                    remember_last_number(state, call.line_instance, &number, config);
6398                    state.active_call_id = Some(call.call_id);
6399                    refresh_mwi_lamps(stream, state, protocol).await?;
6400                    for message in [
6401                        ServerMessage::StopTone {
6402                            line_instance: call.line_instance,
6403                            call_reference: call.wire_reference,
6404                        },
6405                        ServerMessage::SetLamp {
6406                            stimulus: ButtonType::Line,
6407                            instance: call.line_instance,
6408                            mode: LampMode::Blink,
6409                        },
6410                        ServerMessage::CallInfo {
6411                            info,
6412                            line_instance: call.line_instance,
6413                            call_reference: call.wire_reference,
6414                        },
6415                        ServerMessage::DialedNumber {
6416                            number,
6417                            line_instance: call.line_instance,
6418                            call_reference: call.wire_reference,
6419                        },
6420                        ServerMessage::CallState {
6421                            state: CallState::Proceed,
6422                            line_instance: call.line_instance,
6423                            call_reference: call.wire_reference,
6424                        },
6425                    ] {
6426                        send_station_ui_message(stream, state, &message).await?;
6427                    }
6428                }
6429                CommandAction::PresentOutboundProceeding { call_id, info, .. } => {
6430                    let statistics_directory_number =
6431                        statistics_directory_for_call_info(&info).to_owned();
6432                    let call = require_call_mut(state, call_id)?;
6433                    call.state = CallState::Proceed;
6434                    call.history_disposition =
6435                        updated_history_disposition(call.history_disposition, CallState::Proceed);
6436                    call.statistics_directory_number = statistics_directory_number;
6437                    let call = call.clone();
6438                    state.active_call_id = Some(call.call_id);
6439                    refresh_mwi_lamps(stream, state, protocol).await?;
6440                    for message in [
6441                        ServerMessage::StopTone {
6442                            line_instance: call.line_instance,
6443                            call_reference: call.wire_reference,
6444                        },
6445                        ServerMessage::CallState {
6446                            state: CallState::Proceed,
6447                            line_instance: call.line_instance,
6448                            call_reference: call.wire_reference,
6449                        },
6450                        ServerMessage::CallInfo {
6451                            info,
6452                            line_instance: call.line_instance,
6453                            call_reference: call.wire_reference,
6454                        },
6455                        ServerMessage::DisplayPrompt {
6456                            timeout_seconds: 0,
6457                            text: "Call Proceed".into(),
6458                            line_instance: call.line_instance,
6459                            call_reference: call.wire_reference,
6460                        },
6461                    ] {
6462                        send_station_ui_message(stream, state, &message).await?;
6463                    }
6464                }
6465                CommandAction::PresentOutboundRinging { call_id, info, .. } => {
6466                    let statistics_directory_number =
6467                        statistics_directory_for_call_info(&info).to_owned();
6468                    let call = require_call_mut(state, call_id)?;
6469                    call.state = CallState::Proceed;
6470                    call.history_disposition =
6471                        updated_history_disposition(call.history_disposition, CallState::Proceed);
6472                    call.statistics_directory_number = statistics_directory_number;
6473                    let call = call.clone();
6474                    state.active_call_id = Some(call.call_id);
6475                    let key_mode = transfer_key_mode(&call, CallState::RingOut);
6476                    state.active_key_mode = key_mode;
6477                    refresh_mwi_lamps(stream, state, protocol).await?;
6478                    for message in [
6479                        ServerMessage::CallState {
6480                            state: CallState::Proceed,
6481                            line_instance: call.line_instance,
6482                            call_reference: call.wire_reference,
6483                        },
6484                        ServerMessage::DisplayPrompt {
6485                            timeout_seconds: 0,
6486                            text: "Ring out".into(),
6487                            line_instance: call.line_instance,
6488                            call_reference: call.wire_reference,
6489                        },
6490                        ServerMessage::StartTone {
6491                            tone: Tone::Alerting,
6492                            direction: ToneDirection::User,
6493                            line_instance: call.line_instance,
6494                            call_reference: call.wire_reference,
6495                        },
6496                        ServerMessage::SelectSoftKeys {
6497                            line_instance: call.line_instance,
6498                            call_reference: call.wire_reference,
6499                            set: key_mode,
6500                            valid_mask: state.device.soft_keys.valid_mask(key_mode),
6501                        },
6502                        ServerMessage::CallInfo {
6503                            info,
6504                            line_instance: call.line_instance,
6505                            call_reference: call.wire_reference,
6506                        },
6507                    ] {
6508                        send_station_ui_message(stream, state, &message).await?;
6509                    }
6510                }
6511                CommandAction::SetCallState {
6512                    call_id,
6513                    state: call_state,
6514                    ..
6515                } => {
6516                    let transfer_source_to_clear =
6517                        state
6518                            .calls_by_id
6519                            .get(&call_id)
6520                            .and_then(|call| match call.transfer_role {
6521                                Some(SessionTransferRole::Source {
6522                                    consultation_call_id,
6523                                }) if call_state != CallState::Transfer => {
6524                                    Some((consultation_call_id, call.line_instance))
6525                                }
6526                                _ => None,
6527                            });
6528                    let call = require_call_mut(state, call_id)?;
6529                    call.state = call_state;
6530                    call.history_disposition =
6531                        updated_history_disposition(call.history_disposition, call_state);
6532                    let call = call.clone();
6533                    if matches!(
6534                        call_state,
6535                        CallState::Proceed | CallState::RingOut | CallState::Connected
6536                    ) {
6537                        remember_last_number(
6538                            state,
6539                            call.line_instance,
6540                            &call.dialed_number,
6541                            config,
6542                        );
6543                    }
6544                    prepare_call_state_ui(stream, &call, call_state, protocol).await?;
6545                    send_message(
6546                        stream,
6547                        &ServerMessage::CallState {
6548                            state: call_state,
6549                            line_instance: call.line_instance,
6550                            call_reference: call.wire_reference,
6551                        },
6552                        protocol,
6553                    )
6554                    .await?;
6555                    finish_call_state_ui(stream, &call, call_state, state.station_context())
6556                        .await?;
6557                    let set = transfer_key_mode(&call, call_state);
6558                    state.active_key_mode = set;
6559                    match call_state {
6560                        CallState::Connected
6561                        | CallState::OffHook
6562                        | CallState::Transfer
6563                        | CallState::RingOut
6564                        | CallState::Proceed
6565                        | CallState::IntercomOneWay => {
6566                            state.active_call_id = Some(call.call_id);
6567                        }
6568                        CallState::OnHook
6569                        | CallState::Hold
6570                        | CallState::HoldYellow
6571                        | CallState::HoldRed
6572                            if state.active_call_id == Some(call.call_id) =>
6573                        {
6574                            state.active_call_id = None;
6575                        }
6576                        _ => {}
6577                    }
6578                    refresh_mwi_lamps(stream, state, protocol).await?;
6579                    send_message(
6580                        stream,
6581                        &ServerMessage::SelectSoftKeys {
6582                            line_instance: call.line_instance,
6583                            call_reference: call.wire_reference,
6584                            set,
6585                            valid_mask: state.device.soft_keys.valid_mask(set),
6586                        },
6587                        protocol,
6588                    )
6589                    .await?;
6590                    if let Some((consultation_call_id, line_instance)) = transfer_source_to_clear {
6591                        if let Some(source) = state.calls_by_id.get_mut(&call_id) {
6592                            source.transfer_role = None;
6593                        }
6594                        if let Some(consultation) = state.calls_by_id.get_mut(&consultation_call_id)
6595                        {
6596                            consultation.transfer_role = None;
6597                        }
6598                        send_message(
6599                            stream,
6600                            &ServerMessage::SetLamp {
6601                                stimulus: ButtonType::Transfer,
6602                                instance: line_instance,
6603                                mode: LampMode::Off,
6604                            },
6605                            protocol,
6606                        )
6607                        .await?;
6608                    }
6609                }
6610                CommandAction::DisplayPrompt {
6611                    call_id,
6612                    timeout_seconds,
6613                    text,
6614                    ..
6615                } => {
6616                    let call = require_call(state, call_id)?.clone();
6617                    send_station_ui_message(
6618                        stream,
6619                        state,
6620                        &ServerMessage::DisplayPrompt {
6621                            timeout_seconds,
6622                            text,
6623                            line_instance: call.line_instance,
6624                            call_reference: call.wire_reference,
6625                        },
6626                    )
6627                    .await?;
6628                }
6629                CommandAction::ClearPrompt { call_id, .. } => {
6630                    let call = require_call(state, call_id)?.clone();
6631                    send_message(
6632                        stream,
6633                        &ServerMessage::ClearPrompt {
6634                            line_instance: call.line_instance,
6635                            call_reference: call.wire_reference,
6636                        },
6637                        protocol,
6638                    )
6639                    .await?;
6640                }
6641                CommandAction::SetStatusMessage { message, beep, .. } => {
6642                    let frames = status_message_frames(
6643                        message,
6644                        state.registration.device_type,
6645                        &mut state.persistent_status_message,
6646                    );
6647                    for frame in frames {
6648                        send_station_ui_message(stream, state, &frame).await?;
6649                    }
6650                    if beep {
6651                        send_message(
6652                            stream,
6653                            &ServerMessage::StartTone {
6654                                tone: Tone::ZipZip,
6655                                direction: ToneDirection::User,
6656                                line_instance: 0,
6657                                call_reference: 0,
6658                            },
6659                            protocol,
6660                        )
6661                        .await?;
6662                    }
6663                }
6664                CommandAction::SetMicrophoneMode { enabled, .. } => {
6665                    send_message(
6666                        stream,
6667                        &ServerMessage::SetMicrophoneMode(if enabled {
6668                            MicrophoneMode::On
6669                        } else {
6670                            MicrophoneMode::Off
6671                        }),
6672                        protocol,
6673                    )
6674                    .await?;
6675                }
6676                CommandAction::SetRecordingStatus {
6677                    call_id, active, ..
6678                } => {
6679                    let call = require_call(state, call_id)?.clone();
6680                    send_message(
6681                        stream,
6682                        &ServerMessage::RecordingStatus {
6683                            call_reference: call.wire_reference,
6684                            active,
6685                        },
6686                        protocol,
6687                    )
6688                    .await?;
6689                }
6690                CommandAction::ResetDevice { reset_type, .. } => {
6691                    send_message(stream, &ServerMessage::Reset(reset_type), protocol).await?;
6692                }
6693                ringing @ (CommandAction::StartRinging { call_id }
6694                | CommandAction::StopRinging { call_id }) => {
6695                    let enabled = matches!(ringing, CommandAction::StartRinging { .. });
6696                    let call = require_call(state, call_id)?.clone();
6697                    send_message(
6698                        stream,
6699                        &ServerMessage::SetRinger {
6700                            mode: if enabled {
6701                                RingerMode::Inside
6702                            } else {
6703                                RingerMode::Off
6704                            },
6705                            duration: RingDuration::Normal,
6706                            line_instance: call.line_instance,
6707                            call_reference: call.wire_reference,
6708                        },
6709                        protocol,
6710                    )
6711                    .await?;
6712                }
6713                CommandAction::OpenReceiveChannel {
6714                    call_id,
6715                    source,
6716                    codec,
6717                    packet_ms,
6718                    max_frames_per_packet,
6719                    dtmf_mode,
6720                    audio_processing,
6721                    ..
6722                } => {
6723                    let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
6724                    let request = allocate_media_request_identity(state, call_id)?;
6725                    let call = require_call_mut(state, call_id)?;
6726                    call.media.requested = true;
6727                    call.media.codec = codec;
6728                    call.media.packet_ms = packet_ms;
6729                    call.media.max_frames_per_packet = max_frames_per_packet;
6730                    call.media.receive.telephone_event_payload = telephone_event_payload;
6731                    call.media.receive.peer = None;
6732                    call.media.receive.state = MediaChannelState::Opening;
6733                    call.media.receive.deadline =
6734                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6735                    call.media.receive.request = Some(request);
6736                    if call.media.transmit.state == MediaChannelState::Closed {
6737                        call.media.transmit.request = None;
6738                    }
6739                    call.media.coupled_transmit_endpoint = None;
6740                    let call = call.clone();
6741                    send_message(
6742                        stream,
6743                        &ServerMessage::OpenReceiveChannel {
6744                            call_reference: call.wire_reference,
6745                            passthrough_party_id: request.token().get(),
6746                            packet_ms,
6747                            codec,
6748                            echo_cancellation: audio_processing.echo_cancellation,
6749                            telephone_event_payload,
6750                            source_address: source
6751                                .map(|endpoint| endpoint.address)
6752                                .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
6753                            source_port: source.map_or(0, |endpoint| endpoint.rtp_port),
6754                            encryption: None,
6755                            wire: None,
6756                        },
6757                        protocol,
6758                    )
6759                    .await?;
6760                }
6761                CommandAction::OpenMultimediaReceiveChannel {
6762                    call_id,
6763                    descriptor,
6764                } => {
6765                    let call_state = require_call(state, call_id)?.state;
6766                    if call_state != CallState::Connected {
6767                        return Err(ServerError::InvalidCallTransaction {
6768                            call_id,
6769                            operation: "open video receive media",
6770                            state: call_state,
6771                        });
6772                    }
6773                    validate_multimedia_receive(state, &descriptor)?;
6774                    let request = allocate_video_receive_identity(state, call_id)?;
6775                    let replacement_close = take_multimedia_receive_close(state, call_id);
6776                    let call = require_call_mut(state, call_id)?;
6777                    let line_instance = call.line_instance;
6778                    let call_reference = CallReference::new(call.wire_reference);
6779                    call.video_receive.leg = Some(VideoReceiveLeg {
6780                        request,
6781                        conference_id: descriptor.conference_id,
6782                        codec: descriptor.payload.codec(),
6783                        requested_address_type: descriptor.requested_address_type,
6784                        state: MediaChannelState::Opening,
6785                        deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
6786                    });
6787
6788                    if let Some(close) = replacement_close {
6789                        send_message(stream, &close, protocol).await?;
6790                    }
6791                    send_message(
6792                        stream,
6793                        &ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
6794                            conference_id: descriptor.conference_id,
6795                            passthrough_party_id: request.token().get().into(),
6796                            line_instance,
6797                            call_reference,
6798                            payload: descriptor.payload,
6799                            conference_creator: descriptor.conference_creator,
6800                            encryption: descriptor.encryption,
6801                            stream_passthrough_id: descriptor.stream_passthrough_id,
6802                            associated_stream_id: descriptor.associated_stream_id,
6803                            source: descriptor.source,
6804                            requested_address_type: descriptor.requested_address_type,
6805                        }),
6806                        protocol,
6807                    )
6808                    .await?;
6809                }
6810                CommandAction::CloseMultimediaReceiveChannel { call_id } => {
6811                    if let Some(close) = take_multimedia_receive_close(state, call_id) {
6812                        send_message(stream, &close, protocol).await?;
6813                    }
6814                }
6815                CommandAction::StartMultimediaTransmission {
6816                    call_id,
6817                    descriptor,
6818                } => {
6819                    let call_state = require_call(state, call_id)?.state;
6820                    if call_state != CallState::Connected {
6821                        return Err(ServerError::InvalidCallTransaction {
6822                            call_id,
6823                            operation: "start video transmit media",
6824                            state: call_state,
6825                        });
6826                    }
6827                    validate_multimedia_transmit(state, &descriptor)?;
6828                    let request = allocate_video_transmit_identity(state, call_id)?;
6829                    let replacement_stop = take_multimedia_transmit_stop(state, call_id);
6830                    let call_reference = {
6831                        let call = require_call_mut(state, call_id)?;
6832                        let call_reference = CallReference::new(call.wire_reference);
6833                        call.video_transmit.leg = Some(VideoTransmitLeg {
6834                            request,
6835                            conference_id: descriptor.conference_id,
6836                            codec: descriptor.payload.codec(),
6837                            address_type: address_type(descriptor.endpoint.address),
6838                            state: MediaChannelState::Opening,
6839                            deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
6840                        });
6841                        call_reference
6842                    };
6843
6844                    if let Some(stop) = replacement_stop {
6845                        send_message(stream, &stop, protocol).await?;
6846                    }
6847                    send_message(
6848                        stream,
6849                        &ServerMessage::StartMultimediaTransmission(MultimediaTransmissionStart {
6850                            conference_id: descriptor.conference_id,
6851                            passthrough_party_id: request.token().get().into(),
6852                            endpoint: descriptor.endpoint,
6853                            call_reference,
6854                            payload: descriptor.payload,
6855                            traffic_class: descriptor.traffic_class,
6856                            encryption: descriptor.encryption,
6857                            stream_passthrough_id: descriptor.stream_passthrough_id,
6858                            associated_stream_id: descriptor.associated_stream_id,
6859                        }),
6860                        protocol,
6861                    )
6862                    .await?;
6863                }
6864                CommandAction::StopMultimediaTransmission { call_id } => {
6865                    if let Some(stop) = take_multimedia_transmit_stop(state, call_id) {
6866                        send_message(stream, &stop, protocol).await?;
6867                    }
6868                }
6869                flow_action @ (CommandAction::SetMultimediaTransmitBitRate {
6870                    call_id,
6871                    passthrough_party_id,
6872                    maximum_bit_rate,
6873                }
6874                | CommandAction::NotifyMultimediaTransmitBitRate {
6875                    call_id,
6876                    passthrough_party_id,
6877                    maximum_bit_rate,
6878                }) => {
6879                    if maximum_bit_rate == 0 {
6880                        return Err(ServerError::InvalidMultimediaTransmitControl(
6881                            "maximum bit rate must be nonzero",
6882                        ));
6883                    }
6884                    let (conference_id, call_reference) =
6885                        multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
6886                    let flow = VideoFlowControl {
6887                        conference_id,
6888                        passthrough_party_id,
6889                        call_reference,
6890                        maximum_bit_rate,
6891                    };
6892                    let message = if matches!(
6893                        flow_action,
6894                        CommandAction::SetMultimediaTransmitBitRate { .. }
6895                    ) {
6896                        ServerMessage::FlowControlCommand(flow)
6897                    } else {
6898                        ServerMessage::FlowControlNotify(flow)
6899                    };
6900                    send_message(stream, &message, protocol).await?;
6901                }
6902                CommandAction::ControlMultimediaTransmission {
6903                    call_id,
6904                    passthrough_party_id,
6905                    control,
6906                } => {
6907                    let (conference_id, call_reference) =
6908                        multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
6909                    let (command, data) = encode_multimedia_transmit_control(control)?;
6910                    send_message(
6911                        stream,
6912                        &ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
6913                            conference_id,
6914                            passthrough_party_id,
6915                            call_reference,
6916                            command,
6917                            data,
6918                        }),
6919                        protocol,
6920                    )
6921                    .await?;
6922                }
6923                CommandAction::OpenOutboundMedia {
6924                    call_id,
6925                    source,
6926                    mut endpoint,
6927                    codec,
6928                    packet_ms,
6929                    max_frames_per_packet,
6930                    dtmf_mode,
6931                    audio_processing,
6932                    traffic_class,
6933                } => {
6934                    let call_state = require_call(state, call_id)?.state;
6935                    if !matches!(call_state, CallState::Proceed | CallState::RingOut) {
6936                        return Err(ServerError::InvalidCallTransaction {
6937                            call_id,
6938                            operation: "open coupled outbound media",
6939                            state: call_state,
6940                        });
6941                    }
6942                    let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
6943                    let source_address = source
6944                        .map(|source| source.address)
6945                        .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
6946                    let source_port = source.map_or(0, |source| source.rtp_port);
6947                    let request = allocate_media_request_identity(state, call_id)?;
6948                    let call = require_call_mut(state, call_id)?;
6949                    call.media.requested = true;
6950                    call.media.codec = codec;
6951                    call.media.packet_ms = packet_ms;
6952                    call.media.max_frames_per_packet = max_frames_per_packet;
6953                    call.media.receive.telephone_event_payload = telephone_event_payload;
6954                    call.media.receive.peer = None;
6955                    call.media.receive.state = MediaChannelState::Opening;
6956                    call.media.receive.deadline =
6957                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6958                    call.media.receive.request = Some(request);
6959                    call.media.transmit.telephone_event_payload = telephone_event_payload;
6960                    call.media.transmit.peer = None;
6961                    call.media.transmit.state = MediaChannelState::Opening;
6962                    call.media.transmit.deadline =
6963                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6964                    call.media.transmit.request = Some(request);
6965                    endpoint.telephone_event_payload = telephone_event_payload;
6966                    call.media.coupled_transmit_endpoint = Some(endpoint);
6967                    let call = call.clone();
6968                    send_message(
6969                        stream,
6970                        &ServerMessage::OpenReceiveChannel {
6971                            call_reference: call.wire_reference,
6972                            passthrough_party_id: request.token().get(),
6973                            packet_ms,
6974                            codec,
6975                            echo_cancellation: audio_processing.echo_cancellation,
6976                            telephone_event_payload,
6977                            source_address,
6978                            source_port,
6979                            encryption: None,
6980                            wire: None,
6981                        },
6982                        protocol,
6983                    )
6984                    .await?;
6985                    send_message(
6986                        stream,
6987                        &ServerMessage::StartMediaTransmission {
6988                            call_reference: call.wire_reference,
6989                            passthrough_party_id: request.token().get(),
6990                            endpoint,
6991                            silence_suppression: audio_processing.silence_suppression,
6992                            traffic_class,
6993                            encryption: None,
6994                            wire: None,
6995                        },
6996                        protocol,
6997                    )
6998                    .await?;
6999                }
7000                CommandAction::CloseReceiveChannel { call_id, .. } => {
7001                    let call = require_call_mut(state, call_id)?;
7002                    call.media.coupled_transmit_endpoint = None;
7003                    if call.media.receive.state != MediaChannelState::Closed {
7004                        call.media.receive.state = MediaChannelState::Closed;
7005                        call.media.receive.deadline = None;
7006                        let call = call.clone();
7007                        send_message(
7008                            stream,
7009                            &ServerMessage::CloseReceiveChannel(AudioStreamControl {
7010                                conference_id: ConferenceId::new(call.wire_reference),
7011                                call_reference: CallReference::new(call.wire_reference),
7012                                passthrough_party_id: media_request_party_id(
7013                                    call.media.receive.request,
7014                                    call.wire_reference,
7015                                )
7016                                .into(),
7017                                port_handling_flag: 0,
7018                            }),
7019                            protocol,
7020                        )
7021                        .await?;
7022                    }
7023                }
7024                CommandAction::StartMedia {
7025                    call_id,
7026                    mut endpoint,
7027                    dtmf_mode,
7028                    audio_processing,
7029                    traffic_class,
7030                } => {
7031                    let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
7032                    let request = {
7033                        let call = require_call(state, call_id)?;
7034                        if call.media.transmit.request.is_none() {
7035                            call.media.receive.request
7036                        } else {
7037                            None
7038                        }
7039                    };
7040                    let request = match request {
7041                        Some(request) => request,
7042                        None => allocate_media_request_identity(state, call_id)?,
7043                    };
7044                    let call = require_call_mut(state, call_id)?;
7045                    call.media.requested = true;
7046                    call.media.transmit.telephone_event_payload = telephone_event_payload;
7047                    call.media.transmit.peer = None;
7048                    call.media.transmit.state = MediaChannelState::Opening;
7049                    call.media.transmit.deadline =
7050                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
7051                    call.media.transmit.request = Some(request);
7052                    call.media.coupled_transmit_endpoint = None;
7053                    let call = call.clone();
7054                    endpoint.telephone_event_payload = telephone_event_payload;
7055                    send_message(
7056                        stream,
7057                        &ServerMessage::StartMediaTransmission {
7058                            call_reference: call.wire_reference,
7059                            passthrough_party_id: request.token().get(),
7060                            endpoint,
7061                            silence_suppression: audio_processing.silence_suppression,
7062                            traffic_class,
7063                            encryption: None,
7064                            wire: None,
7065                        },
7066                        protocol,
7067                    )
7068                    .await?;
7069                }
7070                CommandAction::StartMulticastReception {
7071                    conference_id,
7072                    call_id,
7073                    route,
7074                    echo_cancellation,
7075                    g723_bitrate,
7076                } => {
7077                    validate_multicast_route(state, route, None)?;
7078                    let wire_call_reference = require_call(state, call_id)?.wire_reference;
7079                    let request = allocate_multicast_request_identity(state)?;
7080                    let key = MulticastKey {
7081                        conference_id,
7082                        call_id,
7083                    };
7084                    if let Some(stop) = take_multicast_stop(state, key, true) {
7085                        send_message(stream, &stop, protocol).await?;
7086                    }
7087                    send_message(
7088                        stream,
7089                        &ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
7090                            conference_id,
7091                            passthrough_party_id: request.token().get().into(),
7092                            call_reference: CallReference::new(wire_call_reference),
7093                            address: route.address,
7094                            port: route.port,
7095                            packet_millis: route.packet_millis,
7096                            codec: route.codec,
7097                            echo_cancellation,
7098                            g723_bitrate,
7099                        }),
7100                        protocol,
7101                    )
7102                    .await?;
7103                    state
7104                        .multicast
7105                        .entry(key)
7106                        .or_insert_with(|| MulticastSession {
7107                            wire_call_reference,
7108                            receive: None,
7109                            transmit: None,
7110                        })
7111                        .receive = Some(MulticastReceive {
7112                        request,
7113                        route,
7114                        state: MulticastReceiveState::AwaitingAcknowledgement {
7115                            deadline: Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT,
7116                        },
7117                    });
7118                }
7119                CommandAction::StopMulticastReception {
7120                    conference_id,
7121                    call_id,
7122                } => {
7123                    let key = MulticastKey {
7124                        conference_id,
7125                        call_id,
7126                    };
7127                    if let Some(stop) = take_multicast_stop(state, key, true) {
7128                        send_message(stream, &stop, protocol).await?;
7129                    }
7130                }
7131                CommandAction::StartMulticastTransmission {
7132                    conference_id,
7133                    call_id,
7134                    route,
7135                    precedence,
7136                    silence_suppression,
7137                    max_frames_per_packet,
7138                    g723_bitrate,
7139                } => {
7140                    validate_multicast_route(state, route, Some(max_frames_per_packet))?;
7141                    let wire_call_reference = require_call(state, call_id)?.wire_reference;
7142                    let request = allocate_multicast_request_identity(state)?;
7143                    let key = MulticastKey {
7144                        conference_id,
7145                        call_id,
7146                    };
7147                    if let Some(stop) = take_multicast_stop(state, key, false) {
7148                        send_message(stream, &stop, protocol).await?;
7149                    }
7150                    send_message(
7151                        stream,
7152                        &ServerMessage::StartMulticastMediaTransmission(
7153                            MulticastMediaTransmission {
7154                                conference_id,
7155                                passthrough_party_id: request.token().get().into(),
7156                                call_reference: CallReference::new(wire_call_reference),
7157                                address: route.address,
7158                                port: route.port,
7159                                packet_millis: route.packet_millis,
7160                                codec: route.codec,
7161                                precedence,
7162                                silence_suppression: silence_suppression.wire_value(),
7163                                max_frames_per_packet,
7164                                g723_bitrate,
7165                            },
7166                        ),
7167                        protocol,
7168                    )
7169                    .await?;
7170                    state
7171                        .multicast
7172                        .entry(key)
7173                        .or_insert_with(|| MulticastSession {
7174                            wire_call_reference,
7175                            receive: None,
7176                            transmit: None,
7177                        })
7178                        .transmit = Some(MulticastTransmit { request, route });
7179                    context
7180                        .event_tx
7181                        .send(Event::device(
7182                            state.device.id.clone(),
7183                            state.generation,
7184                            DeviceEventKind::MulticastTransmissionStarted {
7185                                conference_id,
7186                                call_id,
7187                                route,
7188                            },
7189                        ))
7190                        .await
7191                        .map_err(|_| ServerError::Stopped)?;
7192                }
7193                CommandAction::StopMulticastTransmission {
7194                    conference_id,
7195                    call_id,
7196                } => {
7197                    let key = MulticastKey {
7198                        conference_id,
7199                        call_id,
7200                    };
7201                    if let Some(stop) = take_multicast_stop(state, key, false) {
7202                        send_message(stream, &stop, protocol).await?;
7203                    }
7204                }
7205                CommandAction::StopMedia { call_id, .. } => {
7206                    if let Some(call) = state
7207                        .calls_by_id
7208                        .get_mut(&call_id)
7209                        .filter(|call| call.media.transmit.state != MediaChannelState::Closed)
7210                    {
7211                        call.media.transmit.state = MediaChannelState::Closed;
7212                        call.media.transmit.deadline = None;
7213                        call.media.coupled_transmit_endpoint = None;
7214                        let call = call.clone();
7215                        send_message(
7216                            stream,
7217                            &ServerMessage::StopMediaTransmission(AudioStreamControl {
7218                                conference_id: ConferenceId::new(call.wire_reference),
7219                                call_reference: CallReference::new(call.wire_reference),
7220                                passthrough_party_id: media_request_party_id(
7221                                    call.media.transmit.request,
7222                                    call.wire_reference,
7223                                )
7224                                .into(),
7225                                port_handling_flag: 0,
7226                            }),
7227                            protocol,
7228                        )
7229                        .await?;
7230                    }
7231                }
7232                CommandAction::CloseCall { call_id, .. } => {
7233                    if let Some(call) = state.calls_by_id.get(&call_id).cloned() {
7234                        state.active_key_mode = KeyMode::OnHook;
7235                        stop_call_multicast(stream, state, call_id, protocol).await?;
7236                        if call.state != CallState::OnHook {
7237                            close_call_media_messages(stream, &call, protocol).await?;
7238                            close_call_messages(
7239                                stream,
7240                                &call,
7241                                &state.device.soft_keys,
7242                                protocol,
7243                                context.config.timezone_offset_minutes,
7244                            )
7245                            .await?;
7246                            request_connection_statistics(stream, state, &call, context).await?;
7247                        }
7248                        remove_call(state, call_id);
7249                        refresh_mwi_lamps(stream, state, protocol).await?;
7250                    } else {
7251                        state.cancelled_calls.insert(call_id);
7252                    }
7253                }
7254            }
7255        }
7256    }
7257    Ok(false)
7258}
7259
7260async fn send_mwi_lamp(
7261    stream: &mut dyn StationIo,
7262    state: &SessionState,
7263    line_instance: u32,
7264    enabled: bool,
7265    protocol: ProtocolVersion,
7266) -> Result<(), ServerError> {
7267    let mode = projected_mwi_lamp(state.device.ui, state.active_call_id.is_some(), enabled);
7268    send_message(
7269        stream,
7270        &ServerMessage::SetLamp {
7271            stimulus: ButtonType::Voicemail,
7272            instance: line_instance,
7273            mode,
7274        },
7275        protocol,
7276    )
7277    .await
7278}
7279
7280fn projected_mwi_lamp(ui: crate::types::StationUiPolicy, on_call: bool, enabled: bool) -> LampMode {
7281    if enabled && (ui.mwi_on_call || !on_call) {
7282        ui.mwi_lamp_mode
7283    } else {
7284        LampMode::Off
7285    }
7286}
7287
7288fn updated_history_disposition(
7289    current: CallHistoryDisposition,
7290    state: CallState,
7291) -> CallHistoryDisposition {
7292    if current != CallHistoryDisposition::Missed {
7293        return current;
7294    }
7295    match state {
7296        CallState::Connected => CallHistoryDisposition::Received,
7297        CallState::RemoteMultiline => CallHistoryDisposition::Ignore,
7298        _ => current,
7299    }
7300}
7301
7302async fn refresh_mwi_lamps(
7303    stream: &mut dyn StationIo,
7304    state: &SessionState,
7305    protocol: ProtocolVersion,
7306) -> Result<(), ServerError> {
7307    for (&line_instance, &enabled) in &state.mwi_by_line {
7308        send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
7309    }
7310    Ok(())
7311}
7312
7313fn incoming_ringer(
7314    ringer: Option<IncomingRing>,
7315    incoming_state: CallState,
7316) -> Option<IncomingRing> {
7317    ringer.map(|mut ringer| {
7318        if incoming_state == CallState::CallWaiting {
7319            ringer.duration = RingDuration::Single;
7320            if ringer.mode != RingerMode::Urgent {
7321                ringer.mode = RingerMode::Silent;
7322            }
7323        }
7324        ringer
7325    })
7326}
7327
7328async fn request_connection_statistics(
7329    stream: &mut dyn StationIo,
7330    state: &mut SessionState,
7331    call: &SessionCall,
7332    context: &SessionContext,
7333) -> Result<(), ServerError> {
7334    prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
7335    if !call.media.requested
7336        || state.pending_connection_statistics.len() >= MAX_PENDING_CONNECTION_STATISTICS
7337        || state.statistics_references.len() >= MAX_STATISTICS_REFERENCES_PER_SESSION
7338    {
7339        return Ok(());
7340    }
7341    let directory_number = if call.statistics_directory_number.is_empty() {
7342        call.dialed_number.trim()
7343    } else {
7344        call.statistics_directory_number.trim()
7345    };
7346    let maximum = if state.registration.protocol >= ProtocolVersion::V19 {
7347        24
7348    } else {
7349        23
7350    };
7351    if directory_number.is_empty()
7352        || directory_number.len() > maximum
7353        || directory_number.contains(['\0', '\r', '\n'])
7354    {
7355        warn!(
7356            device_id = %state.device.id,
7357            ?call.call_id,
7358            byte_count = directory_number.len(),
7359            "skipping connection-statistics request with unusable directory number"
7360        );
7361        return Ok(());
7362    }
7363    if !state.statistics_references.insert(call.wire_reference) {
7364        warn!(
7365            device_id = %state.device.id,
7366            ?call.call_id,
7367            call_reference = call.wire_reference,
7368            "skipping connection-statistics request for a reused call reference"
7369        );
7370        return Ok(());
7371    }
7372    let request_generation = context
7373        .next_statistics_generation
7374        .fetch_add(1, Ordering::Relaxed);
7375    let processing = StatisticsProcessing::Clear;
7376    state.pending_connection_statistics.insert(
7377        call.wire_reference,
7378        PendingConnectionStatistics {
7379            session_generation: state.generation,
7380            request_generation,
7381            call_id: call.call_id,
7382            line_instance: call.line_instance,
7383            codec: call.media.codec,
7384            packet_ms: call.media.packet_ms,
7385            max_frames_per_packet: call.media.max_frames_per_packet,
7386            receive_peer: call.media.receive.peer,
7387            transmit_peer: call.media.transmit.peer,
7388            directory_number: directory_number.to_owned(),
7389            processing,
7390            expires_at: Instant::now() + CONNECTION_STATISTICS_TIMEOUT,
7391        },
7392    );
7393    send_message(
7394        stream,
7395        &ServerMessage::ConnectionStatisticsRequest {
7396            directory_number: directory_number.to_owned(),
7397            call_reference: call.wire_reference,
7398            processing,
7399        },
7400        state.registration.protocol,
7401    )
7402    .await
7403}
7404
7405fn statistics_directory_for_call_info(info: &CallInfo) -> &str {
7406    match info.direction {
7407        crate::types::CallDirection::Inbound => &info.calling_number,
7408        crate::types::CallDirection::Outbound => &info.called_number,
7409    }
7410}
7411
7412fn prune_connection_statistics(
7413    pending_statistics: &mut HashMap<u32, PendingConnectionStatistics>,
7414    now: Instant,
7415) {
7416    pending_statistics.retain(|_, pending| pending.expires_at > now);
7417}
7418
7419async fn collect_connection_statistics(
7420    state: &mut SessionState,
7421    statistics: ConnectionStatistics,
7422    context: &SessionContext,
7423) -> Result<(), ServerError> {
7424    prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
7425    let Some(pending) = state
7426        .pending_connection_statistics
7427        .get(&statistics.call_reference)
7428        .cloned()
7429    else {
7430        warn!(
7431            device_id = %state.device.id,
7432            call_reference = statistics.call_reference,
7433            "ignoring unsolicited or expired connection-statistics response"
7434        );
7435        return Ok(());
7436    };
7437    let current_session = context
7438        .sessions
7439        .lock()
7440        .await
7441        .get(&state.device.id)
7442        .is_some_and(|session| session.generation == pending.session_generation);
7443    if !current_session
7444        || pending.session_generation != state.generation
7445        || statistics.processing != pending.processing
7446        || statistics.directory_number != pending.directory_number
7447    {
7448        warn!(
7449            device_id = %state.device.id,
7450            call_reference = statistics.call_reference,
7451            processing = ?statistics.processing,
7452            "ignoring mismatched connection-statistics response"
7453        );
7454        return Ok(());
7455    }
7456    state
7457        .pending_connection_statistics
7458        .remove(&statistics.call_reference);
7459    let snapshot = MediaStatisticsSnapshot {
7460        request_generation: pending.request_generation,
7461        call_id: pending.call_id,
7462        line_instance: LineInstance::new(pending.line_instance),
7463        codec: pending.codec,
7464        packet_ms: pending.packet_ms,
7465        max_frames_per_packet: pending.max_frames_per_packet,
7466        receive_peer: pending.receive_peer,
7467        transmit_peer: pending.transmit_peer,
7468        packets_sent: statistics.packets_sent,
7469        octets_sent: statistics.octets_sent,
7470        packets_received: statistics.packets_received,
7471        octets_received: statistics.octets_received,
7472        packets_lost: statistics.packets_lost,
7473        jitter_millis: statistics.jitter_millis,
7474        latency_millis: statistics.latency_millis,
7475        quality_byte_count: statistics.quality.as_bytes().len(),
7476    };
7477    {
7478        let mut latest = context
7479            .latest_media_statistics
7480            .write()
7481            .expect("SCCP media-statistics lock poisoned");
7482        let replace = latest
7483            .get(&state.device.id)
7484            .is_none_or(|existing| existing.request_generation < snapshot.request_generation);
7485        if !replace {
7486            return Ok(());
7487        }
7488        latest.insert(state.device.id.clone(), snapshot.clone());
7489    }
7490    context
7491        .event_tx
7492        .send(Event::device(
7493            state.device.id.clone(),
7494            state.generation,
7495            DeviceEventKind::ConnectionStatisticsCollected { snapshot },
7496        ))
7497        .await
7498        .map_err(|_| ServerError::Stopped)
7499}
7500
7501fn status_message_frames(
7502    message: HandsetStatusMessage,
7503    device_type: DeviceType,
7504    persistent: &mut bool,
7505) -> Vec<ServerMessage> {
7506    let prompt_for_timed_message = matches!(
7507        device_type,
7508        DeviceType::Cisco6901
7509            | DeviceType::Cisco6921
7510            | DeviceType::Cisco6941
7511            | DeviceType::Cisco6945
7512            | DeviceType::Cisco6961
7513    );
7514    match message {
7515        HandsetStatusMessage::Display {
7516            text,
7517            timeout_seconds,
7518            priority: Some(priority),
7519        } => vec![ServerMessage::DisplayPriorityNotify {
7520            timeout_seconds: u32::from(timeout_seconds),
7521            priority,
7522            text,
7523        }],
7524        HandsetStatusMessage::Clear {
7525            priority: Some(priority),
7526        } => vec![ServerMessage::ClearPriorityNotify { priority }],
7527        HandsetStatusMessage::Display {
7528            text,
7529            timeout_seconds,
7530            priority: None,
7531        } if timeout_seconds == 0 || prompt_for_timed_message => {
7532            if timeout_seconds == 0 {
7533                *persistent = true;
7534            }
7535            vec![ServerMessage::DisplayPrompt {
7536                timeout_seconds: u32::from(timeout_seconds),
7537                text,
7538                line_instance: 0,
7539                call_reference: 0,
7540            }]
7541        }
7542        HandsetStatusMessage::Display {
7543            text,
7544            timeout_seconds,
7545            priority: None,
7546        } => vec![ServerMessage::DisplayPriorityNotify {
7547            timeout_seconds: u32::from(timeout_seconds),
7548            priority: NotificationPriority::Timed,
7549            text,
7550        }],
7551        HandsetStatusMessage::Clear { priority: None } => {
7552            let clear_prompt = std::mem::take(persistent) || prompt_for_timed_message;
7553            let mut frames = Vec::with_capacity(2);
7554            if clear_prompt {
7555                frames.push(ServerMessage::ClearPrompt {
7556                    line_instance: 0,
7557                    call_reference: 0,
7558                });
7559            }
7560            if !prompt_for_timed_message {
7561                frames.push(ServerMessage::ClearPriorityNotify {
7562                    priority: NotificationPriority::Timed,
7563                });
7564            }
7565            frames
7566        }
7567    }
7568}
7569
7570async fn send_message(
7571    stream: &mut dyn StationIo,
7572    message: &ServerMessage,
7573    session: impl Into<StationSessionContext>,
7574) -> Result<(), ServerError> {
7575    stream
7576        .write_all(&message.encode_for_session(session.into())?)
7577        .await?;
7578    Ok(())
7579}
7580
7581async fn send_station_ui_message(
7582    stream: &mut dyn StationIo,
7583    state: &SessionState,
7584    message: &ServerMessage,
7585) -> Result<(), ServerError> {
7586    let session = state.station_context();
7587    let bytes = if state.features.contains(PhoneFeatures::UTF8) {
7588        message.encode_for_session(session)?
7589    } else {
7590        message.encode_for_legacy_session(session, state.device.ui.legacy_code_page)?
7591    };
7592    stream.write_all(&bytes).await?;
7593    Ok(())
7594}
7595
7596async fn begin_phone_call_ui(
7597    stream: &mut dyn StationIo,
7598    call: &SessionCall,
7599    device: &DeviceDefinition,
7600    session: StationSessionContext,
7601) -> Result<(), ServerError> {
7602    begin_phone_call_ui_with_key_mode(stream, call, device, KeyMode::OffHook, session).await
7603}
7604
7605async fn begin_phone_call_ui_with_key_mode(
7606    stream: &mut dyn StationIo,
7607    call: &SessionCall,
7608    device: &DeviceDefinition,
7609    key_mode: KeyMode,
7610    session: StationSessionContext,
7611) -> Result<(), ServerError> {
7612    let initial_tone = device
7613        .line(call.line_instance)
7614        .map_or(Tone::InsideDial, |line| line.initial_tone);
7615    send_message(
7616        stream,
7617        &ServerMessage::SetSpeakerMode(SpeakerMode::On),
7618        session,
7619    )
7620    .await?;
7621    send_message(
7622        stream,
7623        &ServerMessage::SetLamp {
7624            stimulus: ButtonType::Line,
7625            instance: call.line_instance,
7626            mode: LampMode::On,
7627        },
7628        session,
7629    )
7630    .await?;
7631    send_message(
7632        stream,
7633        &ServerMessage::CallState {
7634            state: CallState::OffHook,
7635            line_instance: call.line_instance,
7636            call_reference: call.wire_reference,
7637        },
7638        session,
7639    )
7640    .await?;
7641    send_message(
7642        stream,
7643        &ServerMessage::ActivateCallPlane {
7644            line_instance: call.line_instance,
7645        },
7646        session,
7647    )
7648    .await?;
7649    send_message(
7650        stream,
7651        &ServerMessage::DisplayPrompt {
7652            timeout_seconds: 0,
7653            text: "Enter number".into(),
7654            line_instance: call.line_instance,
7655            call_reference: call.wire_reference,
7656        },
7657        session,
7658    )
7659    .await?;
7660    send_message(
7661        stream,
7662        &ServerMessage::StartTone {
7663            tone: initial_tone,
7664            direction: ToneDirection::User,
7665            line_instance: call.line_instance,
7666            call_reference: call.wire_reference,
7667        },
7668        session,
7669    )
7670    .await?;
7671    send_message(
7672        stream,
7673        &ServerMessage::SelectSoftKeys {
7674            line_instance: call.line_instance,
7675            call_reference: call.wire_reference,
7676            set: key_mode,
7677            valid_mask: device.soft_keys.valid_mask(key_mode),
7678        },
7679        session,
7680    )
7681    .await
7682}
7683
7684async fn begin_answer_ui(
7685    stream: &mut dyn StationIo,
7686    call: &SessionCall,
7687    protocol: ProtocolVersion,
7688) -> Result<(), ServerError> {
7689    send_message(
7690        stream,
7691        &ServerMessage::SetRinger {
7692            mode: RingerMode::Off,
7693            duration: RingDuration::Normal,
7694            line_instance: call.line_instance,
7695            call_reference: call.wire_reference,
7696        },
7697        protocol,
7698    )
7699    .await?;
7700    send_message(
7701        stream,
7702        &ServerMessage::CallState {
7703            state: CallState::OffHook,
7704            line_instance: call.line_instance,
7705            call_reference: call.wire_reference,
7706        },
7707        protocol,
7708    )
7709    .await?;
7710    send_message(
7711        stream,
7712        &ServerMessage::ActivateCallPlane {
7713            line_instance: call.line_instance,
7714        },
7715        protocol,
7716    )
7717    .await?;
7718    send_message(
7719        stream,
7720        &ServerMessage::StopTone {
7721            line_instance: call.line_instance,
7722            call_reference: call.wire_reference,
7723        },
7724        protocol,
7725    )
7726    .await?;
7727    send_message(
7728        stream,
7729        &ServerMessage::SetLamp {
7730            stimulus: ButtonType::Line,
7731            instance: call.line_instance,
7732            mode: LampMode::On,
7733        },
7734        protocol,
7735    )
7736    .await?;
7737    Ok(())
7738}
7739
7740async fn prepare_call_state_ui(
7741    stream: &mut dyn StationIo,
7742    call: &SessionCall,
7743    state: CallState,
7744    protocol: ProtocolVersion,
7745) -> Result<(), ServerError> {
7746    match state {
7747        CallState::Connected => {
7748            send_message(
7749                stream,
7750                &ServerMessage::SetRinger {
7751                    mode: RingerMode::Off,
7752                    duration: RingDuration::Normal,
7753                    line_instance: call.line_instance,
7754                    call_reference: call.wire_reference,
7755                },
7756                protocol,
7757            )
7758            .await?;
7759            send_message(
7760                stream,
7761                &ServerMessage::SetSpeakerMode(SpeakerMode::On),
7762                protocol,
7763            )
7764            .await?;
7765            send_message(
7766                stream,
7767                &ServerMessage::StopTone {
7768                    line_instance: call.line_instance,
7769                    call_reference: call.wire_reference,
7770                },
7771                protocol,
7772            )
7773            .await?;
7774            send_message(
7775                stream,
7776                &ServerMessage::SetLamp {
7777                    stimulus: ButtonType::Line,
7778                    instance: call.line_instance,
7779                    mode: LampMode::On,
7780                },
7781                protocol,
7782            )
7783            .await?;
7784        }
7785        CallState::RemoteMultiline => {
7786            send_message(
7787                stream,
7788                &ServerMessage::SetRinger {
7789                    mode: RingerMode::Off,
7790                    duration: RingDuration::Normal,
7791                    line_instance: call.line_instance,
7792                    call_reference: call.wire_reference,
7793                },
7794                protocol,
7795            )
7796            .await?;
7797            send_message(
7798                stream,
7799                &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
7800                protocol,
7801            )
7802            .await?;
7803            send_message(
7804                stream,
7805                &ServerMessage::SetLamp {
7806                    stimulus: ButtonType::Line,
7807                    instance: call.line_instance,
7808                    mode: LampMode::On,
7809                },
7810                protocol,
7811            )
7812            .await?;
7813        }
7814        CallState::OnHook => {
7815            send_message(
7816                stream,
7817                &ServerMessage::SetRinger {
7818                    mode: RingerMode::Off,
7819                    duration: RingDuration::Normal,
7820                    line_instance: call.line_instance,
7821                    call_reference: call.wire_reference,
7822                },
7823                protocol,
7824            )
7825            .await?;
7826        }
7827        CallState::Hold | CallState::HoldYellow | CallState::HoldRed => {
7828            send_message(
7829                stream,
7830                &ServerMessage::SetLamp {
7831                    stimulus: ButtonType::Line,
7832                    instance: call.line_instance,
7833                    mode: LampMode::Wink,
7834                },
7835                protocol,
7836            )
7837            .await?;
7838        }
7839        CallState::RingOut | CallState::Proceed => {
7840            send_message(
7841                stream,
7842                &ServerMessage::SetLamp {
7843                    stimulus: ButtonType::Line,
7844                    instance: call.line_instance,
7845                    mode: LampMode::Blink,
7846                },
7847                protocol,
7848            )
7849            .await?;
7850        }
7851        _ => {}
7852    }
7853    Ok(())
7854}
7855
7856async fn finish_call_state_ui(
7857    stream: &mut dyn StationIo,
7858    call: &SessionCall,
7859    state: CallState,
7860    session: StationSessionContext,
7861) -> Result<(), ServerError> {
7862    let prompt = match state {
7863        CallState::Connected => Some("Connected"),
7864        CallState::Hold | CallState::HoldYellow | CallState::HoldRed => Some("Hold"),
7865        CallState::RingOut => Some("Ring out"),
7866        CallState::Proceed => Some("Call proceeding"),
7867        CallState::Busy => Some("Busy"),
7868        CallState::Congestion => Some("Network congestion"),
7869        CallState::InvalidNumber => Some("Unknown number"),
7870        _ => None,
7871    };
7872    if state == CallState::Connected {
7873        send_message(
7874            stream,
7875            &ServerMessage::ActivateCallPlane {
7876                line_instance: call.line_instance,
7877            },
7878            session,
7879        )
7880        .await?;
7881    } else if matches!(
7882        state,
7883        CallState::Hold | CallState::HoldYellow | CallState::HoldRed
7884    ) {
7885        send_message(
7886            stream,
7887            &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
7888            session,
7889        )
7890        .await?;
7891    }
7892    if let Some(text) = prompt {
7893        send_message(
7894            stream,
7895            &ServerMessage::DisplayPrompt {
7896                timeout_seconds: 0,
7897                text: text.into(),
7898                line_instance: call.line_instance,
7899                call_reference: call.wire_reference,
7900            },
7901            session,
7902        )
7903        .await?;
7904    }
7905    Ok(())
7906}
7907
7908fn normalize_line(state: &SessionState, requested: u32) -> u32 {
7909    if requested != 0 && state.device.line(requested).is_some() {
7910        requested
7911    } else {
7912        state.device.first_line().map_or(1, |line| line.instance)
7913    }
7914}
7915
7916fn ensure_phone_call(
7917    state: &mut SessionState,
7918    wire_reference: u32,
7919    line_instance: u32,
7920    next: &AtomicU64,
7921) -> SessionCall {
7922    let reusable = if wire_reference == 0 {
7923        state
7924            .calls_by_id
7925            .values()
7926            .filter(|call| call.state != CallState::OnHook)
7927            .max_by_key(|call| call.call_id.0)
7928    } else {
7929        find_call(state, wire_reference).filter(|call| call.state != CallState::OnHook)
7930    };
7931    if let Some(call) = reusable {
7932        return call.clone();
7933    }
7934    let mut call = reserve_phone_call(state, line_instance, next);
7935    if wire_reference != 0
7936        && wire_reference != call.wire_reference
7937        && !state.statistics_references.contains(&wire_reference)
7938    {
7939        state.calls_by_wire.remove(&call.wire_reference);
7940        call.wire_reference = wire_reference;
7941        state.calls_by_wire.insert(wire_reference, call.call_id);
7942        state.calls_by_id.insert(call.call_id, call.clone());
7943    }
7944    call
7945}
7946
7947fn reserve_phone_call(
7948    state: &mut SessionState,
7949    line_instance: u32,
7950    next: &AtomicU64,
7951) -> SessionCall {
7952    let call_id = CallId(next.fetch_add(1, Ordering::Relaxed));
7953    insert_call(
7954        state,
7955        call_id,
7956        line_instance,
7957        Codec::Pcmu,
7958        CallState::OffHook,
7959    )
7960}
7961
7962fn insert_call(
7963    state: &mut SessionState,
7964    call_id: CallId,
7965    line_instance: u32,
7966    codec: Codec,
7967    call_state: CallState,
7968) -> SessionCall {
7969    let mut wire_reference = (call_id.0 as u32).max(1);
7970    while state.calls_by_wire.contains_key(&wire_reference)
7971        || state.statistics_references.contains(&wire_reference)
7972    {
7973        wire_reference = wire_reference.wrapping_add(1).max(1);
7974    }
7975    let call = SessionCall {
7976        call_id,
7977        wire_reference,
7978        line_instance,
7979        media: CallMedia::new(codec),
7980        video_receive: VideoReceive::default(),
7981        video_transmit: VideoTransmit::default(),
7982        state: call_state,
7983        history_disposition: if matches!(call_state, CallState::RingIn | CallState::CallWaiting) {
7984            CallHistoryDisposition::Missed
7985        } else {
7986            CallHistoryDisposition::Placed
7987        },
7988        dialed_number: String::new(),
7989        statistics_directory_number: String::new(),
7990        transfer_role: None,
7991    };
7992    state.calls_by_wire.insert(wire_reference, call_id);
7993    state.calls_by_id.insert(call_id, call.clone());
7994    call
7995}
7996
7997fn find_call(state: &SessionState, wire_reference: u32) -> Option<&SessionCall> {
7998    if wire_reference != 0 {
7999        state
8000            .calls_by_wire
8001            .get(&wire_reference)
8002            .and_then(|id| state.calls_by_id.get(id))
8003    } else {
8004        state
8005            .active_call_id
8006            .and_then(|call_id| state.calls_by_id.get(&call_id))
8007            .or_else(|| {
8008                (state.calls_by_id.len() == 1)
8009                    .then(|| state.calls_by_id.values().next())
8010                    .flatten()
8011            })
8012    }
8013}
8014
8015fn find_answer_call(
8016    state: &SessionState,
8017    wire_reference: u32,
8018    line_instance: u32,
8019    order: CallSelectionOrder,
8020) -> Option<&SessionCall> {
8021    let matches_line = |call: &&SessionCall| {
8022        matches!(call.state, CallState::RingIn | CallState::CallWaiting)
8023            && (line_instance == 0 || call.line_instance == line_instance)
8024    };
8025    if wire_reference != 0 {
8026        return state
8027            .calls_by_wire
8028            .get(&wire_reference)
8029            .and_then(|call_id| state.calls_by_id.get(call_id))
8030            .filter(matches_line);
8031    }
8032    if let Some(active) = state
8033        .active_call_id
8034        .and_then(|call_id| state.calls_by_id.get(&call_id))
8035        .filter(matches_line)
8036    {
8037        return Some(active);
8038    }
8039    let candidates = state.calls_by_id.values().filter(matches_line);
8040    match order {
8041        CallSelectionOrder::OldestFirst => candidates.min_by_key(|call| call.call_id.0),
8042        CallSelectionOrder::LastFirst => candidates.max_by_key(|call| call.call_id.0),
8043    }
8044}
8045
8046fn find_receive_media_call_id(
8047    state: &SessionState,
8048    wire_reference: u32,
8049    passthrough_party_id: u32,
8050) -> Option<CallId> {
8051    find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
8052        call.media.receive.request
8053    })
8054}
8055
8056fn find_multicast_receive_key(
8057    state: &SessionState,
8058    wire_reference: u32,
8059    passthrough_party_id: u32,
8060) -> Option<MulticastKey> {
8061    state.multicast.iter().find_map(|(key, session)| {
8062        session.receive.as_ref().and_then(|receive| {
8063            (matches!(
8064                receive.state,
8065                MulticastReceiveState::AwaitingAcknowledgement { .. }
8066            ) && session.wire_call_reference == wire_reference
8067                && receive.request.token().get() == passthrough_party_id)
8068                .then_some(*key)
8069        })
8070    })
8071}
8072
8073fn find_multicast_transmit_key(
8074    state: &SessionState,
8075    conference_id: u32,
8076    wire_reference: u32,
8077    passthrough_party_id: u32,
8078    address: IpAddr,
8079    port: u16,
8080) -> Option<MulticastKey> {
8081    state.multicast.iter().find_map(|(key, session)| {
8082        session.transmit.as_ref().and_then(|transmit| {
8083            (key.conference_id.get() == conference_id
8084                && session.wire_call_reference == wire_reference
8085                && transmit.request.token().get() == passthrough_party_id
8086                && canonical_ip_address(transmit.route.address) == canonical_ip_address(address)
8087                && transmit.route.port == port)
8088                .then_some(*key)
8089        })
8090    })
8091}
8092
8093fn find_transmit_media_call_id(
8094    state: &SessionState,
8095    conference_id: u32,
8096    wire_reference: u32,
8097    passthrough_party_id: u32,
8098) -> Option<CallId> {
8099    find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
8100        call.media.transmit.request
8101    })
8102    .filter(|call_id| {
8103        state
8104            .calls_by_id
8105            .get(call_id)
8106            .is_some_and(|call| conference_id == 0 || conference_id == call.wire_reference)
8107    })
8108}
8109
8110fn find_media_call_id(
8111    state: &SessionState,
8112    wire_reference: u32,
8113    passthrough_party_id: u32,
8114    request: impl Fn(&SessionCall) -> Option<MediaRequestIdentity>,
8115) -> Option<CallId> {
8116    state
8117        .calls_by_id
8118        .values()
8119        .find(|call| {
8120            request(call).is_some_and(|identity| {
8121                identity.accepts_ack(passthrough_party_id, wire_reference, call.wire_reference)
8122            })
8123        })
8124        .map(|call| call.call_id)
8125}
8126
8127fn require_call(state: &SessionState, call_id: CallId) -> Result<&SessionCall, ServerError> {
8128    state
8129        .calls_by_id
8130        .get(&call_id)
8131        .ok_or(ServerError::UnknownCall(call_id))
8132}
8133
8134fn require_call_mut(
8135    state: &mut SessionState,
8136    call_id: CallId,
8137) -> Result<&mut SessionCall, ServerError> {
8138    state
8139        .calls_by_id
8140        .get_mut(&call_id)
8141        .ok_or(ServerError::UnknownCall(call_id))
8142}
8143
8144fn address_matches_type(address: IpAddr, requested: IpAddressType) -> bool {
8145    match requested {
8146        IpAddressType::Ipv4 => address.is_ipv4(),
8147        IpAddressType::Ipv6 => address.is_ipv6(),
8148        IpAddressType::Ipv4AndIpv6 => true,
8149        IpAddressType::Invalid | IpAddressType::Unknown(_) => false,
8150    }
8151}
8152
8153fn address_type(address: IpAddr) -> IpAddressType {
8154    if address.is_ipv4() {
8155        IpAddressType::Ipv4
8156    } else {
8157        IpAddressType::Ipv6
8158    }
8159}
8160
8161fn endpoint_is_usable(endpoint: MediaEndpointAddress) -> bool {
8162    endpoint.port != 0 && !endpoint.address.is_unspecified() && !endpoint.address.is_multicast()
8163}
8164
8165fn capability_supports_address(
8166    advertised: Option<IpAddressType>,
8167    requested: IpAddressType,
8168) -> bool {
8169    match advertised {
8170        None => requested == IpAddressType::Ipv4,
8171        Some(IpAddressType::Ipv4AndIpv6) => true,
8172        Some(address_type) => address_type == requested,
8173    }
8174}
8175
8176fn validate_multimedia_receive_descriptor(
8177    descriptor: &MultimediaReceiveDescriptor,
8178) -> Result<(), ServerError> {
8179    if !descriptor
8180        .payload
8181        .is_direction(MultimediaPayloadDirection::Receive)
8182    {
8183        return Err(ServerError::InvalidMultimediaReceive(
8184            "payload was not decoded from a receive message",
8185        ));
8186    }
8187    if descriptor.payload.codec().kind() != CodecKind::Video {
8188        return Err(ServerError::InvalidMultimediaReceive("codec is not video"));
8189    }
8190    if !address_matches_type(descriptor.source.address, descriptor.requested_address_type) {
8191        return Err(ServerError::InvalidMultimediaReceive(
8192            "source address does not match the requested address type",
8193        ));
8194    }
8195    if descriptor.source.address.is_multicast() {
8196        return Err(ServerError::InvalidMultimediaReceive(
8197            "source address must not be multicast",
8198        ));
8199    }
8200    Ok(())
8201}
8202
8203fn validate_multimedia_receive(
8204    state: &SessionState,
8205    descriptor: &MultimediaReceiveDescriptor,
8206) -> Result<(), ServerError> {
8207    validate_multimedia_receive_descriptor(descriptor)?;
8208
8209    if !descriptor.payload.is_valid_for(
8210        MultimediaPayloadDirection::Receive,
8211        state.registration.protocol,
8212    ) {
8213        return Err(ServerError::InvalidMultimediaReceive(
8214            "payload protocol does not match the live session",
8215        ));
8216    }
8217
8218    match state.registration.protocol {
8219        protocol if protocol < ProtocolVersion::V12 => {
8220            if descriptor.source
8221                != (MediaEndpointAddress {
8222                    address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
8223                    port: 0,
8224                })
8225                || descriptor.requested_address_type != IpAddressType::Ipv4
8226            {
8227                return Err(ServerError::InvalidMultimediaReceive(
8228                    "this protocol version cannot carry a source endpoint",
8229                ));
8230            }
8231        }
8232        protocol
8233            if protocol < ProtocolVersion::V17
8234                && (!descriptor.source.address.is_ipv4()
8235                    || descriptor.requested_address_type != IpAddressType::Ipv4) =>
8236        {
8237            return Err(ServerError::InvalidMultimediaReceive(
8238                "this protocol version carries only IPv4 video endpoints",
8239            ));
8240        }
8241        _ => {}
8242    }
8243
8244    let supported = state.media_capabilities.video().iter().any(|capability| {
8245        let encryption_supported = descriptor.encryption.is_none()
8246            || capability.encryption_capability == Some(EncryptionCapability::Capable);
8247        capability.codec == descriptor.payload.codec()
8248            && capability.direction.contains(ReceiveTransmit::RECEIVE)
8249            && capability_supports_address(
8250                capability.address_type,
8251                descriptor.requested_address_type,
8252            )
8253            && encryption_supported
8254    });
8255    supported
8256        .then_some(())
8257        .ok_or(ServerError::UnsupportedMultimediaReceive)
8258}
8259
8260fn validate_multimedia_transmit_descriptor(
8261    descriptor: &MultimediaTransmitDescriptor,
8262) -> Result<(), ServerError> {
8263    if !descriptor
8264        .payload
8265        .is_direction(MultimediaPayloadDirection::Transmit)
8266    {
8267        return Err(ServerError::InvalidMultimediaTransmit(
8268            "payload was not decoded from a transmit message",
8269        ));
8270    }
8271    if descriptor.payload.codec().kind() != CodecKind::Video {
8272        return Err(ServerError::InvalidMultimediaTransmit("codec is not video"));
8273    }
8274    if !endpoint_is_usable(descriptor.endpoint) {
8275        return Err(ServerError::InvalidMultimediaTransmit(
8276            "destination endpoint must be unicast and nonzero",
8277        ));
8278    }
8279    Ok(())
8280}
8281
8282fn validate_multimedia_transmit(
8283    state: &SessionState,
8284    descriptor: &MultimediaTransmitDescriptor,
8285) -> Result<(), ServerError> {
8286    validate_multimedia_transmit_descriptor(descriptor)?;
8287    if !descriptor.payload.is_valid_for(
8288        MultimediaPayloadDirection::Transmit,
8289        state.registration.protocol,
8290    ) {
8291        return Err(ServerError::InvalidMultimediaTransmit(
8292            "payload protocol does not match the live session",
8293        ));
8294    }
8295    if state.registration.protocol < ProtocolVersion::V17 && descriptor.endpoint.address.is_ipv6() {
8296        return Err(ServerError::InvalidMultimediaTransmit(
8297            "this protocol version carries only IPv4 video endpoints",
8298        ));
8299    }
8300    let requested_address = address_type(descriptor.endpoint.address);
8301    let supported = state.media_capabilities.video().iter().any(|capability| {
8302        let encryption_supported = descriptor.encryption.is_none()
8303            || capability.encryption_capability == Some(EncryptionCapability::Capable);
8304        capability.codec == descriptor.payload.codec()
8305            && capability.direction.contains(ReceiveTransmit::TRANSMIT)
8306            && capability_supports_address(capability.address_type, requested_address)
8307            && encryption_supported
8308    });
8309    supported
8310        .then_some(())
8311        .ok_or(ServerError::UnsupportedMultimediaTransmit)
8312}
8313
8314fn allocate_video_receive_identity(
8315    state: &mut SessionState,
8316    call_id: CallId,
8317) -> Result<MediaRequestIdentity, ServerError> {
8318    let generation = require_call(state, call_id)?
8319        .video_receive
8320        .generation
8321        .checked_add(1)
8322        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8323    let token = state
8324        .next_media_token
8325        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8326    let request = MediaRequestIdentity::new(generation, token)
8327        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8328    state.next_media_token = token.checked_next();
8329    require_call_mut(state, call_id)?.video_receive.generation = generation;
8330    Ok(request)
8331}
8332
8333fn multimedia_receive_close_message(call: &SessionCall, leg: &VideoReceiveLeg) -> ServerMessage {
8334    ServerMessage::CloseMultimediaReceiveChannel(MultimediaStreamControl {
8335        conference_id: leg.conference_id,
8336        passthrough_party_id: leg.request.token().get().into(),
8337        call_reference: CallReference::new(call.wire_reference),
8338        port_handling_flag: 0,
8339    })
8340}
8341
8342fn take_multimedia_receive_close(
8343    state: &mut SessionState,
8344    call_id: CallId,
8345) -> Option<ServerMessage> {
8346    let call = state.calls_by_id.get_mut(&call_id)?;
8347    let leg = call.video_receive.leg.take()?;
8348    Some(multimedia_receive_close_message(call, &leg))
8349}
8350
8351fn take_all_multimedia_receive_closes(state: &mut SessionState) -> Vec<ServerMessage> {
8352    let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
8353    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8354    call_ids
8355        .into_iter()
8356        .filter_map(|call_id| take_multimedia_receive_close(state, call_id))
8357        .collect()
8358}
8359
8360fn expire_multimedia_receive_acknowledgements(
8361    state: &mut SessionState,
8362    now: Instant,
8363) -> Vec<ExpiredVideoReceive> {
8364    let mut call_ids = state
8365        .calls_by_id
8366        .iter()
8367        .filter_map(|(&call_id, call)| {
8368            call.video_receive.leg.as_ref().and_then(|leg| {
8369                (leg.state == MediaChannelState::Opening
8370                    && leg.deadline.is_some_and(|deadline| deadline <= now))
8371                .then_some(call_id)
8372            })
8373        })
8374        .collect::<Vec<_>>();
8375    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8376    call_ids
8377        .into_iter()
8378        .filter_map(|call_id| {
8379            let leg = state
8380                .calls_by_id
8381                .get(&call_id)?
8382                .video_receive
8383                .leg
8384                .as_ref()?;
8385            let codec = leg.codec;
8386            let passthrough_party_id = leg.request.token().get().into();
8387            take_multimedia_receive_close(state, call_id).map(|close| ExpiredVideoReceive {
8388                call_id,
8389                codec,
8390                passthrough_party_id,
8391                close,
8392            })
8393        })
8394        .collect()
8395}
8396
8397fn allocate_video_transmit_identity(
8398    state: &mut SessionState,
8399    call_id: CallId,
8400) -> Result<MediaRequestIdentity, ServerError> {
8401    let generation = require_call(state, call_id)?
8402        .video_transmit
8403        .generation
8404        .checked_add(1)
8405        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8406    let token = state
8407        .next_media_token
8408        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8409    let request = MediaRequestIdentity::new(generation, token)
8410        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8411    state.next_media_token = token.checked_next();
8412    require_call_mut(state, call_id)?.video_transmit.generation = generation;
8413    Ok(request)
8414}
8415
8416fn multimedia_transmit_control_identity(
8417    state: &SessionState,
8418    call_id: CallId,
8419    passthrough_party_id: PassthroughPartyId,
8420) -> Result<(ConferenceId, CallReference), ServerError> {
8421    let call = require_call(state, call_id)?;
8422    if call.state != CallState::Connected {
8423        return Err(ServerError::InvalidCallTransaction {
8424            call_id,
8425            operation: "control video transmit media",
8426            state: call.state,
8427        });
8428    }
8429    let leg = call
8430        .video_transmit
8431        .leg
8432        .as_ref()
8433        .filter(|leg| {
8434            leg.state == MediaChannelState::Open
8435                && leg.request.token().get() == passthrough_party_id.get()
8436        })
8437        .ok_or(ServerError::StaleMultimediaTransmitControl {
8438            call_id,
8439            passthrough_party_id,
8440        })?;
8441    Ok((leg.conference_id, CallReference::new(call.wire_reference)))
8442}
8443
8444fn encode_multimedia_transmit_control(
8445    control: MultimediaTransmitControl,
8446) -> Result<(MiscCommandType, BoundedBytes<36>), ServerError> {
8447    let (command, words) = match control {
8448        MultimediaTransmitControl::FreezePicture => {
8449            (MiscCommandType::VideoFreezePicture, Vec::new())
8450        }
8451        MultimediaTransmitControl::FastPictureUpdate {
8452            first_gob,
8453            gob_count,
8454        } => (
8455            MiscCommandType::VideoFastUpdatePicture,
8456            vec![first_gob, gob_count],
8457        ),
8458        MultimediaTransmitControl::FastGobUpdate {
8459            first_gob,
8460            gob_count,
8461        } => (
8462            MiscCommandType::VideoFastUpdateGob,
8463            vec![first_gob, gob_count],
8464        ),
8465        MultimediaTransmitControl::FastMacroblockUpdate {
8466            first_gob,
8467            first_macroblock,
8468            macroblock_count,
8469        } => (
8470            MiscCommandType::VideoFastUpdateMacroblock,
8471            vec![first_gob, first_macroblock, macroblock_count],
8472        ),
8473        MultimediaTransmitControl::LostPicture {
8474            picture_number,
8475            long_term_picture_index,
8476        } => (
8477            MiscCommandType::LostPicture,
8478            vec![picture_number, long_term_picture_index],
8479        ),
8480        MultimediaTransmitControl::LostPartialPicture {
8481            picture_number,
8482            long_term_picture_index,
8483            first_macroblock,
8484            macroblock_count,
8485        } => (
8486            MiscCommandType::LostPartialPicture,
8487            vec![
8488                picture_number,
8489                long_term_picture_index,
8490                first_macroblock,
8491                macroblock_count,
8492            ],
8493        ),
8494        MultimediaTransmitControl::RecoveryReferencePicture { pictures } => {
8495            let words =
8496                std::iter::once(pictures.as_slice().len() as u32)
8497                    .chain(pictures.as_slice().iter().flat_map(|picture| {
8498                        [picture.picture_number, picture.long_term_picture_index]
8499                    }))
8500                    .collect();
8501            (MiscCommandType::RecoveryReferencePicture, words)
8502        }
8503        MultimediaTransmitControl::TemporalSpatialTradeoff { value } => {
8504            (MiscCommandType::TemporalSpatialTradeoff, vec![value])
8505        }
8506    };
8507    let data = words
8508        .into_iter()
8509        .flat_map(u32::to_le_bytes)
8510        .collect::<Vec<_>>();
8511    let data = BoundedBytes::new(data.into_boxed_slice()).map_err(|_| {
8512        ServerError::InvalidMultimediaTransmitControl("parameter area exceeds 36 bytes")
8513    })?;
8514    Ok((command, data))
8515}
8516
8517fn multimedia_transmit_stop_message(call: &SessionCall, leg: &VideoTransmitLeg) -> ServerMessage {
8518    ServerMessage::StopMultimediaTransmission(MultimediaStreamControl {
8519        conference_id: leg.conference_id,
8520        passthrough_party_id: leg.request.token().get().into(),
8521        call_reference: CallReference::new(call.wire_reference),
8522        port_handling_flag: 0,
8523    })
8524}
8525
8526fn take_multimedia_transmit_stop(
8527    state: &mut SessionState,
8528    call_id: CallId,
8529) -> Option<ServerMessage> {
8530    let call = state.calls_by_id.get_mut(&call_id)?;
8531    let leg = call.video_transmit.leg.take()?;
8532    Some(multimedia_transmit_stop_message(call, &leg))
8533}
8534
8535fn take_all_multimedia_transmit_stops(state: &mut SessionState) -> Vec<ServerMessage> {
8536    let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
8537    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8538    call_ids
8539        .into_iter()
8540        .filter_map(|call_id| take_multimedia_transmit_stop(state, call_id))
8541        .collect()
8542}
8543
8544fn expire_multimedia_transmit_acknowledgements(
8545    state: &mut SessionState,
8546    now: Instant,
8547) -> Vec<ExpiredVideoTransmit> {
8548    let mut call_ids = state
8549        .calls_by_id
8550        .iter()
8551        .filter_map(|(&call_id, call)| {
8552            call.video_transmit.leg.as_ref().and_then(|leg| {
8553                (leg.state == MediaChannelState::Opening
8554                    && leg.deadline.is_some_and(|deadline| deadline <= now))
8555                .then_some(call_id)
8556            })
8557        })
8558        .collect::<Vec<_>>();
8559    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8560    call_ids
8561        .into_iter()
8562        .filter_map(|call_id| {
8563            let leg = state
8564                .calls_by_id
8565                .get(&call_id)?
8566                .video_transmit
8567                .leg
8568                .as_ref()?;
8569            let codec = leg.codec;
8570            let passthrough_party_id = leg.request.token().get().into();
8571            take_multimedia_transmit_stop(state, call_id).map(|stop| ExpiredVideoTransmit {
8572                call_id,
8573                codec,
8574                passthrough_party_id,
8575                stop,
8576            })
8577        })
8578        .collect()
8579}
8580
8581fn validate_multicast_route(
8582    state: &SessionState,
8583    route: MulticastMediaRoute,
8584    max_frames_per_packet: Option<u32>,
8585) -> Result<(), ServerError> {
8586    if !route.address.is_multicast() {
8587        return Err(ServerError::InvalidMulticastMedia(
8588            "address must be multicast",
8589        ));
8590    }
8591    if route.address.is_ipv6() && state.registration.protocol < ProtocolVersion::V17 {
8592        return Err(ServerError::InvalidMulticastMedia(
8593            "IPv6 requires protocol v17 or later",
8594        ));
8595    }
8596    if route.port == 0 {
8597        return Err(ServerError::InvalidMulticastMedia("port must be nonzero"));
8598    }
8599    if route.packet_millis == 0 {
8600        return Err(ServerError::InvalidMulticastMedia(
8601            "packet duration must be nonzero",
8602        ));
8603    }
8604    if route.codec.kind() != CodecKind::Audio {
8605        return Err(ServerError::UnsupportedMulticastCodec);
8606    }
8607    let capability = state
8608        .media_capabilities
8609        .audio()
8610        .iter()
8611        .find(|capability| capability.codec == route.codec)
8612        .filter(|capability| capability.max_frames_per_packet != 0)
8613        .ok_or(ServerError::UnsupportedMulticastCodec)?;
8614    if let Some(requested) = max_frames_per_packet
8615        && (requested == 0 || requested > capability.max_frames_per_packet)
8616    {
8617        return Err(ServerError::InvalidMulticastMedia(
8618            "packet framing exceeds the advertised capability",
8619        ));
8620    }
8621    Ok(())
8622}
8623
8624fn allocate_multicast_request_identity(
8625    state: &mut SessionState,
8626) -> Result<MediaRequestIdentity, ServerError> {
8627    let generation = state
8628        .next_multicast_generation
8629        .checked_add(1)
8630        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8631    let token = state
8632        .next_media_token
8633        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8634    let identity = MediaRequestIdentity::new(generation, token)
8635        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8636    state.next_multicast_generation = generation;
8637    state.next_media_token = token.checked_next();
8638    Ok(identity)
8639}
8640
8641fn multicast_stop_message(
8642    key: MulticastKey,
8643    wire_call_reference: u32,
8644    request: MediaRequestIdentity,
8645    receive: bool,
8646) -> ServerMessage {
8647    if receive {
8648        ServerMessage::StopMulticastMediaReception {
8649            conference_id: key.conference_id,
8650            passthrough_party_id: request.token().get().into(),
8651            call_reference: CallReference::new(wire_call_reference),
8652        }
8653    } else {
8654        ServerMessage::StopMulticastMediaTransmission {
8655            conference_id: key.conference_id,
8656            passthrough_party_id: request.token().get().into(),
8657            call_reference: CallReference::new(wire_call_reference),
8658        }
8659    }
8660}
8661
8662fn take_multicast_stop(
8663    state: &mut SessionState,
8664    key: MulticastKey,
8665    receive: bool,
8666) -> Option<ServerMessage> {
8667    let session = state.multicast.get_mut(&key)?;
8668    let request = if receive {
8669        session.receive.take().map(|leg| leg.request)
8670    } else {
8671        session.transmit.take().map(|leg| leg.request)
8672    }?;
8673    let message = multicast_stop_message(key, session.wire_call_reference, request, receive);
8674    if session.receive.is_none() && session.transmit.is_none() {
8675        state.multicast.remove(&key);
8676    }
8677    Some(message)
8678}
8679
8680fn expire_multicast_reception_acknowledgements(
8681    state: &mut SessionState,
8682    now: Instant,
8683) -> Vec<(MulticastKey, ServerMessage)> {
8684    let mut expired = state
8685        .multicast
8686        .iter()
8687        .filter_map(|(key, session)| {
8688            session.receive.as_ref().and_then(|receive| {
8689                matches!(
8690                    receive.state,
8691                    MulticastReceiveState::AwaitingAcknowledgement { deadline }
8692                        if deadline <= now
8693                )
8694                .then_some(*key)
8695            })
8696        })
8697        .collect::<Vec<_>>();
8698    expired.sort_unstable_by_key(|key| (key.conference_id.get(), key.call_id.get()));
8699    expired
8700        .into_iter()
8701        .filter_map(|key| take_multicast_stop(state, key, true).map(|stop| (key, stop)))
8702        .collect()
8703}
8704
8705fn take_multicast_stops_for_call(state: &mut SessionState, call_id: CallId) -> Vec<ServerMessage> {
8706    let mut keys = state
8707        .multicast
8708        .keys()
8709        .copied()
8710        .filter(|key| key.call_id == call_id)
8711        .collect::<Vec<_>>();
8712    keys.sort_unstable_by_key(|key| key.conference_id.get());
8713    keys.into_iter()
8714        .flat_map(|key| {
8715            [
8716                take_multicast_stop(state, key, true),
8717                take_multicast_stop(state, key, false),
8718            ]
8719            .into_iter()
8720            .flatten()
8721        })
8722        .collect()
8723}
8724
8725fn take_all_multicast_stops(state: &mut SessionState) -> Vec<ServerMessage> {
8726    let mut sessions = std::mem::take(&mut state.multicast)
8727        .into_iter()
8728        .collect::<Vec<_>>();
8729    sessions.sort_unstable_by_key(|(key, _)| (key.conference_id.get(), key.call_id.get()));
8730    sessions
8731        .into_iter()
8732        .flat_map(|(key, session)| {
8733            [
8734                session.receive.map(|leg| {
8735                    multicast_stop_message(key, session.wire_call_reference, leg.request, true)
8736                }),
8737                session.transmit.map(|leg| {
8738                    multicast_stop_message(key, session.wire_call_reference, leg.request, false)
8739                }),
8740            ]
8741            .into_iter()
8742            .flatten()
8743        })
8744        .collect()
8745}
8746
8747async fn drain_session_media(stream: &mut dyn StationIo, state: &mut SessionState) {
8748    let protocol = state.registration.protocol;
8749    let messages = take_all_multimedia_receive_closes(state)
8750        .into_iter()
8751        .chain(take_all_multimedia_transmit_stops(state))
8752        .chain(take_all_multicast_stops(state));
8753    for message in messages {
8754        if send_message(stream, &message, protocol).await.is_err() {
8755            break;
8756        }
8757    }
8758}
8759
8760async fn stop_call_multicast(
8761    stream: &mut dyn StationIo,
8762    state: &mut SessionState,
8763    call_id: CallId,
8764    protocol: ProtocolVersion,
8765) -> Result<(), ServerError> {
8766    for message in take_multicast_stops_for_call(state, call_id) {
8767        send_message(stream, &message, protocol).await?;
8768    }
8769    Ok(())
8770}
8771
8772fn allocate_media_request_identity(
8773    state: &mut SessionState,
8774    call_id: CallId,
8775) -> Result<MediaRequestIdentity, ServerError> {
8776    let generation = require_call(state, call_id)?
8777        .media
8778        .generation
8779        .checked_add(1)
8780        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8781    let token = state
8782        .next_media_token
8783        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8784    let identity = MediaRequestIdentity::new(generation, token)
8785        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8786    state.next_media_token = token.checked_next();
8787    require_call_mut(state, call_id)?.media.generation = generation;
8788    Ok(identity)
8789}
8790
8791fn media_request_party_id(
8792    request: Option<MediaRequestIdentity>,
8793    stable_call_reference: u32,
8794) -> u32 {
8795    request.map_or(stable_call_reference, |identity| identity.token().get())
8796}
8797
8798fn remove_call(state: &mut SessionState, call_id: CallId) {
8799    if let Some(call) = state.calls_by_id.remove(&call_id) {
8800        state.calls_by_wire.remove(&call.wire_reference);
8801        if state.active_call_id == Some(call_id) {
8802            state.active_call_id = None;
8803        }
8804    }
8805}
8806
8807fn canonical_ip_address(address: IpAddr) -> IpAddr {
8808    match address {
8809        IpAddr::V6(address) => address
8810            .to_ipv4_mapped()
8811            .map_or(IpAddr::V6(address), IpAddr::V4),
8812        address => address,
8813    }
8814}
8815
8816fn server_response_address(
8817    local: IpAddr,
8818    configured_ipv4_fallback: Ipv4Addr,
8819    configured_ipv6_fallback: Option<Ipv6Addr>,
8820) -> IpAddr {
8821    match canonical_ip_address(local) {
8822        IpAddr::V4(address) if address.is_unspecified() => IpAddr::V4(configured_ipv4_fallback),
8823        IpAddr::V6(address) if address.is_unspecified() => {
8824            configured_ipv6_fallback.map_or(IpAddr::V4(configured_ipv4_fallback), IpAddr::V6)
8825        }
8826        local => local,
8827    }
8828}
8829
8830fn server_response_endpoints(
8831    context: &SessionContext,
8832    protocol: ProtocolVersion,
8833) -> Result<Vec<SignalingServerEndpoint>, ServerError> {
8834    let local_endpoint = || {
8835        let address = server_response_address(
8836            context.local.ip(),
8837            context.config.advertised_address,
8838            context.config.advertised_ipv6_address,
8839        );
8840        let address = if protocol < ProtocolVersion::V17 && address.is_ipv6() {
8841            IpAddr::V4(context.config.advertised_address)
8842        } else {
8843            address
8844        };
8845        if address.is_unspecified() {
8846            return Err(ServerError::InvalidConfig(
8847                "server-list fallback address is unspecified".into(),
8848            ));
8849        }
8850        Ok(SignalingServerEndpoint {
8851            name: context.config.server_name.clone(),
8852            address,
8853            port: NonZeroU16::new(context.local.port()).ok_or_else(|| {
8854                ServerError::InvalidConfig("accepted local endpoint has port zero".into())
8855            })?,
8856        })
8857    };
8858    if context.config.signaling_servers.is_empty() {
8859        return local_endpoint().map(|endpoint| vec![endpoint]);
8860    }
8861
8862    let mut routes = context.config.signaling_servers.iter().collect::<Vec<_>>();
8863    routes.sort_unstable_by_key(|route| route.priority);
8864    let endpoints = routes
8865        .into_iter()
8866        .filter(|route| protocol >= ProtocolVersion::V17 || route.address.is_ipv4())
8867        .filter_map(|route| route.endpoint(context.transport))
8868        .collect::<Vec<_>>();
8869    if endpoints.is_empty() {
8870        local_endpoint().map(|endpoint| vec![endpoint])
8871    } else {
8872        Ok(endpoints)
8873    }
8874}
8875
8876async fn close_call_media_messages(
8877    stream: &mut dyn StationIo,
8878    call: &SessionCall,
8879    protocol: ProtocolVersion,
8880) -> Result<(), ServerError> {
8881    if let Some(leg) = &call.video_receive.leg {
8882        send_message(
8883            stream,
8884            &multimedia_receive_close_message(call, leg),
8885            protocol,
8886        )
8887        .await?;
8888    }
8889    if let Some(leg) = &call.video_transmit.leg {
8890        send_message(
8891            stream,
8892            &multimedia_transmit_stop_message(call, leg),
8893            protocol,
8894        )
8895        .await?;
8896    }
8897    if call.media.receive.state != MediaChannelState::Closed {
8898        send_message(
8899            stream,
8900            &ServerMessage::CloseReceiveChannel(AudioStreamControl {
8901                conference_id: ConferenceId::new(call.wire_reference),
8902                call_reference: CallReference::new(call.wire_reference),
8903                passthrough_party_id: media_request_party_id(
8904                    call.media.receive.request,
8905                    call.wire_reference,
8906                )
8907                .into(),
8908                port_handling_flag: 0,
8909            }),
8910            protocol,
8911        )
8912        .await?;
8913    }
8914    if call.media.transmit.state != MediaChannelState::Closed {
8915        send_message(
8916            stream,
8917            &ServerMessage::StopMediaTransmission(AudioStreamControl {
8918                conference_id: ConferenceId::new(call.wire_reference),
8919                call_reference: CallReference::new(call.wire_reference),
8920                passthrough_party_id: media_request_party_id(
8921                    call.media.transmit.request,
8922                    call.wire_reference,
8923                )
8924                .into(),
8925                port_handling_flag: 0,
8926            }),
8927            protocol,
8928        )
8929        .await?;
8930    }
8931    Ok(())
8932}
8933
8934async fn close_call_messages(
8935    stream: &mut dyn StationIo,
8936    call: &SessionCall,
8937    soft_keys: &SoftKeyProfile,
8938    protocol: ProtocolVersion,
8939    timezone_offset_minutes: i16,
8940) -> Result<(), ServerError> {
8941    send_message(
8942        stream,
8943        &ServerMessage::StopTone {
8944            line_instance: call.line_instance,
8945            call_reference: call.wire_reference,
8946        },
8947        protocol,
8948    )
8949    .await?;
8950    send_message(
8951        stream,
8952        &ServerMessage::SetLamp {
8953            stimulus: ButtonType::Line,
8954            instance: call.line_instance,
8955            mode: LampMode::Off,
8956        },
8957        protocol,
8958    )
8959    .await?;
8960    send_message(
8961        stream,
8962        &ServerMessage::ClearPrompt {
8963            line_instance: call.line_instance,
8964            call_reference: call.wire_reference,
8965        },
8966        protocol,
8967    )
8968    .await?;
8969    send_message(
8970        stream,
8971        &ServerMessage::CallState {
8972            state: CallState::OnHook,
8973            line_instance: call.line_instance,
8974            call_reference: call.wire_reference,
8975        },
8976        protocol,
8977    )
8978    .await?;
8979    send_message(
8980        stream,
8981        &ServerMessage::SelectSoftKeys {
8982            line_instance: 0,
8983            call_reference: 0,
8984            set: KeyMode::OnHook,
8985            valid_mask: soft_keys.valid_mask(KeyMode::OnHook),
8986        },
8987        protocol,
8988    )
8989    .await?;
8990    send_message(
8991        stream,
8992        &time_date_message(timezone_offset_minutes),
8993        protocol,
8994    )
8995    .await?;
8996    send_message(
8997        stream,
8998        &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
8999        protocol,
9000    )
9001    .await?;
9002    // Publish the matching OnHook state before stopping alerting.
9003    send_message(
9004        stream,
9005        &ServerMessage::SetRinger {
9006            mode: RingerMode::Off,
9007            duration: RingDuration::Normal,
9008            line_instance: call.line_instance,
9009            call_reference: call.wire_reference,
9010        },
9011        protocol,
9012    )
9013    .await?;
9014    Ok(())
9015}
9016
9017fn time_date_message(timezone_offset_minutes: i16) -> ServerMessage {
9018    time_date_message_at(SystemTime::now(), timezone_offset_minutes)
9019}
9020
9021fn time_date_message_at(now: SystemTime, timezone_offset_minutes: i16) -> ServerMessage {
9022    let unix = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
9023    let local = (unix as i128 + i128::from(timezone_offset_minutes) * 60)
9024        .clamp(0, i128::from(u32::MAX)) as u64;
9025    let days = (local / 86_400) as i64;
9026    let seconds = local % 86_400;
9027    let (year, month, day) = civil_from_days(days);
9028    ServerMessage::TimeDate {
9029        year: year as u32,
9030        month,
9031        weekday: ((days + 4).rem_euclid(7) + 1) as u32,
9032        day,
9033        hour: (seconds / 3600) as u32,
9034        minute: ((seconds % 3600) / 60) as u32,
9035        second: (seconds % 60) as u32,
9036        milliseconds: 0,
9037        unix_seconds: local as u32,
9038    }
9039}
9040
9041// Howard Hinnant's civil-from-days algorithm, with days based at Unix epoch.
9042fn civil_from_days(days_since_epoch: i64) -> (i64, u32, u32) {
9043    let z = days_since_epoch + 719_468;
9044    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
9045    let doe = z - era * 146_097;
9046    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
9047    let mut year = yoe + era * 400;
9048    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
9049    let mp = (5 * doy + 2) / 153;
9050    let day = doy - (153 * mp + 2) / 5 + 1;
9051    let month = mp + if mp < 10 { 3 } else { -9 };
9052    year += i64::from(month <= 2);
9053    (year, month as u32, day as u32)
9054}
9055
9056#[cfg(test)]
9057mod tests {
9058    use std::net::Ipv6Addr;
9059
9060    use super::*;
9061    use crate::PhoneSoftKeyPosition;
9062    use crate::message::values::{
9063        AnnouncementPlayMode, EndOfAnnouncementAck, IpAddressType, RFC2833_TELEPHONE_EVENT_PAYLOAD,
9064        ReceiveTransmit,
9065    };
9066    use crate::message::wire::Frame;
9067    use crate::message::{
9068        ClientMessage, MediaTransmissionAck, RegistrationMessage, ServerMessage, id,
9069    };
9070    use crate::types::{
9071        BlfSpeedDialDefinition, FeatureDefinition, LineAppearance, LineDefinition,
9072        ServiceDefinition, SpeedDialDefinition,
9073    };
9074
9075    fn definition() -> DeviceDefinition {
9076        definition_for("SEP001122334455")
9077    }
9078
9079    fn definition_for(device_id: &str) -> DeviceDefinition {
9080        DeviceDefinition {
9081            id: DeviceId::new(device_id).unwrap(),
9082            description: "Test phone".into(),
9083            transport: StationTransportRequirement::Either,
9084            signaling_qos: None,
9085            buttons: vec![ButtonDefinition::Line(LineAppearance::new(
9086                1,
9087                LineDefinition {
9088                    number: "1001".into(),
9089                    display_name: "Desk 1001".into(),
9090                },
9091            ))],
9092            soft_keys: SoftKeyProfile::default(),
9093            ui: Default::default(),
9094        }
9095    }
9096
9097    #[test]
9098    fn session_generations_are_nonzero_monotonic_and_fail_closed_at_exhaustion() {
9099        let next = AtomicU64::new(1);
9100        let first = allocate_session_generation(&next).unwrap();
9101        let second = allocate_session_generation(&next).unwrap();
9102        assert_eq!(u64::from(first), 1);
9103        assert_eq!(u64::from(second), 2);
9104
9105        let exhausted = AtomicU64::new(u64::MAX);
9106        assert!(matches!(
9107            allocate_session_generation(&exhausted),
9108            Err(ServerError::SessionGenerationExhausted)
9109        ));
9110        assert_eq!(exhausted.load(Ordering::Relaxed), u64::MAX);
9111
9112        let boundary = AtomicU64::new(u64::MAX - 1);
9113        assert_eq!(
9114            u64::from(allocate_session_generation(&boundary).unwrap()),
9115            u64::MAX - 1
9116        );
9117        assert!(matches!(
9118            allocate_session_generation(&boundary),
9119            Err(ServerError::SessionGenerationExhausted)
9120        ));
9121        assert_eq!(boundary.load(Ordering::Relaxed), u64::MAX);
9122
9123        let invalid = AtomicU64::new(0);
9124        assert!(matches!(
9125            allocate_session_generation(&invalid),
9126            Err(ServerError::SessionGenerationExhausted)
9127        ));
9128        assert_eq!(invalid.load(Ordering::Relaxed), 0);
9129    }
9130
9131    fn multicast_test_state(protocol: ProtocolVersion) -> SessionState {
9132        let device = definition();
9133        SessionState {
9134            registration: DeviceRegistration {
9135                id: device.id.clone(),
9136                peer: "127.0.0.1:2000".parse().unwrap(),
9137                transport: StationTransport::Clear,
9138                reported_address: Some(Ipv4Addr::LOCALHOST),
9139                reported_ipv6_address: None,
9140                device_type: DeviceType::Undefined,
9141                protocol,
9142                firmware: "test".into(),
9143            },
9144            device,
9145            features: PhoneFeatures::empty(),
9146            generation: SessionGeneration::new(1).unwrap(),
9147            calls_by_id: HashMap::new(),
9148            calls_by_wire: HashMap::new(),
9149            media_capabilities: vec![MediaCapability {
9150                codec: Codec::Pcmu,
9151                max_frames_per_packet: 2,
9152                codec_parameters: [0; 8],
9153            }]
9154            .into(),
9155            next_media_token: MediaRequestToken::new(1),
9156            next_multicast_generation: 0,
9157            multicast: HashMap::new(),
9158            pending_connection_statistics: HashMap::new(),
9159            statistics_references: HashSet::new(),
9160            cancelled_calls: HashSet::new(),
9161            last_number_by_line: HashMap::new(),
9162            forwarding_by_line: HashMap::new(),
9163            feature_states: HashMap::new(),
9164            mwi_by_line: HashMap::new(),
9165            mobility_appearances: HashMap::new(),
9166            active_key_mode: KeyMode::OnHook,
9167            active_call_id: None,
9168            pending_parking_menu: None,
9169            persistent_status_message: false,
9170            headset_enabled: false,
9171            media_path_states: HashMap::new(),
9172            pending_media_path_release: None,
9173        }
9174    }
9175
9176    fn multicast_route(address: IpAddr, codec: Codec) -> MulticastMediaRoute {
9177        MulticastMediaRoute {
9178            address,
9179            port: 5004,
9180            codec,
9181            packet_millis: 20,
9182        }
9183    }
9184
9185    const fn test_rtp_payload_number(value: u8) -> crate::RtpPayloadNumber {
9186        match crate::RtpPayloadNumber::new(value as u32) {
9187            Ok(payload_number) => payload_number,
9188            Err(_) => panic!("test RTP payload number is out of range"),
9189        }
9190    }
9191
9192    fn video_receive_descriptor(
9193        conference_id: u32,
9194        source: MediaEndpointAddress,
9195    ) -> MultimediaReceiveDescriptor {
9196        MultimediaReceiveDescriptor {
9197            conference_id: ConferenceId::new(conference_id),
9198            payload: MultimediaPayload::from_wire(
9199                0,
9200                test_rtp_payload_number(97),
9201                [0xa5; crate::MULTIMEDIA_CAPABILITY_BYTES],
9202                Codec::H264,
9203                MultimediaPayloadDirection::Receive,
9204                ProtocolVersion::V22,
9205            ),
9206            conference_creator: false,
9207            encryption: None,
9208            stream_passthrough_id: conference_id + 100,
9209            associated_stream_id: 0,
9210            source,
9211            requested_address_type: IpAddressType::Ipv4AndIpv6,
9212        }
9213    }
9214
9215    fn video_transmit_descriptor(
9216        conference_id: u32,
9217        endpoint: MediaEndpointAddress,
9218    ) -> MultimediaTransmitDescriptor {
9219        MultimediaTransmitDescriptor {
9220            conference_id: ConferenceId::new(conference_id),
9221            endpoint,
9222            payload: MultimediaPayload::from_wire(
9223                0,
9224                test_rtp_payload_number(98),
9225                [0x5a; crate::MULTIMEDIA_CAPABILITY_BYTES],
9226                Codec::H264,
9227                MultimediaPayloadDirection::Transmit,
9228                ProtocolVersion::V22,
9229            ),
9230            traffic_class: MediaTrafficClass::from_wire(136),
9231            encryption: None,
9232            stream_passthrough_id: conference_id + 200,
9233            associated_stream_id: 0,
9234        }
9235    }
9236
9237    #[test]
9238    fn video_receive_validation_uses_typed_station_policy_without_touching_audio() {
9239        let mut state = multicast_test_state(ProtocolVersion::V22);
9240        state.media_capabilities = StationMediaCapabilities::new(
9241            state.media_capabilities.audio().to_vec(),
9242            vec![crate::message::capabilities::VideoCapability {
9243                codec: Codec::H264,
9244                direction: ReceiveTransmit::RECEIVE,
9245                level_preferences: Vec::new(),
9246                codec_parameters: Vec::new(),
9247                encryption_capability: Some(EncryptionCapability::Capable),
9248                address_type: Some(IpAddressType::Ipv4AndIpv6),
9249            }],
9250        );
9251        let audio_before = state.media_capabilities.audio().to_vec();
9252        let descriptor = video_receive_descriptor(
9253            70,
9254            MediaEndpointAddress {
9255                address: "192.0.2.10".parse().unwrap(),
9256                port: 5004,
9257            },
9258        );
9259        assert!(validate_multimedia_receive(&state, &descriptor).is_ok());
9260        assert_eq!(state.media_capabilities.audio(), audio_before);
9261
9262        let receive_capability = state.media_capabilities.video()[0].clone();
9263        state.media_capabilities = StationMediaCapabilities::new(
9264            audio_before.clone(),
9265            vec![crate::message::capabilities::VideoCapability {
9266                direction: ReceiveTransmit::TRANSMIT,
9267                ..receive_capability.clone()
9268            }],
9269        );
9270        assert!(matches!(
9271            validate_multimedia_receive(&state, &descriptor),
9272            Err(ServerError::UnsupportedMultimediaReceive)
9273        ));
9274        state.media_capabilities =
9275            StationMediaCapabilities::new(audio_before.clone(), vec![receive_capability]);
9276
9277        let mut wrong_direction = descriptor.clone();
9278        wrong_direction.payload = video_transmit_descriptor(
9279            70,
9280            MediaEndpointAddress {
9281                address: "192.0.2.10".parse().unwrap(),
9282                port: 5004,
9283            },
9284        )
9285        .payload;
9286        assert!(matches!(
9287            wrong_direction.validate(),
9288            Err(ServerError::InvalidMultimediaReceive(_))
9289        ));
9290
9291        let mut mismatched_address = descriptor.clone();
9292        mismatched_address.requested_address_type = IpAddressType::Ipv6;
9293        assert!(matches!(
9294            mismatched_address.validate(),
9295            Err(ServerError::InvalidMultimediaReceive(_))
9296        ));
9297
9298        for protocol in [
9299            ProtocolVersion::V3,
9300            ProtocolVersion::V10,
9301            ProtocolVersion::V11,
9302        ] {
9303            state.registration.protocol = protocol;
9304            let mut legacy_descriptor = descriptor.clone();
9305            legacy_descriptor.source = MediaEndpointAddress {
9306                address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9307                port: 0,
9308            };
9309            legacy_descriptor.requested_address_type = IpAddressType::Ipv4;
9310            legacy_descriptor.payload = MultimediaPayload::from_wire(
9311                0,
9312                test_rtp_payload_number(97),
9313                [0xa5; crate::MULTIMEDIA_CAPABILITY_BYTES],
9314                Codec::H264,
9315                MultimediaPayloadDirection::Receive,
9316                protocol,
9317            );
9318            assert!(validate_multimedia_receive(&state, &legacy_descriptor).is_ok());
9319            assert_eq!(state.media_capabilities.audio(), audio_before);
9320        }
9321    }
9322
9323    #[test]
9324    fn video_transmit_validation_requires_exact_direction_endpoint_and_protocol() {
9325        let mut state = multicast_test_state(ProtocolVersion::V22);
9326        let audio_before = state.media_capabilities.audio().to_vec();
9327        let capability = crate::message::capabilities::VideoCapability {
9328            codec: Codec::H264,
9329            direction: ReceiveTransmit::TRANSMIT,
9330            level_preferences: Vec::new(),
9331            codec_parameters: Vec::new(),
9332            encryption_capability: Some(EncryptionCapability::Capable),
9333            address_type: Some(IpAddressType::Ipv4AndIpv6),
9334        };
9335        state.media_capabilities =
9336            StationMediaCapabilities::new(audio_before.clone(), vec![capability.clone()]);
9337        let descriptor = video_transmit_descriptor(
9338            80,
9339            MediaEndpointAddress {
9340                address: "192.0.2.80".parse().unwrap(),
9341                port: 5080,
9342            },
9343        );
9344        assert!(validate_multimedia_transmit(&state, &descriptor).is_ok());
9345        assert_eq!(state.media_capabilities.audio(), audio_before);
9346
9347        state.media_capabilities = StationMediaCapabilities::new(
9348            audio_before.clone(),
9349            vec![crate::message::capabilities::VideoCapability {
9350                direction: ReceiveTransmit::RECEIVE,
9351                ..capability.clone()
9352            }],
9353        );
9354        assert!(matches!(
9355            validate_multimedia_transmit(&state, &descriptor),
9356            Err(ServerError::UnsupportedMultimediaTransmit)
9357        ));
9358        state.media_capabilities =
9359            StationMediaCapabilities::new(audio_before.clone(), vec![capability]);
9360
9361        for invalid in [
9362            MultimediaTransmitDescriptor {
9363                payload: video_receive_descriptor(
9364                    80,
9365                    MediaEndpointAddress {
9366                        address: "192.0.2.80".parse().unwrap(),
9367                        port: 5080,
9368                    },
9369                )
9370                .payload,
9371                ..descriptor.clone()
9372            },
9373            MultimediaTransmitDescriptor {
9374                endpoint: MediaEndpointAddress {
9375                    address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9376                    port: 5080,
9377                },
9378                ..descriptor.clone()
9379            },
9380        ] {
9381            assert!(matches!(
9382                invalid.validate(),
9383                Err(ServerError::InvalidMultimediaTransmit(_))
9384            ));
9385        }
9386
9387        for protocol in [ProtocolVersion::V3, ProtocolVersion::V10] {
9388            state.registration.protocol = protocol;
9389            let mut legacy = descriptor.clone();
9390            legacy.payload = MultimediaPayload::from_wire(
9391                0,
9392                test_rtp_payload_number(98),
9393                [0x5a; crate::MULTIMEDIA_CAPABILITY_BYTES],
9394                Codec::H264,
9395                MultimediaPayloadDirection::Transmit,
9396                protocol,
9397            );
9398            assert!(validate_multimedia_transmit(&state, &legacy).is_ok());
9399        }
9400
9401        state.registration.protocol = ProtocolVersion::V16;
9402        let ipv6 = MultimediaTransmitDescriptor {
9403            endpoint: MediaEndpointAddress {
9404                address: "2001:db8::80".parse().unwrap(),
9405                port: 5080,
9406            },
9407            ..descriptor
9408        };
9409        assert!(matches!(
9410            validate_multimedia_transmit(&state, &ipv6),
9411            Err(ServerError::InvalidMultimediaTransmit(_))
9412        ));
9413        assert_eq!(state.media_capabilities.audio(), audio_before);
9414    }
9415
9416    #[test]
9417    fn multimedia_transmit_controls_encode_only_their_typed_parameter_words() {
9418        fn assert_control(
9419            control: MultimediaTransmitControl,
9420            expected_command: MiscCommandType,
9421            expected_words: &[u32],
9422        ) {
9423            let (command, data) = encode_multimedia_transmit_control(control).unwrap();
9424            let expected = expected_words
9425                .iter()
9426                .flat_map(|word| word.to_le_bytes())
9427                .collect::<Vec<_>>();
9428            assert_eq!(command, expected_command);
9429            assert_eq!(data.as_bytes(), expected);
9430        }
9431
9432        assert_control(
9433            MultimediaTransmitControl::FreezePicture,
9434            MiscCommandType::VideoFreezePicture,
9435            &[],
9436        );
9437        assert_control(
9438            MultimediaTransmitControl::FastPictureUpdate {
9439                first_gob: 1,
9440                gob_count: 2,
9441            },
9442            MiscCommandType::VideoFastUpdatePicture,
9443            &[1, 2],
9444        );
9445        assert_control(
9446            MultimediaTransmitControl::FastGobUpdate {
9447                first_gob: 3,
9448                gob_count: 4,
9449            },
9450            MiscCommandType::VideoFastUpdateGob,
9451            &[3, 4],
9452        );
9453        assert_control(
9454            MultimediaTransmitControl::FastMacroblockUpdate {
9455                first_gob: 5,
9456                first_macroblock: 6,
9457                macroblock_count: 7,
9458            },
9459            MiscCommandType::VideoFastUpdateMacroblock,
9460            &[5, 6, 7],
9461        );
9462        assert_control(
9463            MultimediaTransmitControl::LostPicture {
9464                picture_number: 8,
9465                long_term_picture_index: 9,
9466            },
9467            MiscCommandType::LostPicture,
9468            &[8, 9],
9469        );
9470        assert_control(
9471            MultimediaTransmitControl::LostPartialPicture {
9472                picture_number: 10,
9473                long_term_picture_index: 11,
9474                first_macroblock: 12,
9475                macroblock_count: 13,
9476            },
9477            MiscCommandType::LostPartialPicture,
9478            &[10, 11, 12, 13],
9479        );
9480        let pictures = VideoPictureReferences::new([
9481            VideoPictureReference {
9482                picture_number: 14,
9483                long_term_picture_index: 15,
9484            },
9485            VideoPictureReference {
9486                picture_number: 16,
9487                long_term_picture_index: 17,
9488            },
9489        ])
9490        .unwrap();
9491        assert_control(
9492            MultimediaTransmitControl::RecoveryReferencePicture { pictures },
9493            MiscCommandType::RecoveryReferencePicture,
9494            &[2, 14, 15, 16, 17],
9495        );
9496        assert_control(
9497            MultimediaTransmitControl::TemporalSpatialTradeoff { value: 18 },
9498            MiscCommandType::TemporalSpatialTradeoff,
9499            &[18],
9500        );
9501        assert!(matches!(
9502            VideoPictureReferences::new(std::iter::repeat(VideoPictureReference {
9503                picture_number: 1,
9504                long_term_picture_index: 2,
9505            })),
9506            Err(ServerError::InvalidMultimediaTransmitControl(_))
9507        ));
9508    }
9509
9510    #[tokio::test]
9511    async fn video_receive_session_correlates_fragmented_acknowledgements_and_preserves_audio() {
9512        let device = definition();
9513        let device_id = device.id.clone();
9514        let config = ServerConfig {
9515            bind: "127.0.0.1:0".parse().unwrap(),
9516            advertised_address: Ipv4Addr::LOCALHOST,
9517            ..ServerConfig::default()
9518        };
9519        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
9520        let address = server.local_addr().unwrap();
9521        let task = tokio::spawn(server.run());
9522        let mut phone = TcpStream::connect(address).await.unwrap();
9523        let mut decoder = FrameDecoder::new();
9524        let protocol = ProtocolVersion::V22;
9525        let call_id = CallId::new(71);
9526
9527        phone.write_all(&register_bytes(protocol)).await.unwrap();
9528        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
9529        assert!(matches!(
9530            events.recv().await,
9531            Some(Event::Device(DeviceEvent {
9532                event: DeviceEventKind::Registered(_),
9533                ..
9534            }))
9535        ));
9536        phone
9537            .write_all(&capability_update_bytes(
9538                protocol,
9539                Codec::Pcmu,
9540                Codec::H264,
9541                71,
9542            ))
9543            .await
9544            .unwrap();
9545        assert!(matches!(
9546            events.recv().await,
9547            Some(Event::Device(DeviceEvent {
9548                event: DeviceEventKind::Capabilities { .. },
9549                ..
9550            }))
9551        ));
9552
9553        handle
9554            .send_confirmed(Command::new(
9555                device_id.clone(),
9556                CommandAction::BeginCall {
9557                    line_instance: LineInstance::new(1),
9558                    call_id,
9559                    codec: Codec::Pcmu,
9560                },
9561            ))
9562            .await
9563            .unwrap();
9564        let begin = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9565            matches!(message, ServerMessage::SelectSoftKeys { .. })
9566        })
9567        .await;
9568        let wire_reference = begin
9569            .iter()
9570            .find_map(|message| match message {
9571                ServerMessage::CallState { call_reference, .. } => {
9572                    Some(CallReference::new(*call_reference))
9573                }
9574                _ => None,
9575            })
9576            .expect("begin call omitted its wire identity");
9577        assert!(matches!(
9578            handle
9579                .send_confirmed(Command::new(
9580                    device_id.clone(),
9581                    CommandAction::OpenMultimediaReceiveChannel {
9582                        call_id,
9583                        descriptor: video_receive_descriptor(
9584                            699,
9585                            MediaEndpointAddress {
9586                                address: "192.0.2.69".parse().unwrap(),
9587                                port: 5068,
9588                            },
9589                        ),
9590                    },
9591                ))
9592                .await,
9593            Err(ServerError::CommandWrite(_))
9594        ));
9595        handle
9596            .send_confirmed(Command::new(
9597                device_id.clone(),
9598                CommandAction::SetCallState {
9599                    call_id,
9600                    state: CallState::Connected,
9601                },
9602            ))
9603            .await
9604            .unwrap();
9605        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
9606
9607        handle
9608            .send_confirmed(Command::new(
9609                device_id.clone(),
9610                CommandAction::OpenReceiveChannel {
9611                    call_id,
9612                    source: None,
9613                    codec: Codec::Pcmu,
9614                    packet_ms: 20,
9615                    max_frames_per_packet: 2,
9616                    dtmf_mode: DtmfMode::Rfc2833,
9617                    audio_processing: AudioProcessingPolicy::default(),
9618                },
9619            ))
9620            .await
9621            .unwrap();
9622        let audio_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9623            matches!(message, ServerMessage::OpenReceiveChannel { .. })
9624        })
9625        .await;
9626        let audio_token = audio_open
9627            .iter()
9628            .find_map(|message| match message {
9629                ServerMessage::OpenReceiveChannel {
9630                    passthrough_party_id,
9631                    ..
9632                } => Some(*passthrough_party_id),
9633                _ => None,
9634            })
9635            .unwrap();
9636
9637        let first_descriptor = video_receive_descriptor(
9638            700,
9639            MediaEndpointAddress {
9640                address: "192.0.2.70".parse().unwrap(),
9641                port: 5070,
9642            },
9643        );
9644        handle
9645            .send_confirmed(Command::new(
9646                device_id.clone(),
9647                CommandAction::OpenMultimediaReceiveChannel {
9648                    call_id,
9649                    descriptor: first_descriptor.clone(),
9650                },
9651            ))
9652            .await
9653            .unwrap();
9654        let first_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9655            matches!(message, ServerMessage::OpenMultimediaChannel(_))
9656        })
9657        .await
9658        .into_iter()
9659        .find_map(|message| match message {
9660            ServerMessage::OpenMultimediaChannel(open) => Some(open),
9661            _ => None,
9662        })
9663        .unwrap();
9664        assert_eq!(first_open.payload.codec(), Codec::H264);
9665        assert_eq!(first_open.line_instance, 1);
9666        assert_eq!(first_open.call_reference, wire_reference);
9667        assert_eq!(first_open.payload, first_descriptor.payload);
9668        let first_token = first_open.passthrough_party_id;
9669        let endpoint = MediaEndpointAddress {
9670            address: "198.51.100.70".parse().unwrap(),
9671            port: 6070,
9672        };
9673        let wrong_call = ClientMessage::OpenMultimediaReceiveChannelAck(
9674            crate::OpenMultimediaReceiveChannelAck {
9675                status: MediaStatus::Ok,
9676                endpoint,
9677                passthrough_party_id: first_token,
9678                call_reference: CallReference::new(wire_reference.get() + 1),
9679            },
9680        )
9681        .encode(protocol)
9682        .unwrap();
9683        let exact = ClientMessage::OpenMultimediaReceiveChannelAck(
9684            crate::OpenMultimediaReceiveChannelAck {
9685                status: MediaStatus::Ok,
9686                endpoint,
9687                passthrough_party_id: first_token,
9688                call_reference: wire_reference,
9689            },
9690        )
9691        .encode(protocol)
9692        .unwrap();
9693        let mut coalesced_prefix = wrong_call;
9694        coalesced_prefix.extend(
9695            ClientMessage::OpenMultimediaReceiveChannelAck(
9696                crate::OpenMultimediaReceiveChannelAck {
9697                    status: MediaStatus::Ok,
9698                    endpoint: MediaEndpointAddress {
9699                        address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9700                        port: endpoint.port,
9701                    },
9702                    passthrough_party_id: first_token,
9703                    call_reference: wire_reference,
9704                },
9705            )
9706            .encode(protocol)
9707            .unwrap(),
9708        );
9709        coalesced_prefix.push(exact[0]);
9710        phone.write_all(&coalesced_prefix).await.unwrap();
9711        assert!(
9712            tokio::time::timeout(Duration::from_millis(25), events.recv())
9713                .await
9714                .is_err()
9715        );
9716        for fragment in exact[1..].chunks(3) {
9717            phone.write_all(fragment).await.unwrap();
9718        }
9719        assert!(matches!(
9720            events.recv().await,
9721            Some(Event::Device(DeviceEvent {
9722                event: DeviceEventKind::MultimediaReceiveChannelOpened {
9723                    call_id: actual_call,
9724                    codec: Codec::H264,
9725                    endpoint: actual_endpoint,
9726                    passthrough_party_id,
9727                },
9728                ..
9729            })) if actual_call == call_id
9730                && actual_endpoint == endpoint
9731                && passthrough_party_id == first_token
9732        ));
9733        phone.write_all(&exact).await.unwrap();
9734        assert!(
9735            tokio::time::timeout(Duration::from_millis(25), events.recv())
9736                .await
9737                .is_err()
9738        );
9739
9740        phone
9741            .write_all(
9742                &ClientMessage::OpenReceiveChannelAck {
9743                    status: MediaStatus::Ok,
9744                    address: "198.51.100.71".parse().unwrap(),
9745                    port: 6072,
9746                    call_reference: wire_reference.get(),
9747                    passthrough_party_id: audio_token,
9748                }
9749                .encode(protocol)
9750                .unwrap(),
9751            )
9752            .await
9753            .unwrap();
9754        assert!(matches!(
9755            events.recv().await,
9756            Some(Event::Device(DeviceEvent {
9757                event: DeviceEventKind::ReceiveChannelOpened {
9758                    call_id: actual_call,
9759                    status: MediaStatus::Ok,
9760                    ..
9761                },
9762                ..
9763            })) if actual_call == call_id
9764        ));
9765
9766        let replacement_descriptor = video_receive_descriptor(
9767            701,
9768            MediaEndpointAddress {
9769                address: "192.0.2.71".parse().unwrap(),
9770                port: 5072,
9771            },
9772        );
9773        handle
9774            .send_confirmed(Command::new(
9775                device_id.clone(),
9776                CommandAction::OpenMultimediaReceiveChannel {
9777                    call_id,
9778                    descriptor: replacement_descriptor,
9779                },
9780            ))
9781            .await
9782            .unwrap();
9783        let replacement =
9784            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9785                matches!(
9786                    message,
9787                    ServerMessage::OpenMultimediaChannel(open)
9788                        if open.conference_id == ConferenceId::new(701)
9789                )
9790            })
9791            .await;
9792        let close_index = replacement
9793            .iter()
9794            .position(|message| {
9795                matches!(
9796                    message,
9797                    ServerMessage::CloseMultimediaReceiveChannel(control)
9798                        if control.passthrough_party_id == first_token
9799                )
9800            })
9801            .expect("replacement omitted the old video close");
9802        let (open_index, replacement_open) = replacement
9803            .iter()
9804            .enumerate()
9805            .find_map(|(index, message)| match message {
9806                ServerMessage::OpenMultimediaChannel(open)
9807                    if open.conference_id == ConferenceId::new(701) =>
9808                {
9809                    Some((index, open))
9810                }
9811                _ => None,
9812            })
9813            .unwrap();
9814        assert!(close_index < open_index);
9815        assert_ne!(replacement_open.passthrough_party_id, first_token);
9816
9817        let negative = ClientMessage::OpenMultimediaReceiveChannelAck(
9818            crate::OpenMultimediaReceiveChannelAck {
9819                status: MediaStatus::OutOfChannels,
9820                endpoint,
9821                passthrough_party_id: replacement_open.passthrough_party_id,
9822                call_reference: wire_reference,
9823            },
9824        )
9825        .encode(protocol)
9826        .unwrap();
9827        phone.write_all(&exact).await.unwrap();
9828        phone.write_all(&negative).await.unwrap();
9829        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9830            matches!(
9831                message,
9832                ServerMessage::CloseMultimediaReceiveChannel(control)
9833                    if control.passthrough_party_id
9834                        == replacement_open.passthrough_party_id
9835            )
9836        })
9837        .await;
9838        assert!(matches!(
9839            events.recv().await,
9840            Some(Event::Device(DeviceEvent {
9841                event: DeviceEventKind::MultimediaReceiveChannelFailed {
9842                    call_id: actual_call,
9843                    codec: Codec::H264,
9844                    status: MediaStatus::OutOfChannels,
9845                    endpoint: actual_endpoint,
9846                    passthrough_party_id,
9847                },
9848                ..
9849            })) if actual_call == call_id
9850                && actual_endpoint == endpoint
9851                && passthrough_party_id == replacement_open.passthrough_party_id
9852        ));
9853        phone.write_all(&negative).await.unwrap();
9854        assert!(
9855            tokio::time::timeout(Duration::from_millis(25), events.recv())
9856                .await
9857                .is_err()
9858        );
9859
9860        handle
9861            .send_confirmed(Command::new(
9862                device_id.clone(),
9863                CommandAction::OpenMultimediaReceiveChannel {
9864                    call_id,
9865                    descriptor: video_receive_descriptor(
9866                        702,
9867                        MediaEndpointAddress {
9868                            address: "192.0.2.72".parse().unwrap(),
9869                            port: 5074,
9870                        },
9871                    ),
9872                },
9873            ))
9874            .await
9875            .unwrap();
9876        let final_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9877            matches!(
9878                message,
9879                ServerMessage::OpenMultimediaChannel(open)
9880                    if open.conference_id == ConferenceId::new(702)
9881            )
9882        })
9883        .await
9884        .into_iter()
9885        .find_map(|message| match message {
9886            ServerMessage::OpenMultimediaChannel(open)
9887                if open.conference_id == ConferenceId::new(702) =>
9888            {
9889                Some(open)
9890            }
9891            _ => None,
9892        })
9893        .unwrap();
9894        handle
9895            .send_confirmed(Command::new(
9896                device_id.clone(),
9897                CommandAction::CloseCall { call_id },
9898            ))
9899            .await
9900            .unwrap();
9901        let close_messages =
9902            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9903                matches!(
9904                    message,
9905                    ServerMessage::CallState {
9906                        state: CallState::OnHook,
9907                        ..
9908                    }
9909                )
9910            })
9911            .await;
9912        let video_close = close_messages
9913            .iter()
9914            .position(|message| {
9915                matches!(
9916                    message,
9917                    ServerMessage::CloseMultimediaReceiveChannel(control)
9918                        if control.passthrough_party_id
9919                            == final_open.passthrough_party_id
9920                )
9921            })
9922            .expect("call close omitted the video receive leg");
9923        let audio_close = close_messages
9924            .iter()
9925            .position(|message| {
9926                matches!(
9927                    message,
9928                    ServerMessage::CloseReceiveChannel(control)
9929                        if control.passthrough_party_id.get() == audio_token
9930                )
9931            })
9932            .expect("call close omitted the independently opened audio receive leg");
9933        let on_hook = close_messages
9934            .iter()
9935            .position(|message| {
9936                matches!(
9937                    message,
9938                    ServerMessage::CallState {
9939                        state: CallState::OnHook,
9940                        ..
9941                    }
9942                )
9943            })
9944            .unwrap();
9945        assert!(video_close < audio_close && audio_close < on_hook);
9946
9947        let reconfigured_call_id = CallId::new(73);
9948        handle
9949            .send_confirmed(Command::new(
9950                device_id.clone(),
9951                CommandAction::BeginCall {
9952                    line_instance: LineInstance::new(1),
9953                    call_id: reconfigured_call_id,
9954                    codec: Codec::Pcmu,
9955                },
9956            ))
9957            .await
9958            .unwrap();
9959        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
9960        handle
9961            .send_confirmed(Command::new(
9962                device_id.clone(),
9963                CommandAction::SetCallState {
9964                    call_id: reconfigured_call_id,
9965                    state: CallState::Connected,
9966                },
9967            ))
9968            .await
9969            .unwrap();
9970        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
9971        handle
9972            .send_confirmed(Command::new(
9973                device_id.clone(),
9974                CommandAction::OpenMultimediaReceiveChannel {
9975                    call_id: reconfigured_call_id,
9976                    descriptor: video_receive_descriptor(
9977                        705,
9978                        MediaEndpointAddress {
9979                            address: "192.0.2.75".parse().unwrap(),
9980                            port: 5080,
9981                        },
9982                    ),
9983                },
9984            ))
9985            .await
9986            .unwrap();
9987        let reconfigure_open =
9988            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9989                matches!(
9990                    message,
9991                    ServerMessage::OpenMultimediaChannel(open)
9992                        if open.conference_id == ConferenceId::new(705)
9993                )
9994            })
9995            .await
9996            .into_iter()
9997            .find_map(|message| match message {
9998                ServerMessage::OpenMultimediaChannel(open)
9999                    if open.conference_id == ConferenceId::new(705) =>
10000                {
10001                    Some(open)
10002                }
10003                _ => None,
10004            })
10005            .unwrap();
10006        handle
10007            .send_confirmed(Command::new(
10008                device_id,
10009                CommandAction::StartMultimediaTransmission {
10010                    call_id: reconfigured_call_id,
10011                    descriptor: video_transmit_descriptor(
10012                        706,
10013                        MediaEndpointAddress {
10014                            address: "192.0.2.76".parse().unwrap(),
10015                            port: 5082,
10016                        },
10017                    ),
10018                },
10019            ))
10020            .await
10021            .unwrap();
10022        let reconfigure_start =
10023            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10024                matches!(
10025                    message,
10026                    ServerMessage::StartMultimediaTransmission(start)
10027                        if start.conference_id == ConferenceId::new(706)
10028                )
10029            })
10030            .await
10031            .into_iter()
10032            .find_map(|message| match message {
10033                ServerMessage::StartMultimediaTransmission(start)
10034                    if start.conference_id == ConferenceId::new(706) =>
10035                {
10036                    Some(start)
10037                }
10038                _ => None,
10039            })
10040            .unwrap();
10041        let mut replacement = definition();
10042        replacement.description = "replacement".into();
10043        handle.reconfigure([replacement]).await.unwrap();
10044        let reconfigure_messages =
10045            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10046                matches!(
10047                    message,
10048                    ServerMessage::StopMultimediaTransmission(control)
10049                        if control.passthrough_party_id
10050                            == reconfigure_start.passthrough_party_id
10051                )
10052            })
10053            .await;
10054        let receive_close = reconfigure_messages
10055            .iter()
10056            .position(|message| {
10057                matches!(
10058                    message,
10059                    ServerMessage::CloseMultimediaReceiveChannel(control)
10060                        if control.passthrough_party_id
10061                            == reconfigure_open.passthrough_party_id
10062                )
10063            })
10064            .expect("reconfigure omitted the video receive close");
10065        let transmit_stop = reconfigure_messages
10066            .iter()
10067            .position(|message| {
10068                matches!(
10069                    message,
10070                    ServerMessage::StopMultimediaTransmission(control)
10071                        if control.passthrough_party_id
10072                            == reconfigure_start.passthrough_party_id
10073                )
10074            })
10075            .expect("reconfigure omitted the video transmit stop");
10076        assert!(receive_close < transmit_stop);
10077        assert!(matches!(
10078            events.recv().await,
10079            Some(Event::Device(DeviceEvent {
10080                event: DeviceEventKind::Disconnected {},
10081                ..
10082            }))
10083        ));
10084
10085        handle.shutdown().await.unwrap();
10086        task.await.unwrap().unwrap();
10087    }
10088
10089    #[tokio::test]
10090    async fn video_transmit_session_correlates_frames_and_preserves_receive_and_audio() {
10091        let device = definition();
10092        let device_id = device.id.clone();
10093        let config = ServerConfig {
10094            bind: "127.0.0.1:0".parse().unwrap(),
10095            advertised_address: Ipv4Addr::LOCALHOST,
10096            ..ServerConfig::default()
10097        };
10098        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
10099        let address = server.local_addr().unwrap();
10100        let task = tokio::spawn(server.run());
10101        let mut phone = TcpStream::connect(address).await.unwrap();
10102        let mut decoder = FrameDecoder::new();
10103        let protocol = ProtocolVersion::V22;
10104        let call_id = CallId::new(81);
10105
10106        phone.write_all(&register_bytes(protocol)).await.unwrap();
10107        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
10108        assert!(matches!(
10109            events.recv().await,
10110            Some(Event::Device(DeviceEvent {
10111                event: DeviceEventKind::Registered(_),
10112                ..
10113            }))
10114        ));
10115        phone
10116            .write_all(&capability_update_bytes(
10117                protocol,
10118                Codec::Pcmu,
10119                Codec::H264,
10120                81,
10121            ))
10122            .await
10123            .unwrap();
10124        assert!(matches!(
10125            events.recv().await,
10126            Some(Event::Device(DeviceEvent {
10127                event: DeviceEventKind::Capabilities { .. },
10128                ..
10129            }))
10130        ));
10131        handle
10132            .send_confirmed(Command::new(
10133                device_id.clone(),
10134                CommandAction::BeginCall {
10135                    line_instance: LineInstance::new(1),
10136                    call_id,
10137                    codec: Codec::Pcmu,
10138                },
10139            ))
10140            .await
10141            .unwrap();
10142        let begin = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10143            matches!(message, ServerMessage::SelectSoftKeys { .. })
10144        })
10145        .await;
10146        let call_reference = begin
10147            .iter()
10148            .find_map(|message| match message {
10149                ServerMessage::CallState { call_reference, .. } => {
10150                    Some(CallReference::new(*call_reference))
10151                }
10152                _ => None,
10153            })
10154            .unwrap();
10155        assert!(matches!(
10156            handle
10157                .send_confirmed(Command::new(
10158                    device_id.clone(),
10159                    CommandAction::StartMultimediaTransmission {
10160                        call_id,
10161                        descriptor: video_transmit_descriptor(
10162                            809,
10163                            MediaEndpointAddress {
10164                                address: "192.0.2.89".parse().unwrap(),
10165                                port: 5088,
10166                            },
10167                        ),
10168                    },
10169                ))
10170                .await,
10171            Err(ServerError::CommandWrite(_))
10172        ));
10173        handle
10174            .send_confirmed(Command::new(
10175                device_id.clone(),
10176                CommandAction::SetCallState {
10177                    call_id,
10178                    state: CallState::Connected,
10179                },
10180            ))
10181            .await
10182            .unwrap();
10183        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
10184
10185        handle
10186            .send_confirmed(Command::new(
10187                device_id.clone(),
10188                CommandAction::OpenReceiveChannel {
10189                    call_id,
10190                    source: None,
10191                    codec: Codec::Pcmu,
10192                    packet_ms: 20,
10193                    max_frames_per_packet: 2,
10194                    dtmf_mode: DtmfMode::Rfc2833,
10195                    audio_processing: AudioProcessingPolicy::default(),
10196                },
10197            ))
10198            .await
10199            .unwrap();
10200        let audio_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10201            matches!(message, ServerMessage::OpenReceiveChannel { .. })
10202        })
10203        .await;
10204        let audio_token = audio_open
10205            .iter()
10206            .find_map(|message| match message {
10207                ServerMessage::OpenReceiveChannel {
10208                    passthrough_party_id,
10209                    ..
10210                } => Some(*passthrough_party_id),
10211                _ => None,
10212            })
10213            .unwrap();
10214
10215        handle
10216            .send_confirmed(Command::new(
10217                device_id.clone(),
10218                CommandAction::OpenMultimediaReceiveChannel {
10219                    call_id,
10220                    descriptor: video_receive_descriptor(
10221                        810,
10222                        MediaEndpointAddress {
10223                            address: "192.0.2.81".parse().unwrap(),
10224                            port: 5082,
10225                        },
10226                    ),
10227                },
10228            ))
10229            .await
10230            .unwrap();
10231        let receive_open =
10232            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10233                matches!(message, ServerMessage::OpenMultimediaChannel(_))
10234            })
10235            .await
10236            .into_iter()
10237            .find_map(|message| match message {
10238                ServerMessage::OpenMultimediaChannel(open) => Some(open),
10239                _ => None,
10240            })
10241            .unwrap();
10242
10243        let first_descriptor = video_transmit_descriptor(
10244            811,
10245            MediaEndpointAddress {
10246                address: "192.0.2.82".parse().unwrap(),
10247                port: 5084,
10248            },
10249        );
10250        handle
10251            .send_confirmed(Command::new(
10252                device_id.clone(),
10253                CommandAction::StartMultimediaTransmission {
10254                    call_id,
10255                    descriptor: first_descriptor.clone(),
10256                },
10257            ))
10258            .await
10259            .unwrap();
10260        let first_start =
10261            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10262                matches!(message, ServerMessage::StartMultimediaTransmission(_))
10263            })
10264            .await
10265            .into_iter()
10266            .find_map(|message| match message {
10267                ServerMessage::StartMultimediaTransmission(start) => Some(start),
10268                _ => None,
10269            })
10270            .unwrap();
10271        assert_eq!(first_start.call_reference, call_reference);
10272        assert_eq!(first_start.endpoint, first_descriptor.endpoint);
10273        assert_eq!(first_start.payload, first_descriptor.payload);
10274        let first_token = first_start.passthrough_party_id;
10275        assert!(matches!(
10276            handle
10277                .send_confirmed(Command::new(
10278                    device_id.clone(),
10279                    CommandAction::ControlMultimediaTransmission {
10280                        call_id,
10281                        passthrough_party_id: first_token,
10282                        control: MultimediaTransmitControl::FreezePicture,
10283                    },
10284                ))
10285                .await,
10286            Err(ServerError::CommandWrite(_))
10287        ));
10288        let station_endpoint = MediaEndpointAddress {
10289            address: "198.51.100.81".parse().unwrap(),
10290            port: 6082,
10291        };
10292        let wrong_conference =
10293            ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10294                conference_id: ConferenceId::new(812),
10295                passthrough_party_id: first_token,
10296                call_reference,
10297                endpoint: station_endpoint,
10298                status: MediaStatus::Ok,
10299            })
10300            .encode(protocol)
10301            .unwrap();
10302        let wrong_call =
10303            ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10304                conference_id: first_start.conference_id,
10305                passthrough_party_id: first_token,
10306                call_reference: CallReference::new(call_reference.get() + 1),
10307                endpoint: station_endpoint,
10308                status: MediaStatus::Ok,
10309            })
10310            .encode(protocol)
10311            .unwrap();
10312        let exact =
10313            ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10314                conference_id: first_start.conference_id,
10315                passthrough_party_id: first_token,
10316                call_reference,
10317                endpoint: station_endpoint,
10318                status: MediaStatus::Ok,
10319            })
10320            .encode(protocol)
10321            .unwrap();
10322        let mut coalesced = wrong_conference;
10323        coalesced.extend(wrong_call);
10324        coalesced.extend(
10325            ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10326                conference_id: first_start.conference_id,
10327                passthrough_party_id: PassthroughPartyId::new(first_token.get() + 1),
10328                call_reference,
10329                endpoint: station_endpoint,
10330                status: MediaStatus::Ok,
10331            })
10332            .encode(protocol)
10333            .unwrap(),
10334        );
10335        coalesced.extend(
10336            ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10337                conference_id: first_start.conference_id,
10338                passthrough_party_id: first_token,
10339                call_reference,
10340                endpoint: MediaEndpointAddress {
10341                    address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
10342                    port: station_endpoint.port,
10343                },
10344                status: MediaStatus::Ok,
10345            })
10346            .encode(protocol)
10347            .unwrap(),
10348        );
10349        coalesced.push(exact[0]);
10350        phone.write_all(&coalesced).await.unwrap();
10351        assert!(
10352            tokio::time::timeout(Duration::from_millis(25), events.recv())
10353                .await
10354                .is_err()
10355        );
10356        for fragment in exact[1..].chunks(3) {
10357            phone.write_all(fragment).await.unwrap();
10358        }
10359        assert!(matches!(
10360            events.recv().await,
10361            Some(Event::Device(DeviceEvent {
10362                event: DeviceEventKind::MultimediaTransmitStarted {
10363                    call_id: actual_call,
10364                    codec: Codec::H264,
10365                    endpoint,
10366                    passthrough_party_id,
10367                },
10368                ..
10369            })) if actual_call == call_id
10370                && endpoint == station_endpoint
10371                && passthrough_party_id == first_token
10372        ));
10373        phone.write_all(&exact).await.unwrap();
10374        assert!(
10375            tokio::time::timeout(Duration::from_millis(25), events.recv())
10376                .await
10377                .is_err()
10378        );
10379
10380        assert!(matches!(
10381            handle
10382                .send_confirmed(Command::new(
10383                    device_id.clone(),
10384                    CommandAction::SetMultimediaTransmitBitRate {
10385                        call_id,
10386                        passthrough_party_id: PassthroughPartyId::new(first_token.get() + 1),
10387                        maximum_bit_rate: 512_000,
10388                    },
10389                ))
10390                .await,
10391            Err(ServerError::CommandWrite(_))
10392        ));
10393        for action in [
10394            CommandAction::SetMultimediaTransmitBitRate {
10395                call_id,
10396                passthrough_party_id: first_token,
10397                maximum_bit_rate: 512_000,
10398            },
10399            CommandAction::NotifyMultimediaTransmitBitRate {
10400                call_id,
10401                passthrough_party_id: first_token,
10402                maximum_bit_rate: 384_000,
10403            },
10404            CommandAction::ControlMultimediaTransmission {
10405                call_id,
10406                passthrough_party_id: first_token,
10407                control: MultimediaTransmitControl::FastPictureUpdate {
10408                    first_gob: 4,
10409                    gob_count: 2,
10410                },
10411            },
10412        ] {
10413            handle
10414                .send_confirmed(Command::new(device_id.clone(), action))
10415                .await
10416                .unwrap();
10417        }
10418        let control_messages =
10419            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10420                matches!(message, ServerMessage::MiscellaneousCommand(_))
10421            })
10422            .await;
10423        assert!(matches!(
10424            control_messages.as_slice(),
10425            [
10426                ServerMessage::FlowControlCommand(VideoFlowControl {
10427                    conference_id,
10428                    passthrough_party_id,
10429                    call_reference: actual_call,
10430                    maximum_bit_rate: 512_000,
10431                }),
10432                ServerMessage::FlowControlNotify(VideoFlowControl {
10433                    conference_id: notify_conference,
10434                    passthrough_party_id: notify_token,
10435                    call_reference: notify_call,
10436                    maximum_bit_rate: 384_000,
10437                }),
10438                ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
10439                    conference_id: command_conference,
10440                    passthrough_party_id: command_token,
10441                    call_reference: command_call,
10442                    command: MiscCommandType::VideoFastUpdatePicture,
10443                    data,
10444                }),
10445            ] if *conference_id == first_start.conference_id
10446                && *passthrough_party_id == first_token
10447                && *actual_call == call_reference
10448                && *notify_conference == first_start.conference_id
10449                && *notify_token == first_token
10450                && *notify_call == call_reference
10451                && *command_conference == first_start.conference_id
10452                && *command_token == first_token
10453                && *command_call == call_reference
10454                && data.as_bytes()[..8]
10455                    == [4_u32.to_le_bytes(), 2_u32.to_le_bytes()].concat()
10456                && data.as_bytes()[8..].iter().all(|byte| *byte == 0)
10457        ));
10458
10459        phone
10460            .write_all(
10461                &ClientMessage::OpenMultimediaReceiveChannelAck(
10462                    crate::OpenMultimediaReceiveChannelAck {
10463                        status: MediaStatus::Ok,
10464                        endpoint: MediaEndpointAddress {
10465                            address: "198.51.100.82".parse().unwrap(),
10466                            port: 6084,
10467                        },
10468                        passthrough_party_id: receive_open.passthrough_party_id,
10469                        call_reference,
10470                    },
10471                )
10472                .encode(protocol)
10473                .unwrap(),
10474            )
10475            .await
10476            .unwrap();
10477        assert!(matches!(
10478            events.recv().await,
10479            Some(Event::Device(DeviceEvent {
10480                event: DeviceEventKind::MultimediaReceiveChannelOpened {
10481                    call_id: actual_call,
10482                    ..
10483                },
10484                ..
10485            })) if actual_call == call_id
10486        ));
10487        phone
10488            .write_all(
10489                &ClientMessage::OpenReceiveChannelAck {
10490                    status: MediaStatus::Ok,
10491                    address: "198.51.100.83".parse().unwrap(),
10492                    port: 6086,
10493                    call_reference: call_reference.get(),
10494                    passthrough_party_id: audio_token,
10495                }
10496                .encode(protocol)
10497                .unwrap(),
10498            )
10499            .await
10500            .unwrap();
10501        assert!(matches!(
10502            events.recv().await,
10503            Some(Event::Device(DeviceEvent {
10504                event: DeviceEventKind::ReceiveChannelOpened {
10505                    call_id: actual_call,
10506                    status: MediaStatus::Ok,
10507                    ..
10508                },
10509                ..
10510            })) if actual_call == call_id
10511        ));
10512
10513        let replacement_descriptor = video_transmit_descriptor(
10514            813,
10515            MediaEndpointAddress {
10516                address: "192.0.2.83".parse().unwrap(),
10517                port: 5086,
10518            },
10519        );
10520        handle
10521            .send_confirmed(Command::new(
10522                device_id.clone(),
10523                CommandAction::StartMultimediaTransmission {
10524                    call_id,
10525                    descriptor: replacement_descriptor,
10526                },
10527            ))
10528            .await
10529            .unwrap();
10530        let replacement =
10531            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10532                matches!(
10533                    message,
10534                    ServerMessage::StartMultimediaTransmission(start)
10535                        if start.conference_id == ConferenceId::new(813)
10536                )
10537            })
10538            .await;
10539        let stop_index = replacement
10540            .iter()
10541            .position(|message| {
10542                matches!(
10543                    message,
10544                    ServerMessage::StopMultimediaTransmission(control)
10545                        if control.passthrough_party_id == first_token
10546                )
10547            })
10548            .expect("replacement omitted the old video transmit stop");
10549        let (start_index, replacement_start) = replacement
10550            .iter()
10551            .enumerate()
10552            .find_map(|(index, message)| match message {
10553                ServerMessage::StartMultimediaTransmission(start)
10554                    if start.conference_id == ConferenceId::new(813) =>
10555                {
10556                    Some((index, start))
10557                }
10558                _ => None,
10559            })
10560            .unwrap();
10561        assert!(stop_index < start_index);
10562        assert_ne!(replacement_start.passthrough_party_id, first_token);
10563        for passthrough_party_id in [first_token, replacement_start.passthrough_party_id] {
10564            assert!(matches!(
10565                handle
10566                    .send_confirmed(Command::new(
10567                        device_id.clone(),
10568                        CommandAction::NotifyMultimediaTransmitBitRate {
10569                            call_id,
10570                            passthrough_party_id,
10571                            maximum_bit_rate: 256_000,
10572                        },
10573                    ))
10574                    .await,
10575                Err(ServerError::CommandWrite(_))
10576            ));
10577        }
10578
10579        let negative =
10580            ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10581                conference_id: replacement_start.conference_id,
10582                passthrough_party_id: replacement_start.passthrough_party_id,
10583                call_reference,
10584                endpoint: station_endpoint,
10585                status: MediaStatus::OutOfChannels,
10586            })
10587            .encode(protocol)
10588            .unwrap();
10589        phone.write_all(&exact).await.unwrap();
10590        phone.write_all(&negative).await.unwrap();
10591        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10592            matches!(
10593                message,
10594                ServerMessage::StopMultimediaTransmission(control)
10595                    if control.passthrough_party_id
10596                        == replacement_start.passthrough_party_id
10597            )
10598        })
10599        .await;
10600        assert!(matches!(
10601            events.recv().await,
10602            Some(Event::Device(DeviceEvent {
10603                event: DeviceEventKind::MultimediaTransmitFailed {
10604                    call_id: actual_call,
10605                    codec: Codec::H264,
10606                    status: MediaStatus::OutOfChannels,
10607                    endpoint,
10608                    passthrough_party_id,
10609                },
10610                ..
10611            })) if actual_call == call_id
10612                && endpoint == station_endpoint
10613                && passthrough_party_id == replacement_start.passthrough_party_id
10614        ));
10615        phone.write_all(&negative).await.unwrap();
10616        assert!(
10617            tokio::time::timeout(Duration::from_millis(25), events.recv())
10618                .await
10619                .is_err()
10620        );
10621
10622        handle
10623            .send_confirmed(Command::new(
10624                device_id.clone(),
10625                CommandAction::StartMultimediaTransmission {
10626                    call_id,
10627                    descriptor: video_transmit_descriptor(
10628                        814,
10629                        MediaEndpointAddress {
10630                            address: "192.0.2.84".parse().unwrap(),
10631                            port: 5088,
10632                        },
10633                    ),
10634                },
10635            ))
10636            .await
10637            .unwrap();
10638        let stopped_start =
10639            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10640                matches!(
10641                    message,
10642                    ServerMessage::StartMultimediaTransmission(start)
10643                        if start.conference_id == ConferenceId::new(814)
10644                )
10645            })
10646            .await
10647            .into_iter()
10648            .find_map(|message| match message {
10649                ServerMessage::StartMultimediaTransmission(start)
10650                    if start.conference_id == ConferenceId::new(814) =>
10651                {
10652                    Some(start)
10653                }
10654                _ => None,
10655            })
10656            .unwrap();
10657        handle
10658            .send_confirmed(Command::new(
10659                device_id.clone(),
10660                CommandAction::StopMultimediaTransmission { call_id },
10661            ))
10662            .await
10663            .unwrap();
10664        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10665            matches!(
10666                message,
10667                ServerMessage::StopMultimediaTransmission(control)
10668                    if control.passthrough_party_id
10669                        == stopped_start.passthrough_party_id
10670            )
10671        })
10672        .await;
10673        handle
10674            .send_confirmed(Command::new(
10675                device_id.clone(),
10676                CommandAction::StopMultimediaTransmission { call_id },
10677            ))
10678            .await
10679            .unwrap();
10680        phone
10681            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
10682            .await
10683            .unwrap();
10684        let after_duplicate_stop =
10685            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10686                matches!(message, ServerMessage::KeepAliveAck)
10687            })
10688            .await;
10689        assert!(!after_duplicate_stop.iter().any(|message| {
10690            matches!(
10691                message,
10692                ServerMessage::StopMultimediaTransmission(control)
10693                    if control.passthrough_party_id
10694                        == stopped_start.passthrough_party_id
10695            )
10696        }));
10697
10698        handle
10699            .send_confirmed(Command::new(
10700                device_id.clone(),
10701                CommandAction::StartMultimediaTransmission {
10702                    call_id,
10703                    descriptor: video_transmit_descriptor(
10704                        815,
10705                        MediaEndpointAddress {
10706                            address: "192.0.2.85".parse().unwrap(),
10707                            port: 5090,
10708                        },
10709                    ),
10710                },
10711            ))
10712            .await
10713            .unwrap();
10714        let final_start =
10715            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10716                matches!(
10717                    message,
10718                    ServerMessage::StartMultimediaTransmission(start)
10719                        if start.conference_id == ConferenceId::new(815)
10720                )
10721            })
10722            .await
10723            .into_iter()
10724            .find_map(|message| match message {
10725                ServerMessage::StartMultimediaTransmission(start)
10726                    if start.conference_id == ConferenceId::new(815) =>
10727                {
10728                    Some(start)
10729                }
10730                _ => None,
10731            })
10732            .unwrap();
10733        handle
10734            .send_confirmed(Command::new(
10735                device_id,
10736                CommandAction::CloseCall { call_id },
10737            ))
10738            .await
10739            .unwrap();
10740        let close_messages =
10741            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10742                matches!(
10743                    message,
10744                    ServerMessage::CallState {
10745                        state: CallState::OnHook,
10746                        ..
10747                    }
10748                )
10749            })
10750            .await;
10751        let video_receive_close = close_messages
10752            .iter()
10753            .position(|message| {
10754                matches!(
10755                    message,
10756                    ServerMessage::CloseMultimediaReceiveChannel(control)
10757                        if control.passthrough_party_id
10758                            == receive_open.passthrough_party_id
10759                )
10760            })
10761            .unwrap();
10762        let video_transmit_stop = close_messages
10763            .iter()
10764            .position(|message| {
10765                matches!(
10766                    message,
10767                    ServerMessage::StopMultimediaTransmission(control)
10768                        if control.passthrough_party_id
10769                            == final_start.passthrough_party_id
10770                )
10771            })
10772            .unwrap();
10773        let audio_close = close_messages
10774            .iter()
10775            .position(|message| {
10776                matches!(
10777                    message,
10778                    ServerMessage::CloseReceiveChannel(control)
10779                        if control.passthrough_party_id.get() == audio_token
10780                )
10781            })
10782            .unwrap();
10783        let on_hook = close_messages
10784            .iter()
10785            .position(|message| {
10786                matches!(
10787                    message,
10788                    ServerMessage::CallState {
10789                        state: CallState::OnHook,
10790                        ..
10791                    }
10792                )
10793            })
10794            .unwrap();
10795        assert!(
10796            video_receive_close < video_transmit_stop
10797                && video_transmit_stop < audio_close
10798                && audio_close < on_hook
10799        );
10800
10801        handle.shutdown().await.unwrap();
10802        task.await.unwrap().unwrap();
10803    }
10804
10805    #[tokio::test(start_paused = true)]
10806    async fn video_receive_deadline_closes_and_retires_the_exact_generation() {
10807        let device = definition();
10808        let device_id = device.id.clone();
10809        let config = ServerConfig {
10810            bind: "127.0.0.1:0".parse().unwrap(),
10811            advertised_address: Ipv4Addr::LOCALHOST,
10812            ..ServerConfig::default()
10813        };
10814        let (server, handle, mut events, ingress) = Server::with_ingress(config, [device]).unwrap();
10815        let task = tokio::spawn(server.run());
10816        let (server_stream, mut phone) = tokio::io::duplex(8_192);
10817        ingress
10818            .accept(
10819                server_stream,
10820                SocketAddr::from(([127, 0, 0, 1], 40_071)),
10821                SocketAddr::from(([127, 0, 0, 1], 2_000)),
10822                StationTransport::Clear,
10823            )
10824            .await
10825            .unwrap();
10826        let mut decoder = FrameDecoder::new();
10827        let protocol = ProtocolVersion::V22;
10828        let call_id = CallId::new(72);
10829
10830        phone.write_all(&register_bytes(protocol)).await.unwrap();
10831        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
10832        assert!(matches!(
10833            events.recv().await,
10834            Some(Event::Device(DeviceEvent {
10835                event: DeviceEventKind::Registered(_),
10836                ..
10837            }))
10838        ));
10839        phone
10840            .write_all(&capability_update_bytes(
10841                protocol,
10842                Codec::Pcmu,
10843                Codec::H264,
10844                72,
10845            ))
10846            .await
10847            .unwrap();
10848        assert!(matches!(
10849            events.recv().await,
10850            Some(Event::Device(DeviceEvent {
10851                event: DeviceEventKind::Capabilities { .. },
10852                ..
10853            }))
10854        ));
10855        handle
10856            .send_confirmed(Command::new(
10857                device_id.clone(),
10858                CommandAction::BeginCall {
10859                    line_instance: LineInstance::new(1),
10860                    call_id,
10861                    codec: Codec::Pcmu,
10862                },
10863            ))
10864            .await
10865            .unwrap();
10866        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
10867        handle
10868            .send_confirmed(Command::new(
10869                device_id.clone(),
10870                CommandAction::SetCallState {
10871                    call_id,
10872                    state: CallState::Connected,
10873                },
10874            ))
10875            .await
10876            .unwrap();
10877        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
10878        handle
10879            .send_confirmed(Command::new(
10880                device_id.clone(),
10881                CommandAction::OpenMultimediaReceiveChannel {
10882                    call_id,
10883                    descriptor: video_receive_descriptor(
10884                        703,
10885                        MediaEndpointAddress {
10886                            address: "192.0.2.73".parse().unwrap(),
10887                            port: 5076,
10888                        },
10889                    ),
10890                },
10891            ))
10892            .await
10893            .unwrap();
10894        let open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10895            matches!(message, ServerMessage::OpenMultimediaChannel(_))
10896        })
10897        .await
10898        .into_iter()
10899        .find_map(|message| match message {
10900            ServerMessage::OpenMultimediaChannel(open) => Some(open),
10901            _ => None,
10902        })
10903        .unwrap();
10904
10905        tokio::time::advance(HANDSET_ACKNOWLEDGEMENT_TIMEOUT + Duration::from_millis(100)).await;
10906        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10907            matches!(
10908                message,
10909                ServerMessage::CloseMultimediaReceiveChannel(control)
10910                    if control.passthrough_party_id == open.passthrough_party_id
10911            )
10912        })
10913        .await;
10914        assert!(matches!(
10915            events.recv().await,
10916            Some(Event::Device(DeviceEvent {
10917                event: DeviceEventKind::MultimediaReceiveChannelTimedOut {
10918                    call_id: actual_call,
10919                    codec: Codec::H264,
10920                    passthrough_party_id,
10921                },
10922                ..
10923            })) if actual_call == call_id
10924                && passthrough_party_id == open.passthrough_party_id
10925        ));
10926
10927        phone
10928            .write_all(
10929                &ClientMessage::OpenMultimediaReceiveChannelAck(
10930                    crate::OpenMultimediaReceiveChannelAck {
10931                        status: MediaStatus::Ok,
10932                        endpoint: MediaEndpointAddress {
10933                            address: "198.51.100.73".parse().unwrap(),
10934                            port: 6076,
10935                        },
10936                        passthrough_party_id: open.passthrough_party_id,
10937                        call_reference: open.call_reference,
10938                    },
10939                )
10940                .encode(protocol)
10941                .unwrap(),
10942            )
10943            .await
10944            .unwrap();
10945        assert!(
10946            tokio::time::timeout(Duration::from_millis(25), events.recv())
10947                .await
10948                .is_err()
10949        );
10950        phone
10951            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
10952            .await
10953            .unwrap();
10954        read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
10955
10956        handle
10957            .send_confirmed(Command::new(
10958                device_id,
10959                CommandAction::OpenMultimediaReceiveChannel {
10960                    call_id,
10961                    descriptor: video_receive_descriptor(
10962                        704,
10963                        MediaEndpointAddress {
10964                            address: "192.0.2.74".parse().unwrap(),
10965                            port: 5078,
10966                        },
10967                    ),
10968                },
10969            ))
10970            .await
10971            .unwrap();
10972        let shutdown_open =
10973            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10974                matches!(
10975                    message,
10976                    ServerMessage::OpenMultimediaChannel(open)
10977                        if open.conference_id == ConferenceId::new(704)
10978                )
10979            })
10980            .await
10981            .into_iter()
10982            .find_map(|message| match message {
10983                ServerMessage::OpenMultimediaChannel(open)
10984                    if open.conference_id == ConferenceId::new(704) =>
10985                {
10986                    Some(open)
10987                }
10988                _ => None,
10989            })
10990            .unwrap();
10991        handle.shutdown().await.unwrap();
10992        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10993            matches!(
10994                message,
10995                ServerMessage::CloseMultimediaReceiveChannel(control)
10996                    if control.passthrough_party_id == shutdown_open.passthrough_party_id
10997            )
10998        })
10999        .await;
11000        task.await.unwrap().unwrap();
11001    }
11002
11003    #[tokio::test(start_paused = true)]
11004    async fn video_transmit_deadline_stops_and_retires_the_exact_generation() {
11005        let device = definition();
11006        let device_id = device.id.clone();
11007        let config = ServerConfig {
11008            bind: "127.0.0.1:0".parse().unwrap(),
11009            advertised_address: Ipv4Addr::LOCALHOST,
11010            ..ServerConfig::default()
11011        };
11012        let (server, handle, mut events, ingress) = Server::with_ingress(config, [device]).unwrap();
11013        let task = tokio::spawn(server.run());
11014        let (server_stream, mut phone) = tokio::io::duplex(8_192);
11015        ingress
11016            .accept(
11017                server_stream,
11018                SocketAddr::from(([127, 0, 0, 1], 40_081)),
11019                SocketAddr::from(([127, 0, 0, 1], 2_000)),
11020                StationTransport::Clear,
11021            )
11022            .await
11023            .unwrap();
11024        let mut decoder = FrameDecoder::new();
11025        let protocol = ProtocolVersion::V22;
11026        let call_id = CallId::new(82);
11027
11028        phone.write_all(&register_bytes(protocol)).await.unwrap();
11029        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
11030        assert!(matches!(
11031            events.recv().await,
11032            Some(Event::Device(DeviceEvent {
11033                event: DeviceEventKind::Registered(_),
11034                ..
11035            }))
11036        ));
11037        phone
11038            .write_all(&capability_update_bytes(
11039                protocol,
11040                Codec::Pcmu,
11041                Codec::H264,
11042                82,
11043            ))
11044            .await
11045            .unwrap();
11046        assert!(matches!(
11047            events.recv().await,
11048            Some(Event::Device(DeviceEvent {
11049                event: DeviceEventKind::Capabilities { .. },
11050                ..
11051            }))
11052        ));
11053        handle
11054            .send_confirmed(Command::new(
11055                device_id.clone(),
11056                CommandAction::BeginCall {
11057                    line_instance: LineInstance::new(1),
11058                    call_id,
11059                    codec: Codec::Pcmu,
11060                },
11061            ))
11062            .await
11063            .unwrap();
11064        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
11065        handle
11066            .send_confirmed(Command::new(
11067                device_id.clone(),
11068                CommandAction::SetCallState {
11069                    call_id,
11070                    state: CallState::Connected,
11071                },
11072            ))
11073            .await
11074            .unwrap();
11075        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
11076        handle
11077            .send_confirmed(Command::new(
11078                device_id.clone(),
11079                CommandAction::StartMultimediaTransmission {
11080                    call_id,
11081                    descriptor: video_transmit_descriptor(
11082                        820,
11083                        MediaEndpointAddress {
11084                            address: "192.0.2.82".parse().unwrap(),
11085                            port: 5090,
11086                        },
11087                    ),
11088                },
11089            ))
11090            .await
11091            .unwrap();
11092        let start = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11093            matches!(message, ServerMessage::StartMultimediaTransmission(_))
11094        })
11095        .await
11096        .into_iter()
11097        .find_map(|message| match message {
11098            ServerMessage::StartMultimediaTransmission(start) => Some(start),
11099            _ => None,
11100        })
11101        .unwrap();
11102
11103        tokio::time::advance(HANDSET_ACKNOWLEDGEMENT_TIMEOUT + Duration::from_millis(100)).await;
11104        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11105            matches!(
11106                message,
11107                ServerMessage::StopMultimediaTransmission(control)
11108                    if control.passthrough_party_id == start.passthrough_party_id
11109            )
11110        })
11111        .await;
11112        assert!(matches!(
11113            events.recv().await,
11114            Some(Event::Device(DeviceEvent {
11115                event: DeviceEventKind::MultimediaTransmitTimedOut {
11116                    call_id: actual_call,
11117                    codec: Codec::H264,
11118                    passthrough_party_id,
11119                },
11120                ..
11121            })) if actual_call == call_id
11122                && passthrough_party_id == start.passthrough_party_id
11123        ));
11124        phone
11125            .write_all(
11126                &ClientMessage::StartMultimediaTransmissionAck(
11127                    crate::StartMultimediaTransmissionAck {
11128                        conference_id: start.conference_id,
11129                        passthrough_party_id: start.passthrough_party_id,
11130                        call_reference: start.call_reference,
11131                        endpoint: MediaEndpointAddress {
11132                            address: "198.51.100.82".parse().unwrap(),
11133                            port: 6090,
11134                        },
11135                        status: MediaStatus::Ok,
11136                    },
11137                )
11138                .encode(protocol)
11139                .unwrap(),
11140            )
11141            .await
11142            .unwrap();
11143        assert!(
11144            tokio::time::timeout(Duration::from_millis(25), events.recv())
11145                .await
11146                .is_err()
11147        );
11148
11149        handle
11150            .send_confirmed(Command::new(
11151                device_id,
11152                CommandAction::StartMultimediaTransmission {
11153                    call_id,
11154                    descriptor: video_transmit_descriptor(
11155                        821,
11156                        MediaEndpointAddress {
11157                            address: "192.0.2.83".parse().unwrap(),
11158                            port: 5092,
11159                        },
11160                    ),
11161                },
11162            ))
11163            .await
11164            .unwrap();
11165        let shutdown_start =
11166            read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11167                matches!(
11168                    message,
11169                    ServerMessage::StartMultimediaTransmission(start)
11170                        if start.conference_id == ConferenceId::new(821)
11171                )
11172            })
11173            .await
11174            .into_iter()
11175            .find_map(|message| match message {
11176                ServerMessage::StartMultimediaTransmission(start)
11177                    if start.conference_id == ConferenceId::new(821) =>
11178                {
11179                    Some(start)
11180                }
11181                _ => None,
11182            })
11183            .unwrap();
11184        handle.shutdown().await.unwrap();
11185        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11186            matches!(
11187                message,
11188                ServerMessage::StopMultimediaTransmission(control)
11189                    if control.passthrough_party_id
11190                        == shutdown_start.passthrough_party_id
11191            )
11192        })
11193        .await;
11194        task.await.unwrap().unwrap();
11195    }
11196
11197    #[test]
11198    fn multicast_admission_requires_a_routable_supported_audio_shape() {
11199        let state = multicast_test_state(ProtocolVersion::V22);
11200        let valid = multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcmu);
11201        assert!(validate_multicast_route(&state, valid, Some(2)).is_ok());
11202
11203        for route in [
11204            MulticastMediaRoute {
11205                address: "192.0.2.1".parse().unwrap(),
11206                ..valid
11207            },
11208            MulticastMediaRoute { port: 0, ..valid },
11209            MulticastMediaRoute {
11210                packet_millis: 0,
11211                ..valid
11212            },
11213        ] {
11214            assert!(matches!(
11215                validate_multicast_route(&state, route, None),
11216                Err(ServerError::InvalidMulticastMedia(_))
11217            ));
11218        }
11219        assert!(matches!(
11220            validate_multicast_route(
11221                &state,
11222                multicast_route("239.1.2.3".parse().unwrap(), Codec::H264),
11223                None,
11224            ),
11225            Err(ServerError::UnsupportedMulticastCodec)
11226        ));
11227        assert!(matches!(
11228            validate_multicast_route(
11229                &state,
11230                multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcma),
11231                None,
11232            ),
11233            Err(ServerError::UnsupportedMulticastCodec)
11234        ));
11235        for requested_frames in [0, 3] {
11236            assert!(matches!(
11237                validate_multicast_route(&state, valid, Some(requested_frames)),
11238                Err(ServerError::InvalidMulticastMedia(_))
11239            ));
11240        }
11241
11242        let legacy = multicast_test_state(ProtocolVersion::V16);
11243        assert!(matches!(
11244            validate_multicast_route(
11245                &legacy,
11246                multicast_route("ff15::1".parse().unwrap(), Codec::Pcmu),
11247                None,
11248            ),
11249            Err(ServerError::InvalidMulticastMedia(_))
11250        ));
11251        assert!(
11252            validate_multicast_route(
11253                &state,
11254                multicast_route("ff15::1".parse().unwrap(), Codec::Pcmu),
11255                None,
11256            )
11257            .is_ok()
11258        );
11259    }
11260
11261    #[test]
11262    fn multicast_transactions_correlate_exactly_and_retire_once_in_wire_order() {
11263        let now = Instant::now();
11264        let mut state = multicast_test_state(ProtocolVersion::V22);
11265        let call = insert_call(&mut state, CallId(10), 1, Codec::Pcmu, CallState::Connected);
11266        let key = MulticastKey {
11267            conference_id: ConferenceId::new(90),
11268            call_id: call.call_id,
11269        };
11270        let receive_request =
11271            MediaRequestIdentity::new(1, MediaRequestToken::new(101).expect("nonzero media token"))
11272                .expect("nonzero generation");
11273        let transmit_request =
11274            MediaRequestIdentity::new(2, MediaRequestToken::new(102).expect("nonzero media token"))
11275                .expect("nonzero generation");
11276        let route = multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcmu);
11277        state.multicast.insert(
11278            key,
11279            MulticastSession {
11280                wire_call_reference: call.wire_reference,
11281                receive: Some(MulticastReceive {
11282                    request: receive_request,
11283                    route,
11284                    state: MulticastReceiveState::AwaitingAcknowledgement { deadline: now },
11285                }),
11286                transmit: Some(MulticastTransmit {
11287                    request: transmit_request,
11288                    route,
11289                }),
11290            },
11291        );
11292
11293        assert_eq!(
11294            find_multicast_receive_key(&state, call.wire_reference, 100),
11295            None
11296        );
11297        assert_eq!(
11298            find_multicast_receive_key(&state, call.wire_reference + 1, 101),
11299            None
11300        );
11301        assert_eq!(
11302            find_multicast_receive_key(&state, call.wire_reference, 101),
11303            Some(key)
11304        );
11305        assert_eq!(
11306            find_multicast_transmit_key(
11307                &state,
11308                90,
11309                call.wire_reference,
11310                102,
11311                route.address,
11312                route.port + 1,
11313            ),
11314            None
11315        );
11316        assert_eq!(
11317            find_multicast_transmit_key(
11318                &state,
11319                90,
11320                call.wire_reference,
11321                102,
11322                route.address,
11323                route.port,
11324            ),
11325            Some(key)
11326        );
11327
11328        let expired = expire_multicast_reception_acknowledgements(&mut state, now);
11329        assert!(matches!(
11330            expired.as_slice(),
11331            [(
11332                actual_key,
11333                ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
11334            )] if *actual_key == key && passthrough_party_id.get() == 101
11335        ));
11336        assert!(expire_multicast_reception_acknowledgements(&mut state, now).is_empty());
11337
11338        let other_call = insert_call(&mut state, CallId(20), 1, Codec::Pcmu, CallState::Connected);
11339        let other_key = MulticastKey {
11340            conference_id: ConferenceId::new(91),
11341            call_id: other_call.call_id,
11342        };
11343        state.multicast.insert(
11344            other_key,
11345            MulticastSession {
11346                wire_call_reference: other_call.wire_reference,
11347                receive: Some(MulticastReceive {
11348                    request: MediaRequestIdentity::new(
11349                        3,
11350                        MediaRequestToken::new(103).expect("nonzero media token"),
11351                    )
11352                    .expect("nonzero generation"),
11353                    route,
11354                    state: MulticastReceiveState::Open,
11355                }),
11356                transmit: None,
11357            },
11358        );
11359
11360        let remaining = take_multicast_stops_for_call(&mut state, call.call_id);
11361        assert!(matches!(
11362            remaining.as_slice(),
11363            [ServerMessage::StopMulticastMediaTransmission { passthrough_party_id, .. }]
11364                if passthrough_party_id.get() == 102
11365        ));
11366        assert!(take_multicast_stops_for_call(&mut state, call.call_id).is_empty());
11367        assert!(state.multicast.contains_key(&other_key));
11368        let shutdown_stops = take_all_multicast_stops(&mut state);
11369        assert!(matches!(
11370            shutdown_stops.as_slice(),
11371            [ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }]
11372                if passthrough_party_id.get() == 103
11373        ));
11374        assert!(take_all_multicast_stops(&mut state).is_empty());
11375        assert!(state.multicast.is_empty());
11376    }
11377
11378    #[tokio::test]
11379    async fn multicast_session_enforces_transaction_identity_order_and_teardown() {
11380        let device = definition();
11381        let device_id = device.id.clone();
11382        let config = ServerConfig {
11383            bind: "127.0.0.1:0".parse().unwrap(),
11384            advertised_address: Ipv4Addr::LOCALHOST,
11385            ..ServerConfig::default()
11386        };
11387        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
11388        let address = server.local_addr().unwrap();
11389        let task = tokio::spawn(server.run());
11390        let mut phone = TcpStream::connect(address).await.unwrap();
11391        let mut decoder = FrameDecoder::new();
11392        let protocol = ProtocolVersion::V22;
11393        let call_id = CallId(41);
11394        let conference_id = ConferenceId::new(900);
11395        let first_route = multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcmu);
11396        let second_route = MulticastMediaRoute {
11397            address: "239.1.2.4".parse().unwrap(),
11398            port: 5006,
11399            ..first_route
11400        };
11401
11402        phone.write_all(&register_bytes(protocol)).await.unwrap();
11403        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
11404        assert!(matches!(
11405            events.recv().await,
11406            Some(Event::Device(DeviceEvent {
11407                session_generation: _,
11408                event: DeviceEventKind::Registered(_),
11409                ..
11410            }))
11411        ));
11412        phone
11413            .write_all(
11414                &ClientMessage::CapabilitiesResponse(vec![MediaCapability {
11415                    codec: Codec::Pcmu,
11416                    max_frames_per_packet: 2,
11417                    codec_parameters: [0; 8],
11418                }])
11419                .encode(protocol)
11420                .unwrap(),
11421            )
11422            .await
11423            .unwrap();
11424        assert!(matches!(
11425            events.recv().await,
11426            Some(Event::Device(DeviceEvent {
11427                session_generation: _,
11428                event: DeviceEventKind::Capabilities { .. },
11429                ..
11430            }))
11431        ));
11432        handle
11433            .send_confirmed(Command::new(
11434                device_id.clone(),
11435                CommandAction::BeginCall {
11436                    line_instance: LineInstance::new(1),
11437                    call_id,
11438                    codec: Codec::Pcmu,
11439                },
11440            ))
11441            .await
11442            .unwrap();
11443        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11444            matches!(message, ServerMessage::SelectSoftKeys { .. })
11445        })
11446        .await;
11447        let wire_call_reference = messages
11448            .iter()
11449            .find_map(|message| match message {
11450                ServerMessage::CallState {
11451                    state: CallState::OffHook,
11452                    call_reference,
11453                    ..
11454                } => Some(*call_reference),
11455                _ => None,
11456            })
11457            .expect("begin call omitted its wire reference");
11458
11459        for invalid_route in [
11460            MulticastMediaRoute {
11461                address: "192.0.2.1".parse().unwrap(),
11462                ..first_route
11463            },
11464            MulticastMediaRoute {
11465                codec: Codec::Pcma,
11466                ..first_route
11467            },
11468        ] {
11469            assert!(matches!(
11470                handle
11471                    .send_confirmed(Command::new(
11472                        device_id.clone(),
11473                        CommandAction::StartMulticastReception {
11474                            conference_id,
11475                            call_id,
11476                            route: invalid_route,
11477                            echo_cancellation: EchoCancellation::On,
11478                            g723_bitrate: G723BitRate::Rate5_3,
11479                        },
11480                    ))
11481                    .await,
11482                Err(ServerError::CommandWrite(_))
11483            ));
11484        }
11485        phone
11486            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
11487            .await
11488            .unwrap();
11489        read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
11490
11491        handle
11492            .send_confirmed(Command::new(
11493                device_id.clone(),
11494                CommandAction::StartMulticastReception {
11495                    conference_id,
11496                    call_id,
11497                    route: first_route,
11498                    echo_cancellation: EchoCancellation::On,
11499                    g723_bitrate: G723BitRate::Rate5_3,
11500                },
11501            ))
11502            .await
11503            .unwrap();
11504        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11505            matches!(message, ServerMessage::StartMulticastMediaReception(_))
11506        })
11507        .await;
11508        let first_receive_token = messages
11509            .iter()
11510            .find_map(|message| match message {
11511                ServerMessage::StartMulticastMediaReception(request) => {
11512                    assert_eq!(request.conference_id, conference_id);
11513                    assert_eq!(request.call_reference.get(), wire_call_reference);
11514                    assert_eq!(request.address, first_route.address);
11515                    assert_eq!(request.port, first_route.port);
11516                    assert_eq!(request.codec, first_route.codec);
11517                    Some(request.passthrough_party_id)
11518                }
11519                _ => None,
11520            })
11521            .expect("multicast reception omitted its request");
11522
11523        let mismatched = ClientMessage::MulticastMediaReceptionAck {
11524            status: MediaStatus::Ok,
11525            passthrough_party_id: first_receive_token,
11526            call_reference: CallReference::new(wire_call_reference + 1),
11527        }
11528        .encode(protocol)
11529        .unwrap();
11530        let exact = ClientMessage::MulticastMediaReceptionAck {
11531            status: MediaStatus::Ok,
11532            passthrough_party_id: first_receive_token,
11533            call_reference: CallReference::new(wire_call_reference),
11534        }
11535        .encode(protocol)
11536        .unwrap();
11537        let mut coalesced_prefix = mismatched;
11538        coalesced_prefix.push(exact[0]);
11539        phone.write_all(&coalesced_prefix).await.unwrap();
11540        assert!(
11541            tokio::time::timeout(Duration::from_millis(25), events.recv())
11542                .await
11543                .is_err(),
11544            "a mismatched acknowledgement completed the transaction"
11545        );
11546        for fragment in exact[1..].chunks(2) {
11547            phone.write_all(fragment).await.unwrap();
11548        }
11549        assert!(matches!(
11550            events.recv().await,
11551            Some(Event::Device(DeviceEvent { session_generation: _,
11552                event: DeviceEventKind::MulticastReceptionStarted {
11553                    conference_id: actual_conference,
11554                    call_id: actual_call,
11555                    route,
11556                },
11557                ..
11558            })) if actual_conference == conference_id && actual_call == call_id && route == first_route
11559        ));
11560        phone.write_all(&exact).await.unwrap();
11561        assert!(
11562            tokio::time::timeout(Duration::from_millis(25), events.recv())
11563                .await
11564                .is_err(),
11565            "a duplicate acknowledgement emitted another event"
11566        );
11567
11568        handle
11569            .send_confirmed(Command::new(
11570                device_id.clone(),
11571                CommandAction::StartMulticastReception {
11572                    conference_id,
11573                    call_id,
11574                    route: second_route,
11575                    echo_cancellation: EchoCancellation::Off,
11576                    g723_bitrate: G723BitRate::Rate6_3,
11577                },
11578            ))
11579            .await
11580            .unwrap();
11581        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11582            matches!(
11583                message,
11584                ServerMessage::StartMulticastMediaReception(request)
11585                    if request.address == second_route.address
11586            )
11587        })
11588        .await;
11589        let stop_index = messages
11590            .iter()
11591            .position(|message| {
11592                matches!(
11593                    message,
11594                    ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
11595                        if *passthrough_party_id == first_receive_token
11596                )
11597            })
11598            .expect("replacement did not stop the previous generation");
11599        let (start_index, second_receive_token) = messages
11600            .iter()
11601            .enumerate()
11602            .find_map(|(index, message)| match message {
11603                ServerMessage::StartMulticastMediaReception(request)
11604                    if request.address == second_route.address =>
11605                {
11606                    Some((index, request.passthrough_party_id))
11607                }
11608                _ => None,
11609            })
11610            .expect("replacement did not start a fresh generation");
11611        assert!(stop_index < start_index);
11612        assert_ne!(first_receive_token, second_receive_token);
11613
11614        let negative = ClientMessage::MulticastMediaReceptionAck {
11615            status: MediaStatus::OutOfChannels,
11616            passthrough_party_id: second_receive_token,
11617            call_reference: CallReference::new(wire_call_reference),
11618        }
11619        .encode(protocol)
11620        .unwrap();
11621        phone.write_all(&negative).await.unwrap();
11622        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11623            matches!(
11624                message,
11625                ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
11626                    if *passthrough_party_id == second_receive_token
11627            )
11628        })
11629        .await;
11630        assert!(matches!(
11631            events.recv().await,
11632            Some(Event::Device(DeviceEvent { session_generation: _,
11633                event: DeviceEventKind::MulticastReceptionFailed {
11634                    conference_id: actual_conference,
11635                    call_id: actual_call,
11636                    status: MediaStatus::OutOfChannels,
11637                },
11638                ..
11639            })) if actual_conference == conference_id && actual_call == call_id
11640        ));
11641        phone.write_all(&negative).await.unwrap();
11642        assert!(
11643            tokio::time::timeout(Duration::from_millis(25), events.recv())
11644                .await
11645                .is_err(),
11646            "a duplicate failure acknowledgement emitted another event"
11647        );
11648
11649        handle
11650            .send_confirmed(Command::new(
11651                device_id.clone(),
11652                CommandAction::StartMulticastTransmission {
11653                    conference_id,
11654                    call_id,
11655                    route: first_route,
11656                    precedence: 0,
11657                    silence_suppression: SilenceSuppression::Off,
11658                    max_frames_per_packet: 2,
11659                    g723_bitrate: G723BitRate::Rate5_3,
11660                },
11661            ))
11662            .await
11663            .unwrap();
11664        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11665            matches!(message, ServerMessage::StartMulticastMediaTransmission(_))
11666        })
11667        .await;
11668        let transmit_token = messages
11669            .iter()
11670            .find_map(|message| match message {
11671                ServerMessage::StartMulticastMediaTransmission(request) => {
11672                    Some(request.passthrough_party_id.get())
11673                }
11674                _ => None,
11675            })
11676            .expect("multicast transmission omitted its request");
11677        let started_event = events.recv().await;
11678        assert!(matches!(
11679            started_event,
11680            Some(Event::Device(DeviceEvent { session_generation: _,
11681                event: DeviceEventKind::MulticastTransmissionStarted {
11682                    conference_id: actual_conference,
11683                    call_id: actual_call,
11684                    route,
11685                },
11686                ..
11687            })) if actual_conference == conference_id && actual_call == call_id && route == first_route
11688        ));
11689        let mismatch_failure = ClientMessage::MediaTransmissionFailure {
11690            conference_id: conference_id.get(),
11691            passthrough_party_id: transmit_token,
11692            address: first_route.address,
11693            port: first_route.port + 1,
11694            call_reference: wire_call_reference,
11695            status: MediaStatus::UnspecifiedError,
11696        }
11697        .encode(protocol)
11698        .unwrap();
11699        phone.write_all(&mismatch_failure).await.unwrap();
11700        assert!(
11701            tokio::time::timeout(Duration::from_millis(25), events.recv())
11702                .await
11703                .is_err(),
11704            "a mismatched transmission failure retired the transaction"
11705        );
11706        let exact_failure = ClientMessage::MediaTransmissionFailure {
11707            conference_id: conference_id.get(),
11708            passthrough_party_id: transmit_token,
11709            address: first_route.address,
11710            port: first_route.port,
11711            call_reference: wire_call_reference,
11712            status: MediaStatus::UnspecifiedError,
11713        }
11714        .encode(protocol)
11715        .unwrap();
11716        phone.write_all(&exact_failure).await.unwrap();
11717        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11718            matches!(
11719                message,
11720                ServerMessage::StopMulticastMediaTransmission { passthrough_party_id, .. }
11721                    if passthrough_party_id.get() == transmit_token
11722            )
11723        })
11724        .await;
11725        let failure_event = events.recv().await;
11726        assert!(
11727            matches!(
11728                failure_event,
11729                Some(Event::Device(DeviceEvent { session_generation: _,
11730                    event: DeviceEventKind::MulticastTransmissionFailed {
11731                        conference_id: actual_conference,
11732                        call_id: actual_call,
11733                        status: MediaStatus::UnspecifiedError,
11734                        ..
11735                    },
11736                    ..
11737                })) if actual_conference == conference_id && actual_call == call_id
11738            ),
11739            "unexpected multicast transmission failure event: {failure_event:?}"
11740        );
11741        phone.write_all(&exact_failure).await.unwrap();
11742        assert!(
11743            tokio::time::timeout(Duration::from_millis(25), events.recv())
11744                .await
11745                .is_err(),
11746            "a duplicate transmission failure emitted another event"
11747        );
11748
11749        phone
11750            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
11751            .await
11752            .unwrap();
11753        read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
11754
11755        handle
11756            .send_confirmed(Command::new(
11757                device_id.clone(),
11758                CommandAction::StartMulticastReception {
11759                    conference_id,
11760                    call_id,
11761                    route: first_route,
11762                    echo_cancellation: EchoCancellation::On,
11763                    g723_bitrate: G723BitRate::Rate5_3,
11764                },
11765            ))
11766            .await
11767            .unwrap();
11768        read_until_message(
11769            &mut phone,
11770            &mut decoder,
11771            id::START_MULTICAST_MEDIA_RECEPTION,
11772        )
11773        .await;
11774        handle
11775            .send_confirmed(Command::new(
11776                device_id.clone(),
11777                CommandAction::StartMulticastTransmission {
11778                    conference_id,
11779                    call_id,
11780                    route: first_route,
11781                    precedence: 0,
11782                    silence_suppression: SilenceSuppression::Off,
11783                    max_frames_per_packet: 2,
11784                    g723_bitrate: G723BitRate::Rate5_3,
11785                },
11786            ))
11787            .await
11788            .unwrap();
11789        read_until_message(
11790            &mut phone,
11791            &mut decoder,
11792            id::START_MULTICAST_MEDIA_TRANSMISSION,
11793        )
11794        .await;
11795        assert!(matches!(
11796            events.recv().await,
11797            Some(Event::Device(DeviceEvent {
11798                session_generation: _,
11799                event: DeviceEventKind::MulticastTransmissionStarted { .. },
11800                ..
11801            }))
11802        ));
11803        handle
11804            .send_confirmed(Command::new(
11805                device_id.clone(),
11806                CommandAction::CloseCall { call_id },
11807            ))
11808            .await
11809            .unwrap();
11810        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11811            matches!(
11812                message,
11813                ServerMessage::CallState {
11814                    state: CallState::OnHook,
11815                    ..
11816                }
11817            )
11818        })
11819        .await;
11820        let receive_stop = messages
11821            .iter()
11822            .position(|message| {
11823                matches!(message, ServerMessage::StopMulticastMediaReception { .. })
11824            })
11825            .expect("call close omitted multicast reception stop");
11826        let transmit_stop = messages
11827            .iter()
11828            .position(|message| {
11829                matches!(
11830                    message,
11831                    ServerMessage::StopMulticastMediaTransmission { .. }
11832                )
11833            })
11834            .expect("call close omitted multicast transmission stop");
11835        let on_hook = messages
11836            .iter()
11837            .position(|message| {
11838                matches!(
11839                    message,
11840                    ServerMessage::CallState {
11841                        state: CallState::OnHook,
11842                        ..
11843                    }
11844                )
11845            })
11846            .unwrap();
11847        assert!(receive_stop < transmit_stop && transmit_stop < on_hook);
11848
11849        let disconnect_call = CallId(42);
11850        handle
11851            .send_confirmed(Command::new(
11852                device_id.clone(),
11853                CommandAction::BeginCall {
11854                    line_instance: LineInstance::new(1),
11855                    call_id: disconnect_call,
11856                    codec: Codec::Pcmu,
11857                },
11858            ))
11859            .await
11860            .unwrap();
11861        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
11862        for action in [
11863            CommandAction::StartMulticastReception {
11864                conference_id,
11865                call_id: disconnect_call,
11866                route: first_route,
11867                echo_cancellation: EchoCancellation::On,
11868                g723_bitrate: G723BitRate::Rate5_3,
11869            },
11870            CommandAction::StartMulticastTransmission {
11871                conference_id,
11872                call_id: disconnect_call,
11873                route: first_route,
11874                precedence: 0,
11875                silence_suppression: SilenceSuppression::Off,
11876                max_frames_per_packet: 2,
11877                g723_bitrate: G723BitRate::Rate5_3,
11878            },
11879        ] {
11880            handle
11881                .send_confirmed(Command::new(device_id.clone(), action))
11882                .await
11883                .unwrap();
11884        }
11885        read_until_message(
11886            &mut phone,
11887            &mut decoder,
11888            id::START_MULTICAST_MEDIA_TRANSMISSION,
11889        )
11890        .await;
11891        assert!(matches!(
11892            events.recv().await,
11893            Some(Event::Device(DeviceEvent { session_generation: _,
11894                event: DeviceEventKind::MulticastTransmissionStarted {
11895                    call_id: actual_call,
11896                    ..
11897                },
11898                ..
11899            })) if actual_call == disconnect_call
11900        ));
11901
11902        let mut replacement = definition();
11903        replacement.description = "replacement".into();
11904        handle.reconfigure([replacement]).await.unwrap();
11905        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11906            matches!(
11907                message,
11908                ServerMessage::StopMulticastMediaTransmission { .. }
11909            )
11910        })
11911        .await;
11912        let receive_stops = messages
11913            .iter()
11914            .enumerate()
11915            .filter_map(|(index, message)| {
11916                matches!(message, ServerMessage::StopMulticastMediaReception { .. })
11917                    .then_some(index)
11918            })
11919            .collect::<Vec<_>>();
11920        let transmit_stops = messages
11921            .iter()
11922            .enumerate()
11923            .filter_map(|(index, message)| {
11924                matches!(
11925                    message,
11926                    ServerMessage::StopMulticastMediaTransmission { .. }
11927                )
11928                .then_some(index)
11929            })
11930            .collect::<Vec<_>>();
11931        assert_eq!(receive_stops.len(), 1);
11932        assert_eq!(transmit_stops.len(), 1);
11933        assert!(receive_stops[0] < transmit_stops[0]);
11934        assert!(matches!(
11935            events.recv().await,
11936            Some(Event::Device(DeviceEvent {
11937                session_generation: _,
11938                event: DeviceEventKind::Disconnected {},
11939                ..
11940            }))
11941        ));
11942
11943        handle.shutdown().await.unwrap();
11944        task.await.unwrap().unwrap();
11945    }
11946
11947    #[tokio::test(start_paused = true)]
11948    async fn multicast_receive_deadline_stops_and_retires_the_pending_generation() {
11949        let device = definition();
11950        let device_id = device.id.clone();
11951        let config = ServerConfig {
11952            bind: "127.0.0.1:0".parse().unwrap(),
11953            advertised_address: Ipv4Addr::LOCALHOST,
11954            ..ServerConfig::default()
11955        };
11956        let (server, handle, mut events, ingress) = Server::with_ingress(config, [device]).unwrap();
11957        let task = tokio::spawn(server.run());
11958        let (server_stream, mut phone) = tokio::io::duplex(8_192);
11959        ingress
11960            .accept(
11961                server_stream,
11962                SocketAddr::from(([127, 0, 0, 1], 40_000)),
11963                SocketAddr::from(([127, 0, 0, 1], 2_000)),
11964                StationTransport::Clear,
11965            )
11966            .await
11967            .unwrap();
11968        let mut decoder = FrameDecoder::new();
11969        let protocol = ProtocolVersion::V22;
11970        let call_id = CallId(43);
11971        let conference_id = ConferenceId::new(901);
11972        let route = multicast_route("239.1.2.5".parse().unwrap(), Codec::Pcmu);
11973
11974        phone.write_all(&register_bytes(protocol)).await.unwrap();
11975        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
11976        assert!(matches!(
11977            events.recv().await,
11978            Some(Event::Device(DeviceEvent {
11979                session_generation: _,
11980                event: DeviceEventKind::Registered(_),
11981                ..
11982            }))
11983        ));
11984        phone
11985            .write_all(
11986                &ClientMessage::CapabilitiesResponse(vec![MediaCapability {
11987                    codec: Codec::Pcmu,
11988                    max_frames_per_packet: 1,
11989                    codec_parameters: [0; 8],
11990                }])
11991                .encode(protocol)
11992                .unwrap(),
11993            )
11994            .await
11995            .unwrap();
11996        assert!(matches!(
11997            events.recv().await,
11998            Some(Event::Device(DeviceEvent {
11999                session_generation: _,
12000                event: DeviceEventKind::Capabilities { .. },
12001                ..
12002            }))
12003        ));
12004        handle
12005            .send_confirmed(Command::new(
12006                device_id.clone(),
12007                CommandAction::BeginCall {
12008                    line_instance: LineInstance::new(1),
12009                    call_id,
12010                    codec: Codec::Pcmu,
12011                },
12012            ))
12013            .await
12014            .unwrap();
12015        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
12016        handle
12017            .send_confirmed(Command::new(
12018                device_id,
12019                CommandAction::StartMulticastReception {
12020                    conference_id,
12021                    call_id,
12022                    route,
12023                    echo_cancellation: EchoCancellation::On,
12024                    g723_bitrate: G723BitRate::Rate5_3,
12025                },
12026            ))
12027            .await
12028            .unwrap();
12029        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
12030            matches!(message, ServerMessage::StartMulticastMediaReception(_))
12031        })
12032        .await;
12033        let request = messages
12034            .iter()
12035            .find_map(|message| match message {
12036                ServerMessage::StartMulticastMediaReception(request) => Some(request.clone()),
12037                _ => None,
12038            })
12039            .unwrap();
12040
12041        tokio::time::advance(HANDSET_ACKNOWLEDGEMENT_TIMEOUT + Duration::from_millis(100)).await;
12042        read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
12043            matches!(
12044                message,
12045                ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
12046                    if *passthrough_party_id == request.passthrough_party_id
12047            )
12048        })
12049        .await;
12050        assert!(matches!(
12051            events.recv().await,
12052            Some(Event::Device(DeviceEvent { session_generation: _,
12053                event: DeviceEventKind::MulticastReceptionTimedOut {
12054                    conference_id: actual_conference,
12055                    call_id: actual_call,
12056                },
12057                ..
12058            })) if actual_conference == conference_id && actual_call == call_id
12059        ));
12060        phone
12061            .write_all(
12062                &ClientMessage::MulticastMediaReceptionAck {
12063                    status: MediaStatus::Ok,
12064                    passthrough_party_id: request.passthrough_party_id,
12065                    call_reference: request.call_reference,
12066                }
12067                .encode(protocol)
12068                .unwrap(),
12069            )
12070            .await
12071            .unwrap();
12072        assert!(
12073            tokio::time::timeout(Duration::from_millis(25), events.recv())
12074                .await
12075                .is_err(),
12076            "a late acknowledgement resurrected the expired generation"
12077        );
12078        phone
12079            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
12080            .await
12081            .unwrap();
12082        read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
12083
12084        handle.shutdown().await.unwrap();
12085        task.await.unwrap().unwrap();
12086    }
12087
12088    fn mixed_definition() -> DeviceDefinition {
12089        let mut device = definition();
12090        device.buttons.extend([
12091            ButtonDefinition::SpeedDial(SpeedDialDefinition {
12092                instance: 1,
12093                number: "2001".into(),
12094                display_name: "Reception".into(),
12095            }),
12096            ButtonDefinition::Feature(FeatureDefinition {
12097                instance: 1,
12098                label: "DND".into(),
12099                feature: ButtonType::DoNotDisturb,
12100            }),
12101            ButtonDefinition::Service(ServiceDefinition {
12102                instance: 1,
12103                label: "Directory".into(),
12104                url: "http://services.invalid/directory".into(),
12105            }),
12106            ButtonDefinition::Unused,
12107            ButtonDefinition::BlfSpeedDial(BlfSpeedDialDefinition {
12108                instance: 2,
12109                number: "2002".into(),
12110                display_name: "Warehouse".into(),
12111                hint: "2002@internal".into(),
12112            }),
12113        ]);
12114        device
12115    }
12116
12117    fn profile_with(mode: KeyMode, actions: Vec<SoftKey>) -> SoftKeyProfile {
12118        let default = SoftKeyProfile::default();
12119        SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|candidate| {
12120            if candidate == mode {
12121                (candidate, actions.clone())
12122            } else {
12123                (candidate, default.actions(candidate).to_vec())
12124            }
12125        }))
12126        .unwrap()
12127    }
12128
12129    fn session_call(call_id: u64) -> SessionCall {
12130        SessionCall {
12131            call_id: CallId(call_id),
12132            wire_reference: call_id as u32,
12133            line_instance: 1,
12134            media: CallMedia::new(Codec::Pcmu),
12135            video_receive: VideoReceive::default(),
12136            video_transmit: VideoTransmit::default(),
12137            state: CallState::Connected,
12138            history_disposition: CallHistoryDisposition::Placed,
12139            dialed_number: String::new(),
12140            statistics_directory_number: String::new(),
12141            transfer_role: None,
12142        }
12143    }
12144
12145    #[test]
12146    fn every_call_state_and_soft_key_has_an_explicit_availability_result() {
12147        let expected_modes = [
12148            (CallState::OffHook, KeyMode::OffHook),
12149            (CallState::OnHook, KeyMode::OnHook),
12150            (CallState::RingOut, KeyMode::RingOut),
12151            (CallState::RingIn, KeyMode::RingIn),
12152            (CallState::Connected, KeyMode::Connected),
12153            (CallState::Busy, KeyMode::OffHook),
12154            (CallState::Congestion, KeyMode::OffHook),
12155            (CallState::Hold, KeyMode::OnHold),
12156            (CallState::CallWaiting, KeyMode::RingIn),
12157            (CallState::Transfer, KeyMode::ConnectedTransfer),
12158            (CallState::Park, KeyMode::OnHook),
12159            (CallState::Proceed, KeyMode::RingOut),
12160            (CallState::RemoteMultiline, KeyMode::OnHookStealable),
12161            (CallState::InvalidNumber, KeyMode::OffHook),
12162            (CallState::HoldYellow, KeyMode::OnHold),
12163            (CallState::IntercomOneWay, KeyMode::OffHook),
12164            (CallState::HoldRed, KeyMode::OnHold),
12165        ];
12166        assert_eq!(CallState::ALL_KNOWN.len(), expected_modes.len());
12167        assert_eq!(
12168            CallState::ALL_KNOWN,
12169            expected_modes
12170                .iter()
12171                .map(|(state, _)| *state)
12172                .collect::<Vec<_>>()
12173        );
12174        assert_eq!(
12175            key_mode_for_call_state(CallState::Unknown(99)),
12176            KeyMode::OnHook
12177        );
12178
12179        let profile = SoftKeyProfile::built_in();
12180        for (state, expected_mode) in expected_modes {
12181            let mode = key_mode_for_call_state(state);
12182            assert_eq!(mode, expected_mode, "unexpected key mode for {state:?}");
12183            let expected_actions: &[SoftKey] = match mode {
12184                KeyMode::OnHook => &[SoftKey::NewCall],
12185                KeyMode::Connected | KeyMode::ConnectedTransfer => {
12186                    &[SoftKey::Hold, SoftKey::EndCall, SoftKey::Transfer]
12187                }
12188                KeyMode::OnHold | KeyMode::OffHookFeature | KeyMode::HoldConference => {
12189                    &[SoftKey::Resume, SoftKey::NewCall, SoftKey::EndCall]
12190                }
12191                KeyMode::RingIn => &[SoftKey::Answer, SoftKey::EndCall],
12192                KeyMode::OffHook | KeyMode::RingOut => &[SoftKey::EndCall],
12193                KeyMode::DigitsFollowing => &[SoftKey::Backspace, SoftKey::EndCall, SoftKey::Dial],
12194                KeyMode::ConnectedConference => &[SoftKey::Hold, SoftKey::EndCall],
12195                KeyMode::OnHookStealable => &[SoftKey::Intercept, SoftKey::NewCall],
12196                KeyMode::InUseHint | KeyMode::Empty | KeyMode::Unknown(_) => &[],
12197            };
12198            for &soft_key in SoftKey::ALL_KNOWN {
12199                assert_eq!(
12200                    profile.allows(mode, soft_key),
12201                    expected_actions.contains(&soft_key),
12202                    "state={state:?} mode={mode:?} soft_key={soft_key:?}"
12203                );
12204            }
12205        }
12206    }
12207
12208    #[test]
12209    fn blf_updates_map_every_state_to_icon_and_lamp() {
12210        let cases = [
12211            (BlfState::Idle, BusyLampFieldState::Idle, LampMode::Off),
12212            (
12213                BlfState::Ringing,
12214                BusyLampFieldState::Alerting,
12215                LampMode::Blink,
12216            ),
12217            (BlfState::Busy, BusyLampFieldState::InUse, LampMode::On),
12218            (BlfState::Held, BusyLampFieldState::InUse, LampMode::Hold),
12219            (
12220                BlfState::Unavailable,
12221                BusyLampFieldState::UnknownState,
12222                LampMode::Flash,
12223            ),
12224            (
12225                BlfState::Unknown,
12226                BusyLampFieldState::UnknownState,
12227                LampMode::Wink,
12228            ),
12229        ];
12230
12231        for (state, expected_icon, expected_lamp) in cases {
12232            let [speed_dial, feature, lamp] =
12233                blf_status_messages(7, "4100", "Support", state, None);
12234            assert!(matches!(
12235                speed_dial,
12236                ServerMessage::SpeedDialStatus {
12237                    instance: 7,
12238                    ref number,
12239                    ref display_name,
12240                } if number == "4100" && display_name == "Support"
12241            ));
12242            assert!(matches!(
12243                feature,
12244                ServerMessage::FeatureStatus {
12245                    instance: 7,
12246                    button_type: ButtonType::BlfSpeedDial,
12247                    ref label,
12248                    state,
12249                } if label == "Support" && state == expected_icon.wire_value()
12250            ));
12251            assert!(matches!(
12252                lamp,
12253                ServerMessage::SetLamp {
12254                    stimulus: ButtonType::BlfSpeedDial,
12255                    instance: 7,
12256                    mode,
12257                } if mode == expected_lamp
12258            ));
12259        }
12260    }
12261
12262    #[test]
12263    fn hinted_ringing_policy_adds_only_a_ringing_notification() {
12264        let caller = BlfCallerInfo {
12265            name: "Taylor".into(),
12266            number: "5550100".into(),
12267        };
12268        let mut disabled = definition();
12269        assert_eq!(
12270            hinted_ringing_notification(&disabled, "Dispatch", Some(&caller), BlfState::Ringing,),
12271            None
12272        );
12273
12274        disabled.ui.hinted_ringing_notification = true;
12275        assert_eq!(
12276            hinted_ringing_notification(&disabled, "Dispatch", Some(&caller), BlfState::Ringing,),
12277            Some(HandsetStatusMessage::Display {
12278                text: "Dispatch is ringing: Taylor (5550100)".into(),
12279                timeout_seconds: 5,
12280                priority: None,
12281            })
12282        );
12283        for state in [
12284            BlfState::Idle,
12285            BlfState::Busy,
12286            BlfState::Held,
12287            BlfState::Unavailable,
12288            BlfState::Unknown,
12289        ] {
12290            assert_eq!(
12291                hinted_ringing_notification(&disabled, "Dispatch", Some(&caller), state),
12292                None,
12293                "non-ringing BLF state {state:?} must not be replaced by a notification"
12294            );
12295            assert_eq!(
12296                blf_status_messages(7, "4100", "Dispatch", state, Some(&caller)).len(),
12297                3,
12298                "ordinary BLF projection must remain intact"
12299            );
12300        }
12301    }
12302
12303    #[test]
12304    fn last_number_uses_the_configured_terminator_recording_policy() {
12305        let without_terminator = ServerConfig {
12306            dial_terminator: Digit::Star,
12307            record_dial_terminator: false,
12308            ..ServerConfig::default()
12309        };
12310        let with_terminator = ServerConfig {
12311            record_dial_terminator: true,
12312            ..without_terminator.clone()
12313        };
12314
12315        assert_eq!(
12316            normalized_last_number(" 5551212* ", &without_terminator),
12317            Some("5551212".into())
12318        );
12319        assert_eq!(
12320            normalized_last_number(" 5551212* ", &with_terminator),
12321            Some("5551212*".into())
12322        );
12323        assert_eq!(
12324            normalized_last_number("5551212#", &without_terminator),
12325            Some("5551212#".into()),
12326            "non-terminator DTMF must remain part of the remembered number"
12327        );
12328        assert_eq!(normalized_last_number("***", &without_terminator), None);
12329    }
12330
12331    #[tokio::test]
12332    async fn invalid_server_dial_terminator_is_rejected_before_binding() {
12333        let result = Server::bind(
12334            ServerConfig {
12335                dial_terminator: Digit::Unknown(99),
12336                ..ServerConfig::default()
12337            },
12338            [definition()],
12339        )
12340        .await;
12341        assert!(matches!(result, Err(ServerError::InvalidConfig(_))));
12342    }
12343
12344    #[test]
12345    fn invalid_server_signaling_qos_is_rejected_for_external_ingress() {
12346        let result = Server::with_ingress(
12347            ServerConfig {
12348                signaling_qos: SignalingQos::new(64, 0),
12349                ..ServerConfig::default()
12350            },
12351            [definition()],
12352        );
12353
12354        assert!(
12355            matches!(result, Err(ServerError::InvalidConfig(message)) if message.contains("DSCP 64"))
12356        );
12357    }
12358
12359    #[test]
12360    fn invalid_failover_policy_is_rejected_before_ingress_starts() {
12361        let route = |priority| SignalingServerRoute {
12362            priority,
12363            name: format!("node-{priority}"),
12364            address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, priority)),
12365            clear_port: NonZeroU16::new(2000),
12366            secure_port: None,
12367        };
12368        let invalid = [
12369            ServerConfig {
12370                advertised_address: Ipv4Addr::UNSPECIFIED,
12371                ..ServerConfig::default()
12372            },
12373            ServerConfig {
12374                secondary_keepalive_seconds: 4,
12375                ..ServerConfig::default()
12376            },
12377            ServerConfig {
12378                registration_tokens: RegistrationTokenPolicy {
12379                    backoff: Duration::from_secs(29),
12380                    ..RegistrationTokenPolicy::default()
12381                },
12382                ..ServerConfig::default()
12383            },
12384            ServerConfig {
12385                signaling_servers: vec![route(1), route(1)],
12386                ..ServerConfig::default()
12387            },
12388            ServerConfig {
12389                signaling_servers: vec![route(2)],
12390                ..ServerConfig::default()
12391            },
12392            ServerConfig {
12393                signaling_servers: (1..=6).map(route).collect(),
12394                ..ServerConfig::default()
12395            },
12396        ];
12397
12398        for config in invalid {
12399            assert!(matches!(
12400                Server::with_ingress(config, [definition()]),
12401                Err(ServerError::InvalidConfig(_))
12402            ));
12403        }
12404    }
12405
12406    #[test]
12407    fn blf_update_only_displays_explicitly_permitted_caller_information() {
12408        let [_, without_caller, _] =
12409            blf_status_messages(2, "4200", "Dispatch", BlfState::Ringing, None);
12410        assert!(matches!(
12411            without_caller,
12412            ServerMessage::FeatureStatus { label, .. } if label == "Dispatch"
12413        ));
12414
12415        let caller = BlfCallerInfo {
12416            name: "Taylor".into(),
12417            number: "5550100".into(),
12418        };
12419        let [_, with_caller, _] =
12420            blf_status_messages(2, "4200", "Dispatch", BlfState::Ringing, Some(&caller));
12421        assert!(matches!(
12422            with_caller,
12423            ServerMessage::FeatureStatus { label, .. }
12424                if label == "Dispatch: Taylor (5550100)"
12425        ));
12426    }
12427
12428    #[test]
12429    fn button_template_uses_ordered_semantic_device_buttons() {
12430        let device = mixed_definition();
12431
12432        assert_eq!(
12433            button_template(&device),
12434            vec![
12435                ButtonTemplateEntry {
12436                    instance: 1,
12437                    button_type: ButtonType::Line,
12438                },
12439                ButtonTemplateEntry {
12440                    instance: 1,
12441                    button_type: ButtonType::SpeedDial,
12442                },
12443                ButtonTemplateEntry {
12444                    instance: 1,
12445                    button_type: ButtonType::DoNotDisturb,
12446                },
12447                ButtonTemplateEntry {
12448                    instance: 1,
12449                    button_type: ButtonType::ServiceUrl,
12450                },
12451                ButtonTemplateEntry {
12452                    instance: 0,
12453                    button_type: ButtonType::Unused,
12454                },
12455                ButtonTemplateEntry {
12456                    instance: 2,
12457                    button_type: ButtonType::BlfSpeedDial,
12458                },
12459            ]
12460        );
12461    }
12462
12463    #[test]
12464    fn expansion_module_reserves_exact_model_capacity_and_places_configured_keys() {
12465        let mut device = definition();
12466        device.buttons.extend([
12467            ButtonDefinition::AddonModule(crate::types::AddonModuleDefinition {
12468                slot: 1,
12469                device_type: DeviceType::CiscoAddon7914,
12470            }),
12471            ButtonDefinition::SpeedDial(SpeedDialDefinition {
12472                instance: 1,
12473                number: "2001".into(),
12474                display_name: "Reception".into(),
12475            }),
12476            ButtonDefinition::Feature(FeatureDefinition {
12477                instance: 1,
12478                label: "DND".into(),
12479                feature: ButtonType::DoNotDisturb,
12480            }),
12481        ]);
12482        device.validate().unwrap();
12483
12484        let layout = button_template(&device);
12485        assert_eq!(layout.len(), 15, "one base key plus fourteen sidecar keys");
12486        assert_eq!(
12487            &layout[..3],
12488            [
12489                ButtonTemplateEntry {
12490                    instance: 1,
12491                    button_type: ButtonType::Line,
12492                },
12493                ButtonTemplateEntry {
12494                    instance: 1,
12495                    button_type: ButtonType::SpeedDial,
12496                },
12497                ButtonTemplateEntry {
12498                    instance: 1,
12499                    button_type: ButtonType::DoNotDisturb,
12500                },
12501            ]
12502        );
12503        assert!(
12504            layout[3..]
12505                .iter()
12506                .all(|button| { button.instance == 0 && button.button_type == ButtonType::Unused })
12507        );
12508
12509        let mut over_capacity = definition();
12510        over_capacity.buttons.push(ButtonDefinition::AddonModule(
12511            crate::types::AddonModuleDefinition {
12512                slot: 1,
12513                device_type: DeviceType::AddonSpa500s,
12514            },
12515        ));
12516        over_capacity
12517            .buttons
12518            .extend(std::iter::repeat_n(ButtonDefinition::Unused, 33));
12519        assert!(matches!(
12520            over_capacity.validate(),
12521            Err(CodecError::InvalidDefinition(message))
12522                if message.contains("more buttons than its addon module provides")
12523        ));
12524    }
12525
12526    #[test]
12527    fn static_button_statuses_use_typed_instances_and_safe_unknowns() {
12528        let device = mixed_definition();
12529
12530        assert_eq!(
12531            speed_dial_status(&device, 1),
12532            ServerMessage::SpeedDialStatus {
12533                instance: 1,
12534                number: "2001".into(),
12535                display_name: "Reception".into(),
12536            }
12537        );
12538        assert_eq!(
12539            speed_dial_status(&device, 99),
12540            ServerMessage::SpeedDialStatus {
12541                instance: 99,
12542                number: String::new(),
12543                display_name: String::new(),
12544            }
12545        );
12546        assert_eq!(
12547            feature_status(&device, 1, 0),
12548            Some(ServerMessage::FeatureStatus {
12549                instance: 1,
12550                button_type: ButtonType::DoNotDisturb,
12551                label: "DND".into(),
12552                state: 0,
12553            })
12554        );
12555        assert_eq!(feature_status(&device, 99, 0), None);
12556        assert_eq!(
12557            speed_dial_status(&device, 2),
12558            ServerMessage::SpeedDialStatus {
12559                instance: 2,
12560                number: "2002".into(),
12561                display_name: "Warehouse".into(),
12562            }
12563        );
12564        assert_eq!(
12565            feature_status(&device, 2, 1),
12566            Some(ServerMessage::FeatureStatus {
12567                instance: 2,
12568                button_type: ButtonType::BlfSpeedDial,
12569                label: "Warehouse".into(),
12570                state: BusyLampFieldState::UnknownState.wire_value(),
12571            })
12572        );
12573        assert_eq!(
12574            service_url_status(&device, 1),
12575            Some(ServerMessage::ServiceUrlStatus {
12576                index: 1,
12577                url: "http://services.invalid/directory".into(),
12578                label: "Directory".into(),
12579                extension_text: String::new(),
12580            })
12581        );
12582        assert_eq!(service_url_status(&device, 99), None);
12583    }
12584
12585    #[test]
12586    fn do_not_disturb_status_preserves_exact_mode_and_button_behavior() {
12587        let device = mixed_definition();
12588
12589        for (mode, state, lamp) in [
12590            (DoNotDisturbMode::Off, 0x010000, LampMode::Off),
12591            (DoNotDisturbMode::Reject, 0x020202, LampMode::On),
12592            (DoNotDisturbMode::Silent, 0x030302, LampMode::Blink),
12593        ] {
12594            assert_eq!(
12595                do_not_disturb_state_messages(
12596                    &device,
12597                    1,
12598                    mode,
12599                    DoNotDisturbButtonMode::Cycle,
12600                    ProtocolVersion::V22,
12601                ),
12602                Some([
12603                    ServerMessage::FeatureStatus {
12604                        instance: 1,
12605                        button_type: ButtonType::MultiblinkFeature,
12606                        label: "DND".into(),
12607                        state,
12608                    },
12609                    ServerMessage::SetLamp {
12610                        stimulus: ButtonType::DoNotDisturb,
12611                        instance: 1,
12612                        mode: lamp,
12613                    },
12614                ])
12615            );
12616        }
12617
12618        for (button_mode, mode, enabled, lamp) in [
12619            (
12620                DoNotDisturbButtonMode::Silent,
12621                DoNotDisturbMode::Silent,
12622                1,
12623                LampMode::Blink,
12624            ),
12625            (
12626                DoNotDisturbButtonMode::Silent,
12627                DoNotDisturbMode::Reject,
12628                0,
12629                LampMode::Off,
12630            ),
12631            (
12632                DoNotDisturbButtonMode::Reject,
12633                DoNotDisturbMode::Reject,
12634                1,
12635                LampMode::On,
12636            ),
12637            (
12638                DoNotDisturbButtonMode::Reject,
12639                DoNotDisturbMode::Silent,
12640                0,
12641                LampMode::Off,
12642            ),
12643        ] {
12644            let [feature, lamp_message] =
12645                do_not_disturb_state_messages(&device, 1, mode, button_mode, ProtocolVersion::V22)
12646                    .unwrap();
12647            assert!(matches!(
12648                feature,
12649                ServerMessage::FeatureStatus {
12650                    button_type: ButtonType::DoNotDisturb,
12651                    state,
12652                    ..
12653                } if state == enabled
12654            ));
12655            assert!(matches!(
12656                lamp_message,
12657                ServerMessage::SetLamp { mode, .. } if mode == lamp
12658            ));
12659        }
12660
12661        let [legacy_feature, legacy_lamp] = do_not_disturb_state_messages(
12662            &device,
12663            1,
12664            DoNotDisturbMode::Silent,
12665            DoNotDisturbButtonMode::Cycle,
12666            ProtocolVersion::V15,
12667        )
12668        .unwrap();
12669        assert!(matches!(
12670            legacy_feature,
12671            ServerMessage::FeatureStatus {
12672                button_type: ButtonType::DoNotDisturb,
12673                state: 1,
12674                ..
12675            }
12676        ));
12677        assert!(matches!(
12678            legacy_lamp,
12679            ServerMessage::SetLamp {
12680                mode: LampMode::Blink,
12681                ..
12682            }
12683        ));
12684
12685        assert!(
12686            do_not_disturb_state_messages(
12687                &device,
12688                99,
12689                DoNotDisturbMode::Reject,
12690                DoNotDisturbButtonMode::Cycle,
12691                ProtocolVersion::V22,
12692            )
12693            .is_none()
12694        );
12695    }
12696
12697    #[test]
12698    fn shared_line_appearances_keep_distinct_instances_and_labels() {
12699        let logical_line = LineDefinition {
12700            number: "4100".into(),
12701            display_name: "Operations".into(),
12702        };
12703        let mut primary = LineAppearance::new(1, logical_line.clone());
12704        primary.label = Some("Operations primary".into());
12705        let mut shared = LineAppearance::new(2, logical_line);
12706        shared.label = Some("Operations shared".into());
12707        let device = DeviceDefinition {
12708            id: DeviceId::new("SEP00AABBCCDDEE").unwrap(),
12709            description: "Shared line phone".into(),
12710            transport: StationTransportRequirement::Either,
12711            signaling_qos: None,
12712            buttons: vec![
12713                ButtonDefinition::Line(primary),
12714                ButtonDefinition::Line(shared),
12715            ],
12716            soft_keys: SoftKeyProfile::default(),
12717            ui: Default::default(),
12718        };
12719
12720        device.validate().unwrap();
12721        assert_eq!(
12722            line_status(&device, 1),
12723            Some(ServerMessage::LineStatus {
12724                instance: 1,
12725                number: "4100".into(),
12726                display_name: "Operations primary".into(),
12727            })
12728        );
12729        assert_eq!(
12730            line_status(&device, 2),
12731            Some(ServerMessage::LineStatus {
12732                instance: 2,
12733                number: "4100".into(),
12734                display_name: "Operations shared".into(),
12735            })
12736        );
12737        assert_eq!(line_status(&device, 3), None);
12738    }
12739
12740    #[tokio::test]
12741    async fn registered_phone_receives_its_mixed_button_template() {
12742        let device = mixed_definition();
12743        let expected = button_template(&device);
12744        let config = ServerConfig {
12745            bind: "127.0.0.1:0".parse().unwrap(),
12746            advertised_address: Ipv4Addr::LOCALHOST,
12747            ..ServerConfig::default()
12748        };
12749        let (server, handle, _events) = Server::bind(config, [device]).await.unwrap();
12750        let address = server.local_addr().unwrap();
12751        let task = tokio::spawn(server.run());
12752        let mut phone = TcpStream::connect(address).await.unwrap();
12753        let mut decoder = FrameDecoder::new();
12754
12755        phone
12756            .write_all(&register_bytes(ProtocolVersion::V22))
12757            .await
12758            .unwrap();
12759        read_until_message(&mut phone, &mut decoder, id::REGISTER_ACK).await;
12760        phone
12761            .write_all(
12762                &ClientMessage::ButtonTemplateRequest
12763                    .encode(ProtocolVersion::V22)
12764                    .unwrap(),
12765            )
12766            .await
12767            .unwrap();
12768        let frames = read_until_message(&mut phone, &mut decoder, id::BUTTON_TEMPLATE).await;
12769        let frame = frames
12770            .into_iter()
12771            .find(|frame| frame.message_id == id::BUTTON_TEMPLATE)
12772            .unwrap();
12773        assert_eq!(
12774            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12775            ServerMessage::ButtonTemplate { buttons: expected }
12776        );
12777
12778        phone
12779            .write_all(
12780                &ClientMessage::SpeedDialStatusRequest {
12781                    speed_dial_instance: 1,
12782                }
12783                .encode(ProtocolVersion::V22)
12784                .unwrap(),
12785            )
12786            .await
12787            .unwrap();
12788        let frames =
12789            read_until_message(&mut phone, &mut decoder, id::SPEED_DIAL_STAT_DYNAMIC).await;
12790        let frame = frames
12791            .into_iter()
12792            .find(|frame| frame.message_id == id::SPEED_DIAL_STAT_DYNAMIC)
12793            .unwrap();
12794        assert_eq!(
12795            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12796            ServerMessage::SpeedDialStatus {
12797                instance: 1,
12798                number: "2001".into(),
12799                display_name: "Reception".into(),
12800            }
12801        );
12802
12803        phone
12804            .write_all(
12805                &ClientMessage::SpeedDialStatusRequest {
12806                    speed_dial_instance: 2,
12807                }
12808                .encode(ProtocolVersion::V22)
12809                .unwrap(),
12810            )
12811            .await
12812            .unwrap();
12813        let frames =
12814            read_until_message(&mut phone, &mut decoder, id::SPEED_DIAL_STAT_DYNAMIC).await;
12815        let frame = frames
12816            .into_iter()
12817            .find(|frame| frame.message_id == id::SPEED_DIAL_STAT_DYNAMIC)
12818            .unwrap();
12819        assert_eq!(
12820            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12821            ServerMessage::SpeedDialStatus {
12822                instance: 2,
12823                number: "2002".into(),
12824                display_name: "Warehouse".into(),
12825            }
12826        );
12827
12828        phone
12829            .write_all(
12830                &ClientMessage::SpeedDialStatusRequest {
12831                    speed_dial_instance: 99,
12832                }
12833                .encode(ProtocolVersion::V22)
12834                .unwrap(),
12835            )
12836            .await
12837            .unwrap();
12838        let frames =
12839            read_until_message(&mut phone, &mut decoder, id::SPEED_DIAL_STAT_DYNAMIC).await;
12840        let frame = frames
12841            .into_iter()
12842            .find(|frame| frame.message_id == id::SPEED_DIAL_STAT_DYNAMIC)
12843            .unwrap();
12844        assert_eq!(
12845            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12846            ServerMessage::SpeedDialStatus {
12847                instance: 99,
12848                number: String::new(),
12849                display_name: String::new(),
12850            }
12851        );
12852
12853        phone
12854            .write_all(
12855                &ClientMessage::FeatureStatusRequest {
12856                    index: 1,
12857                    capabilities: 0,
12858                }
12859                .encode(ProtocolVersion::V22)
12860                .unwrap(),
12861            )
12862            .await
12863            .unwrap();
12864        let frames = read_until_message(&mut phone, &mut decoder, id::FEATURE_STAT).await;
12865        let frame = frames
12866            .into_iter()
12867            .find(|frame| frame.message_id == id::FEATURE_STAT)
12868            .unwrap();
12869        assert_eq!(
12870            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12871            ServerMessage::FeatureStatus {
12872                instance: 1,
12873                button_type: ButtonType::DoNotDisturb,
12874                label: "DND".into(),
12875                state: 0,
12876            }
12877        );
12878
12879        phone
12880            .write_all(
12881                &ClientMessage::FeatureStatusRequest {
12882                    index: 2,
12883                    capabilities: 1,
12884                }
12885                .encode(ProtocolVersion::V22)
12886                .unwrap(),
12887            )
12888            .await
12889            .unwrap();
12890        let frames = read_until_message(&mut phone, &mut decoder, id::FEATURE_STAT).await;
12891        let frame = frames
12892            .into_iter()
12893            .find(|frame| frame.message_id == id::FEATURE_STAT)
12894            .unwrap();
12895        assert_eq!(
12896            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12897            ServerMessage::FeatureStatus {
12898                instance: 2,
12899                button_type: ButtonType::BlfSpeedDial,
12900                label: "Warehouse".into(),
12901                state: BusyLampFieldState::UnknownState.wire_value(),
12902            }
12903        );
12904
12905        phone
12906            .write_all(
12907                &ClientMessage::ServiceUrlStatusRequest { index: 1 }
12908                    .encode(ProtocolVersion::V22)
12909                    .unwrap(),
12910            )
12911            .await
12912            .unwrap();
12913        let frames =
12914            read_until_message(&mut phone, &mut decoder, id::SERVICE_URL_STAT_DYNAMIC).await;
12915        let frame = frames
12916            .into_iter()
12917            .find(|frame| frame.message_id == id::SERVICE_URL_STAT_DYNAMIC)
12918            .unwrap();
12919        assert_eq!(
12920            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12921            ServerMessage::ServiceUrlStatus {
12922                index: 1,
12923                url: "http://services.invalid/directory".into(),
12924                label: "Directory".into(),
12925                extension_text: String::new(),
12926            }
12927        );
12928
12929        let unknown_requests = [
12930            ClientMessage::FeatureStatusRequest {
12931                index: 99,
12932                capabilities: 0,
12933            }
12934            .encode(ProtocolVersion::V22)
12935            .unwrap(),
12936            ClientMessage::ServiceUrlStatusRequest { index: 99 }
12937                .encode(ProtocolVersion::V22)
12938                .unwrap(),
12939            ClientMessage::KeepAlive
12940                .encode(ProtocolVersion::V22)
12941                .unwrap(),
12942        ]
12943        .concat();
12944        phone.write_all(&unknown_requests).await.unwrap();
12945        let frames = read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
12946        assert!(
12947            frames.iter().all(|frame| !matches!(
12948                frame.message_id,
12949                id::FEATURE_STAT
12950                    | id::FEATURE_STAT_DYNAMIC
12951                    | id::SERVICE_URL_STAT
12952                    | id::SERVICE_URL_STAT_DYNAMIC
12953            )),
12954            "unknown feature and service requests must not produce placeholder statuses"
12955        );
12956
12957        handle.shutdown().await.unwrap();
12958        task.await.unwrap().unwrap();
12959    }
12960
12961    #[tokio::test]
12962    async fn anonymous_hotline_registration_gets_one_restricted_public_line() {
12963        let label = "Guest assistance";
12964        let config = ServerConfig {
12965            bind: "127.0.0.1:0".parse().unwrap(),
12966            advertised_address: Ipv4Addr::LOCALHOST,
12967            anonymous_hotline: Some(AnonymousHotlineDefinition::new(label).unwrap()),
12968            ..ServerConfig::default()
12969        };
12970        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
12971        let address = server.local_addr().unwrap();
12972        let task = tokio::spawn(server.run());
12973        let mut phone = TcpStream::connect(address).await.unwrap();
12974        let mut decoder = FrameDecoder::new();
12975        let protocol = ProtocolVersion::V22;
12976        let device = "SEPFFEEDDCCBBAA";
12977
12978        phone
12979            .write_all(&register_bytes_for_device(protocol, 115, device))
12980            .await
12981            .unwrap();
12982        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
12983        assert!(matches!(
12984            events.recv().await,
12985            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Registered(registration) })) if registration.id.as_str() == device
12986        ));
12987
12988        phone
12989            .write_all(
12990                &[
12991                    ClientMessage::LineStatRequest { line_instance: 1 }
12992                        .encode(protocol)
12993                        .unwrap(),
12994                    ClientMessage::ButtonTemplateRequest
12995                        .encode(protocol)
12996                        .unwrap(),
12997                    ClientMessage::SoftKeySetRequest.encode(protocol).unwrap(),
12998                ]
12999                .concat(),
13000            )
13001            .await
13002            .unwrap();
13003        let frames = read_until_message(&mut phone, &mut decoder, id::SOFT_KEY_SET_RES).await;
13004        assert!(frames.iter().any(|frame| matches!(
13005            ServerMessage::decode(frame.clone(), protocol),
13006            Ok(ServerMessage::LineStatus { instance: 1, number, display_name })
13007                if number == "hotline" && display_name == label
13008        )));
13009        assert!(frames.iter().any(|frame| matches!(
13010            ServerMessage::decode(frame.clone(), protocol),
13011            Ok(ServerMessage::ButtonTemplate { buttons })
13012                if buttons == vec![ButtonTemplateEntry {
13013                    instance: 1,
13014                    button_type: ButtonType::Line,
13015                }]
13016        )));
13017        assert!(frames.iter().any(|frame| matches!(
13018            ServerMessage::decode(frame.clone(), protocol),
13019            Ok(ServerMessage::SoftKeySet { profile })
13020                if profile.actions(KeyMode::OnHook) == [SoftKey::NewCall]
13021                    && profile.actions(KeyMode::OffHook) == [SoftKey::EndCall]
13022                    && profile.actions(KeyMode::RingOut) == [SoftKey::EndCall]
13023                    && profile.actions(KeyMode::Connected).is_empty()
13024        )));
13025
13026        handle.shutdown().await.unwrap();
13027        task.await.unwrap().unwrap();
13028    }
13029
13030    #[tokio::test]
13031    async fn anonymous_hotline_reload_isolated_from_configured_session_and_is_idempotent() {
13032        let config = ServerConfig {
13033            bind: "127.0.0.1:0".parse().unwrap(),
13034            advertised_address: Ipv4Addr::LOCALHOST,
13035            anonymous_hotline: Some(AnonymousHotlineDefinition::new("Guest A").unwrap()),
13036            ..ServerConfig::default()
13037        };
13038        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
13039        let address = server.local_addr().unwrap();
13040        let task = tokio::spawn(server.run());
13041        let protocol = ProtocolVersion::V22;
13042        let mut configured = TcpStream::connect(address).await.unwrap();
13043        let mut configured_decoder = FrameDecoder::new();
13044        configured
13045            .write_all(&register_bytes(protocol))
13046            .await
13047            .unwrap();
13048        read_until_message(
13049            &mut configured,
13050            &mut configured_decoder,
13051            id::CAPABILITIES_REQ,
13052        )
13053        .await;
13054        assert!(matches!(
13055            events.recv().await,
13056            Some(Event::Device(DeviceEvent {
13057                session_generation: _,
13058                device_id: _,
13059                event: DeviceEventKind::Registered(_)
13060            }))
13061        ));
13062
13063        let guest_id = "SEPFFEEDDCCBBAA";
13064        let mut guest = TcpStream::connect(address).await.unwrap();
13065        let mut guest_decoder = FrameDecoder::new();
13066        guest
13067            .write_all(&register_bytes_for_device(protocol, 115, guest_id))
13068            .await
13069            .unwrap();
13070        read_until_message(&mut guest, &mut guest_decoder, id::CAPABILITIES_REQ).await;
13071        loop {
13072            match events.recv().await {
13073                Some(Event::Device(DeviceEvent {
13074                    session_generation: _,
13075                    device_id: _,
13076                    event: DeviceEventKind::Registered(registration),
13077                })) if registration.id.as_str() == guest_id => {
13078                    break;
13079                }
13080                Some(_) => {}
13081                None => panic!("server stopped before replacement guest registration"),
13082            }
13083        }
13084
13085        assert_eq!(
13086            handle
13087                .reconfigure_anonymous_hotline(Some(
13088                    AnonymousHotlineDefinition::new("Guest A").unwrap(),
13089                ))
13090                .await
13091                .unwrap(),
13092            0
13093        );
13094        let station_policy = handle
13095            .reconfigure_station_policy(
13096                [definition()],
13097                [],
13098                Some(AnonymousHotlineDefinition::new("Guest B").unwrap()),
13099            )
13100            .await
13101            .unwrap();
13102        assert!(station_policy.is_unchanged());
13103        let mut closed = [0_u8; 1];
13104        assert_eq!(
13105            tokio::time::timeout(Duration::from_secs(1), guest.read(&mut closed))
13106                .await
13107                .unwrap()
13108                .unwrap(),
13109            0
13110        );
13111
13112        configured
13113            .write_all(
13114                &ClientMessage::LineStatRequest { line_instance: 1 }
13115                    .encode(protocol)
13116                    .unwrap(),
13117            )
13118            .await
13119            .unwrap();
13120        let frames = read_until_message(
13121            &mut configured,
13122            &mut configured_decoder,
13123            id::LINE_STAT_DYNAMIC,
13124        )
13125        .await;
13126        assert!(frames.into_iter().any(|frame| matches!(
13127            ServerMessage::decode(frame, protocol),
13128            Ok(ServerMessage::LineStatus { number, .. }) if number == "1001"
13129        )));
13130
13131        let mut replacement = TcpStream::connect(address).await.unwrap();
13132        let mut replacement_decoder = FrameDecoder::new();
13133        replacement
13134            .write_all(&register_bytes_for_device(protocol, 115, guest_id))
13135            .await
13136            .unwrap();
13137        read_until_message(
13138            &mut replacement,
13139            &mut replacement_decoder,
13140            id::CAPABILITIES_REQ,
13141        )
13142        .await;
13143        loop {
13144            match events.recv().await {
13145                Some(Event::Device(DeviceEvent {
13146                    session_generation: _,
13147                    device_id: _,
13148                    event: DeviceEventKind::Registered(registration),
13149                })) if registration.id.as_str() == guest_id => {
13150                    break;
13151                }
13152                Some(_) => {}
13153                None => panic!("server stopped before replacement guest registration"),
13154            }
13155        }
13156        replacement
13157            .write_all(
13158                &ClientMessage::LineStatRequest { line_instance: 1 }
13159                    .encode(protocol)
13160                    .unwrap(),
13161            )
13162            .await
13163            .unwrap();
13164        let frames = read_until_message(
13165            &mut replacement,
13166            &mut replacement_decoder,
13167            id::LINE_STAT_DYNAMIC,
13168        )
13169        .await;
13170        assert!(frames.into_iter().any(|frame| matches!(
13171            ServerMessage::decode(frame, protocol),
13172            Ok(ServerMessage::LineStatus { number, display_name, .. })
13173                if number == "hotline" && display_name == "Guest B"
13174        )));
13175
13176        assert_eq!(handle.reconfigure_anonymous_hotline(None).await.unwrap(), 1);
13177        assert_eq!(
13178            tokio::time::timeout(Duration::from_secs(1), replacement.read(&mut closed))
13179                .await
13180                .unwrap()
13181                .unwrap(),
13182            0
13183        );
13184        assert_eq!(handle.reconfigure_anonymous_hotline(None).await.unwrap(), 0);
13185
13186        assert_eq!(
13187            handle
13188                .reconfigure_anonymous_hotline(Some(
13189                    AnonymousHotlineDefinition::new("Guest C").unwrap(),
13190                ))
13191                .await
13192                .unwrap(),
13193            0
13194        );
13195        let mut promoted = TcpStream::connect(address).await.unwrap();
13196        let mut promoted_decoder = FrameDecoder::new();
13197        promoted
13198            .write_all(&register_bytes_for_device(protocol, 115, guest_id))
13199            .await
13200            .unwrap();
13201        read_until_message(&mut promoted, &mut promoted_decoder, id::CAPABILITIES_REQ).await;
13202        loop {
13203            match events.recv().await {
13204                Some(Event::Device(DeviceEvent {
13205                    session_generation: _,
13206                    device_id: _,
13207                    event: DeviceEventKind::Registered(registration),
13208                })) if registration.id.as_str() == guest_id => {
13209                    break;
13210                }
13211                Some(_) => {}
13212                None => panic!("server stopped before promoted guest registration"),
13213            }
13214        }
13215        let result = handle
13216            .reconfigure_affected(
13217                [definition(), definition_for(guest_id)],
13218                [DeviceId::new(guest_id).unwrap()],
13219            )
13220            .await
13221            .unwrap();
13222        assert_eq!(result.added, [DeviceId::new(guest_id).unwrap()]);
13223        assert_eq!(
13224            tokio::time::timeout(Duration::from_secs(1), promoted.read(&mut closed))
13225                .await
13226                .unwrap()
13227                .unwrap(),
13228            0
13229        );
13230        let mut configured_guest = TcpStream::connect(address).await.unwrap();
13231        let mut configured_guest_decoder = FrameDecoder::new();
13232        configured_guest
13233            .write_all(&register_bytes_for_device(protocol, 115, guest_id))
13234            .await
13235            .unwrap();
13236        read_until_message(
13237            &mut configured_guest,
13238            &mut configured_guest_decoder,
13239            id::CAPABILITIES_REQ,
13240        )
13241        .await;
13242        configured_guest
13243            .write_all(
13244                &ClientMessage::LineStatRequest { line_instance: 1 }
13245                    .encode(protocol)
13246                    .unwrap(),
13247            )
13248            .await
13249            .unwrap();
13250        let frames = read_until_message(
13251            &mut configured_guest,
13252            &mut configured_guest_decoder,
13253            id::LINE_STAT_DYNAMIC,
13254        )
13255        .await;
13256        assert!(frames.into_iter().any(|frame| matches!(
13257            ServerMessage::decode(frame, protocol),
13258            Ok(ServerMessage::LineStatus { number, .. }) if number == "1001"
13259        )));
13260
13261        handle.shutdown().await.unwrap();
13262        task.await.unwrap().unwrap();
13263    }
13264
13265    #[tokio::test]
13266    async fn duplicate_anonymous_registration_replaces_only_the_previous_guest_session() {
13267        let config = ServerConfig {
13268            bind: "127.0.0.1:0".parse().unwrap(),
13269            advertised_address: Ipv4Addr::LOCALHOST,
13270            anonymous_hotline: Some(AnonymousHotlineDefinition::new("Guest").unwrap()),
13271            ..ServerConfig::default()
13272        };
13273        let (server, handle, mut events) = Server::bind(config, []).await.unwrap();
13274        let address = server.local_addr().unwrap();
13275        let task = tokio::spawn(server.run());
13276        let protocol = ProtocolVersion::V22;
13277        let guest_id = "SEPFFEEDDCCBBAA";
13278        let mut first = TcpStream::connect(address).await.unwrap();
13279        let mut first_decoder = FrameDecoder::new();
13280        first
13281            .write_all(&register_bytes_for_device(protocol, 115, guest_id))
13282            .await
13283            .unwrap();
13284        read_until_message(&mut first, &mut first_decoder, id::CAPABILITIES_REQ).await;
13285        assert!(matches!(
13286            events.recv().await,
13287            Some(Event::Device(DeviceEvent {
13288                session_generation: _,
13289                device_id: _,
13290                event: DeviceEventKind::Registered(_)
13291            }))
13292        ));
13293
13294        let mut second = TcpStream::connect(address).await.unwrap();
13295        let mut second_decoder = FrameDecoder::new();
13296        second
13297            .write_all(&register_bytes_for_device(protocol, 115, guest_id))
13298            .await
13299            .unwrap();
13300        read_until_message(&mut second, &mut second_decoder, id::CAPABILITIES_REQ).await;
13301        assert!(matches!(
13302            events.recv().await,
13303            Some(Event::Device(DeviceEvent {
13304                session_generation: _,
13305                device_id: _,
13306                event: DeviceEventKind::Registered(_)
13307            }))
13308        ));
13309        let mut closed = [0_u8; 1];
13310        assert_eq!(
13311            tokio::time::timeout(Duration::from_secs(1), first.read(&mut closed))
13312                .await
13313                .unwrap()
13314                .unwrap(),
13315            0
13316        );
13317
13318        second
13319            .write_all(
13320                &ClientMessage::LineStatRequest { line_instance: 1 }
13321                    .encode(protocol)
13322                    .unwrap(),
13323            )
13324            .await
13325            .unwrap();
13326        read_until_message(&mut second, &mut second_decoder, id::LINE_STAT_DYNAMIC).await;
13327
13328        handle.shutdown().await.unwrap();
13329        task.await.unwrap().unwrap();
13330    }
13331
13332    #[tokio::test]
13333    async fn anonymous_hotline_disabled_rejects_unknown_without_affecting_configured_devices() {
13334        let config = ServerConfig {
13335            bind: "127.0.0.1:0".parse().unwrap(),
13336            advertised_address: Ipv4Addr::LOCALHOST,
13337            ..ServerConfig::default()
13338        };
13339        let (server, handle, _events) = Server::bind(config, [definition()]).await.unwrap();
13340        let address = server.local_addr().unwrap();
13341        let task = tokio::spawn(server.run());
13342        let mut phone = TcpStream::connect(address).await.unwrap();
13343        let mut decoder = FrameDecoder::new();
13344
13345        phone
13346            .write_all(&register_bytes_for_device(
13347                ProtocolVersion::V22,
13348                115,
13349                "SEPFFEEDDCCBBAA",
13350            ))
13351            .await
13352            .unwrap();
13353        let frames = read_until_message(&mut phone, &mut decoder, id::REGISTER_REJECT).await;
13354        assert!(frames.into_iter().any(|frame| matches!(
13355            ServerMessage::decode(frame, ProtocolVersion::V17),
13356            Ok(ServerMessage::RegisterReject { reason }) if reason == "Device not configured"
13357        )));
13358
13359        handle.shutdown().await.unwrap();
13360        task.await.unwrap().unwrap();
13361    }
13362
13363    #[test]
13364    fn anonymous_hotline_definition_bounds_and_debug_are_destination_free() {
13365        assert!(AnonymousHotlineDefinition::new("").is_err());
13366        assert!(AnonymousHotlineDefinition::new("x".repeat(80)).is_err());
13367        assert!(AnonymousHotlineDefinition::new("guest\nline").is_err());
13368        let definition = AnonymousHotlineDefinition::new("Guest").unwrap();
13369        assert!(!format!("{definition:?}").contains("111"));
13370    }
13371
13372    #[tokio::test]
13373    async fn mutable_forwarding_and_feature_state_is_published_and_answered() {
13374        let protocol = ProtocolVersion::V22;
13375        let device_id = DeviceId::new("SEP001122334455").unwrap();
13376        let config = ServerConfig {
13377            bind: "127.0.0.1:0".parse().unwrap(),
13378            advertised_address: Ipv4Addr::LOCALHOST,
13379            ..ServerConfig::default()
13380        };
13381        let (server, handle, mut events) =
13382            Server::bind(config, [mixed_definition()]).await.unwrap();
13383        let address = server.local_addr().unwrap();
13384        let task = tokio::spawn(server.run());
13385        let mut phone = TcpStream::connect(address).await.unwrap();
13386        let mut decoder = FrameDecoder::new();
13387        phone.write_all(&register_bytes(protocol)).await.unwrap();
13388        read_until_message(&mut phone, &mut decoder, id::REGISTER_ACK).await;
13389        assert!(matches!(
13390            events.recv().await,
13391            Some(Event::Device(DeviceEvent {
13392                session_generation: _,
13393                device_id: _,
13394                event: DeviceEventKind::Registered(_)
13395            }))
13396        ));
13397
13398        handle
13399            .send(Command::new(
13400                device_id.clone(),
13401                CommandAction::SetForwardStatus {
13402                    line_instance: LineInstance(1),
13403                    forward_all: Some("9000".into()),
13404                    forward_busy: None,
13405                    forward_no_answer: Some("9001".into()),
13406                },
13407            ))
13408            .await
13409            .unwrap();
13410        let frames = read_until_message(&mut phone, &mut decoder, id::FORWARD_STAT).await;
13411        assert!(frames.into_iter().any(|frame| matches!(
13412            ServerMessage::decode(frame, protocol),
13413            Ok(ServerMessage::ForwardStatus {
13414                line_instance: 1,
13415                ref forward_all,
13416                forward_busy: None,
13417                ref forward_no_answer,
13418            }) if forward_all.as_deref() == Some("9000")
13419                && forward_no_answer.as_deref() == Some("9001")
13420        )));
13421
13422        handle
13423            .send(Command::new(
13424                device_id.clone(),
13425                CommandAction::SetDoNotDisturbStatus {
13426                    instance: LineInstance::new(1),
13427                    mode: DoNotDisturbMode::Reject,
13428                    button_mode: DoNotDisturbButtonMode::Cycle,
13429                },
13430            ))
13431            .await
13432            .unwrap();
13433        let frames = read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
13434        assert!(frames.iter().any(|frame| matches!(
13435            ServerMessage::decode(frame.clone(), protocol),
13436            Ok(ServerMessage::FeatureStatus {
13437                instance: 1,
13438                button_type: ButtonType::MultiblinkFeature,
13439                state: 0x020202,
13440                ..
13441            })
13442        )));
13443        assert!(frames.into_iter().any(|frame| matches!(
13444            ServerMessage::decode(frame, protocol),
13445            Ok(ServerMessage::SetLamp {
13446                stimulus: ButtonType::DoNotDisturb,
13447                instance: 1,
13448                mode: LampMode::On,
13449            })
13450        )));
13451
13452        phone
13453            .write_all(
13454                &[
13455                    ClientMessage::ForwardStatusRequest { line_instance: 1 }
13456                        .encode(protocol)
13457                        .unwrap(),
13458                    ClientMessage::FeatureStatusRequest {
13459                        index: 1,
13460                        capabilities: 0,
13461                    }
13462                    .encode(protocol)
13463                    .unwrap(),
13464                ]
13465                .concat(),
13466            )
13467            .await
13468            .unwrap();
13469        let frames = read_until_message(&mut phone, &mut decoder, id::FEATURE_STAT).await;
13470        assert!(frames.iter().any(|frame| matches!(
13471            ServerMessage::decode(frame.clone(), protocol),
13472            Ok(ServerMessage::ForwardStatus { ref forward_all, .. })
13473                if forward_all.as_deref() == Some("9000")
13474        )));
13475        assert!(frames.into_iter().any(|frame| matches!(
13476            ServerMessage::decode(frame, protocol),
13477            Ok(ServerMessage::FeatureStatus {
13478                instance: 1,
13479                button_type: ButtonType::MultiblinkFeature,
13480                state: 0x020202,
13481                ..
13482            })
13483        )));
13484
13485        phone
13486            .write_all(
13487                &ClientMessage::Stimulus {
13488                    stimulus: Stimulus::DoNotDisturb,
13489                    instance: 1,
13490                    call_reference: 0,
13491                    status: 0,
13492                }
13493                .encode(protocol)
13494                .unwrap(),
13495            )
13496            .await
13497            .unwrap();
13498        assert!(matches!(
13499            events.recv().await,
13500            Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::DoNotDisturbButton {
13501                instance: LineInstance(1),
13502            } })) if actual_device == device_id
13503        ));
13504        phone
13505            .write_all(
13506                &ClientMessage::Stimulus {
13507                    stimulus: Stimulus::DoNotDisturb,
13508                    instance: 99,
13509                    call_reference: 0,
13510                    status: 0,
13511                }
13512                .encode(protocol)
13513                .unwrap(),
13514            )
13515            .await
13516            .unwrap();
13517        assert!(
13518            tokio::time::timeout(Duration::from_millis(25), events.recv())
13519                .await
13520                .is_err()
13521        );
13522
13523        handle.shutdown().await.unwrap();
13524        task.await.unwrap().unwrap();
13525    }
13526
13527    #[tokio::test]
13528    async fn registered_phone_receives_configured_soft_key_sets_and_masks() {
13529        let mut device = definition();
13530        device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::NewCall, SoftKey::Redial]);
13531        let default = device.soft_keys.clone();
13532        device.soft_keys = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
13533            if mode == KeyMode::OffHook {
13534                (
13535                    mode,
13536                    vec![SoftKey::EndCall, SoftKey::Pickup, SoftKey::GroupPickup],
13537                )
13538            } else {
13539                (mode, default.actions(mode).to_vec())
13540            }
13541        }))
13542        .unwrap();
13543        let expected_profile = device.soft_keys.clone();
13544        let config = ServerConfig {
13545            bind: "127.0.0.1:0".parse().unwrap(),
13546            advertised_address: Ipv4Addr::LOCALHOST,
13547            ..ServerConfig::default()
13548        };
13549        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13550        let address = server.local_addr().unwrap();
13551        let task = tokio::spawn(server.run());
13552        let mut phone = TcpStream::connect(address).await.unwrap();
13553        let mut decoder = FrameDecoder::new();
13554        let protocol = ProtocolVersion::V22;
13555
13556        phone.write_all(&register_bytes(protocol)).await.unwrap();
13557        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13558        assert!(matches!(
13559            events.recv().await,
13560            Some(Event::Device(DeviceEvent {
13561                session_generation: _,
13562                device_id: _,
13563                event: DeviceEventKind::Registered(_)
13564            }))
13565        ));
13566
13567        phone
13568            .write_all(
13569                &[
13570                    ClientMessage::SoftKeyTemplateRequest
13571                        .encode(protocol)
13572                        .unwrap(),
13573                    ClientMessage::SoftKeySetRequest.encode(protocol).unwrap(),
13574                ]
13575                .concat(),
13576            )
13577            .await
13578            .unwrap();
13579        let frames = read_until_message(&mut phone, &mut decoder, id::SOFT_KEY_SET_RES).await;
13580        assert!(frames.iter().any(|frame| matches!(
13581            ServerMessage::decode(frame.clone(), protocol),
13582            Ok(ServerMessage::SoftKeyTemplate { actions })
13583                if actions == expected_profile.template_actions()
13584        )));
13585        assert!(frames.iter().any(|frame| matches!(
13586            ServerMessage::decode(frame.clone(), protocol),
13587            Ok(ServerMessage::SoftKeySet { profile }) if profile == expected_profile
13588        )));
13589
13590        phone
13591            .write_all(
13592                &ClientMessage::SoftKeyEvent {
13593                    event: SoftKey::NewCall.wire_value(),
13594                    line_instance: 1,
13595                    call_reference: 0,
13596                }
13597                .encode(protocol)
13598                .unwrap(),
13599            )
13600            .await
13601            .unwrap();
13602        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
13603        assert!(frames.iter().any(|frame| matches!(
13604            ServerMessage::decode(frame.clone(), protocol),
13605            Ok(ServerMessage::SelectSoftKeys {
13606                set: KeyMode::OffHook,
13607                valid_mask: 0b111,
13608                ..
13609            })
13610        )));
13611        assert!(matches!(
13612            events.recv().await,
13613            Some(Event::Device(DeviceEvent {
13614                session_generation: _,
13615                device_id: _,
13616                event: DeviceEventKind::OffHook { .. }
13617            }))
13618        ));
13619        assert!(matches!(
13620            events.recv().await,
13621            Some(Event::Device(DeviceEvent {
13622                session_generation: _,
13623                device_id: _,
13624                event: DeviceEventKind::SoftKey {
13625                    soft_key: SoftKey::NewCall,
13626                    ..
13627                }
13628            }))
13629        ));
13630
13631        handle.shutdown().await.unwrap();
13632        task.await.unwrap().unwrap();
13633    }
13634
13635    #[tokio::test]
13636    async fn generic_feature_button_emits_only_for_the_configured_instance() {
13637        let mut device = definition();
13638        device
13639            .buttons
13640            .push(ButtonDefinition::Feature(FeatureDefinition {
13641                instance: 1,
13642                label: "Night service".into(),
13643                feature: ButtonType::Feature,
13644            }));
13645        let config = ServerConfig {
13646            bind: "127.0.0.1:0".parse().unwrap(),
13647            advertised_address: Ipv4Addr::LOCALHOST,
13648            ..ServerConfig::default()
13649        };
13650        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13651        let address = server.local_addr().unwrap();
13652        let task = tokio::spawn(server.run());
13653        let mut phone = TcpStream::connect(address).await.unwrap();
13654        let mut decoder = FrameDecoder::new();
13655        let protocol = ProtocolVersion::V22;
13656
13657        phone.write_all(&register_bytes(protocol)).await.unwrap();
13658        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13659        assert!(matches!(
13660            events.recv().await,
13661            Some(Event::Device(DeviceEvent {
13662                session_generation: _,
13663                device_id: _,
13664                event: DeviceEventKind::Registered(_)
13665            }))
13666        ));
13667
13668        phone
13669            .write_all(
13670                &ClientMessage::Stimulus {
13671                    stimulus: Stimulus::Privacy,
13672                    instance: 99,
13673                    call_reference: 0,
13674                    status: 0,
13675                }
13676                .encode(protocol)
13677                .unwrap(),
13678            )
13679            .await
13680            .unwrap();
13681        assert!(
13682            tokio::time::timeout(Duration::from_millis(50), events.recv())
13683                .await
13684                .is_err()
13685        );
13686
13687        phone
13688            .write_all(
13689                &ClientMessage::Stimulus {
13690                    stimulus: Stimulus::Privacy,
13691                    instance: 1,
13692                    call_reference: 0,
13693                    status: 0,
13694                }
13695                .encode(protocol)
13696                .unwrap(),
13697            )
13698            .await
13699            .unwrap();
13700        assert!(matches!(
13701            events.recv().await,
13702            Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::FeatureButton {
13703                instance: LineInstance(1),
13704            } })) if device_id == DeviceId::new("SEP001122334455").unwrap()
13705        ));
13706
13707        handle.shutdown().await.unwrap();
13708        task.await.unwrap().unwrap();
13709    }
13710
13711    #[test]
13712    fn mobility_candidate_rebuilds_every_slot_in_physical_button_order() {
13713        let mut configured = definition();
13714        configured
13715            .buttons
13716            .push(ButtonDefinition::Feature(FeatureDefinition {
13717                instance: 4,
13718                label: "Mobility A".into(),
13719                feature: ButtonType::Mobility,
13720            }));
13721        configured
13722            .buttons
13723            .push(ButtonDefinition::Feature(FeatureDefinition {
13724                instance: 5,
13725                label: "Mobility B".into(),
13726                feature: ButtonType::Mobility,
13727            }));
13728        let first = LineAppearance::new(
13729            2,
13730            LineDefinition {
13731                number: "9001".into(),
13732                display_name: "Roaming 9001".into(),
13733            },
13734        );
13735        let second = LineAppearance::new(
13736            3,
13737            LineDefinition {
13738                number: "9002".into(),
13739                display_name: "Roaming 9002".into(),
13740            },
13741        );
13742
13743        let first_map = HashMap::from([(4, first.clone())]);
13744        let with_first = mobility_device_candidate(&configured, &HashMap::new(), &first_map)
13745            .expect("first roaming appearance is valid");
13746        let both_map = HashMap::from([(5, second.clone()), (4, first)]);
13747        let with_both = mobility_device_candidate(&with_first, &first_map, &both_map)
13748            .expect("both roaming appearances are valid");
13749        let projected = with_both
13750            .buttons
13751            .iter()
13752            .filter_map(|button| match button {
13753                ButtonDefinition::Feature(feature) if feature.feature == ButtonType::Mobility => {
13754                    Some(("mobility", feature.instance))
13755                }
13756                ButtonDefinition::Line(line) if line.instance > 1 => Some(("line", line.instance)),
13757                _ => None,
13758            })
13759            .collect::<Vec<_>>();
13760        assert_eq!(
13761            projected,
13762            vec![("mobility", 4), ("line", 2), ("mobility", 5), ("line", 3),]
13763        );
13764        assert!(matches!(
13765            line_status(&with_both, 2),
13766            Some(ServerMessage::LineStatus { number, .. }) if number == "9001"
13767        ));
13768        assert!(matches!(
13769            line_status(&with_both, 3),
13770            Some(ServerMessage::LineStatus { number, .. }) if number == "9002"
13771        ));
13772
13773        let second_map = HashMap::from([(5, second)]);
13774        let without_first = mobility_device_candidate(&with_both, &both_map, &second_map)
13775            .expect("removing one roaming appearance preserves the other");
13776        assert!(line_status(&without_first, 2).is_none());
13777        assert!(matches!(
13778            line_status(&without_first, 3),
13779            Some(ServerMessage::LineStatus { number, .. }) if number == "9002"
13780        ));
13781    }
13782
13783    #[tokio::test]
13784    async fn mobility_button_and_live_appearance_refresh_preserve_the_session_call() {
13785        let mut device = definition();
13786        device
13787            .buttons
13788            .push(ButtonDefinition::Feature(FeatureDefinition {
13789                instance: 4,
13790                label: "Mobility".into(),
13791                feature: ButtonType::Mobility,
13792            }));
13793        let config = ServerConfig {
13794            bind: "127.0.0.1:0".parse().unwrap(),
13795            advertised_address: Ipv4Addr::LOCALHOST,
13796            ..ServerConfig::default()
13797        };
13798        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13799        let address = server.local_addr().unwrap();
13800        let task = tokio::spawn(server.run());
13801        let mut phone = TcpStream::connect(address).await.unwrap();
13802        let mut decoder = FrameDecoder::new();
13803        let protocol = ProtocolVersion::V22;
13804        let device_id = DeviceId::new("SEP001122334455").unwrap();
13805
13806        phone.write_all(&register_bytes(protocol)).await.unwrap();
13807        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13808        assert!(matches!(
13809            events.recv().await,
13810            Some(Event::Device(DeviceEvent {
13811                session_generation: _,
13812                device_id: _,
13813                event: DeviceEventKind::Registered(_)
13814            }))
13815        ));
13816        phone
13817            .write_all(
13818                &ClientMessage::Stimulus {
13819                    stimulus: Stimulus::Mobility,
13820                    instance: 4,
13821                    call_reference: 0,
13822                    status: 0,
13823                }
13824                .encode(protocol)
13825                .unwrap(),
13826            )
13827            .await
13828            .unwrap();
13829        assert!(matches!(
13830            events.recv().await,
13831            Some(Event::Device(DeviceEvent {
13832                session_generation: _,
13833                device_id: _,
13834                event: DeviceEventKind::MobilityButton {
13835                    instance: LineInstance(4),
13836                    ..
13837                }
13838            }))
13839        ));
13840
13841        let call_id = CallId(77);
13842        handle
13843            .send_confirmed(Command::new(
13844                device_id.clone(),
13845                CommandAction::BeginCall {
13846                    line_instance: LineInstance(1),
13847                    call_id,
13848                    codec: Codec::Pcmu,
13849                },
13850            ))
13851            .await
13852            .unwrap();
13853        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
13854
13855        let roaming = LineAppearance::new(
13856            2,
13857            LineDefinition {
13858                number: "9001".into(),
13859                display_name: "Roaming 9001".into(),
13860            },
13861        );
13862        handle
13863            .send_confirmed(Command::new(
13864                device_id.clone(),
13865                CommandAction::SetMobilityAppearance {
13866                    mobility_instance: LineInstance::new(4),
13867                    appearance: Some(roaming.clone()),
13868                },
13869            ))
13870            .await
13871            .unwrap();
13872        let frames = read_until_message(&mut phone, &mut decoder, id::LINE_STAT_DYNAMIC).await;
13873        assert!(frames.iter().any(|frame| matches!(
13874            ServerMessage::decode(frame.clone(), protocol),
13875            Ok(ServerMessage::ButtonTemplate { ref buttons })
13876                if buttons.iter().any(|button| button.instance == 2 && button.button_type == ButtonType::Line)
13877        )));
13878        assert!(frames.iter().any(|frame| matches!(
13879            ServerMessage::decode(frame.clone(), protocol),
13880            Ok(ServerMessage::LineStatus { instance: 2, ref number, .. }) if number == "9001"
13881        )));
13882
13883        handle
13884            .send_confirmed(Command::new(
13885                device_id.clone(),
13886                CommandAction::SetMobilityAppearance {
13887                    mobility_instance: LineInstance::new(4),
13888                    appearance: None,
13889                },
13890            ))
13891            .await
13892            .unwrap();
13893        let frames = read_until_message(&mut phone, &mut decoder, id::LINE_STAT_DYNAMIC).await;
13894        assert!(frames.iter().any(|frame| matches!(
13895            ServerMessage::decode(frame.clone(), protocol),
13896            Ok(ServerMessage::LineStatus { instance: 2, ref number, .. }) if number.is_empty()
13897        )));
13898        handle
13899            .send_confirmed(Command::new(
13900                device_id,
13901                CommandAction::SetCallState {
13902                    call_id,
13903                    state: CallState::Connected,
13904                },
13905            ))
13906            .await
13907            .unwrap();
13908        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
13909        assert!(frames.iter().any(|frame| matches!(
13910            ServerMessage::decode(frame.clone(), protocol),
13911            Ok(ServerMessage::CallState {
13912                state: CallState::Connected,
13913                ..
13914            })
13915        )));
13916
13917        handle.shutdown().await.unwrap();
13918        task.await.unwrap().unwrap();
13919    }
13920
13921    #[tokio::test]
13922    async fn parking_button_menu_and_selection_are_typed_end_to_end() {
13923        let mut device = definition();
13924        device
13925            .buttons
13926            .push(ButtonDefinition::Feature(FeatureDefinition {
13927                instance: 4,
13928                label: "Parking".into(),
13929                feature: ButtonType::ParkingLot,
13930            }));
13931        let config = ServerConfig {
13932            bind: "127.0.0.1:0".parse().unwrap(),
13933            advertised_address: Ipv4Addr::LOCALHOST,
13934            ..ServerConfig::default()
13935        };
13936        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13937        let address = server.local_addr().unwrap();
13938        let task = tokio::spawn(server.run());
13939        let mut phone = TcpStream::connect(address).await.unwrap();
13940        let mut decoder = FrameDecoder::new();
13941        let protocol = ProtocolVersion::V22;
13942        let device_id = DeviceId::new("SEP001122334455").unwrap();
13943
13944        phone.write_all(&register_bytes(protocol)).await.unwrap();
13945        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13946        assert!(matches!(
13947            events.recv().await,
13948            Some(Event::Device(DeviceEvent {
13949                session_generation: _,
13950                device_id: _,
13951                event: DeviceEventKind::Registered(_)
13952            }))
13953        ));
13954
13955        phone
13956            .write_all(
13957                &ClientMessage::Stimulus {
13958                    stimulus: Stimulus::ParkingLot,
13959                    instance: 4,
13960                    call_reference: 0,
13961                    status: 0,
13962                }
13963                .encode(protocol)
13964                .unwrap(),
13965            )
13966            .await
13967            .unwrap();
13968        assert!(matches!(
13969            events.recv().await,
13970            Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ParkingLotButton {
13971                instance: LineInstance(4),
13972                call_id: None,
13973                line_instance: LineInstance(1),
13974            } })) if actual == device_id
13975        ));
13976
13977        handle
13978            .send(Command::new(
13979                device_id.clone(),
13980                CommandAction::ShowParkingMenu {
13981                    instance: LineInstance::new(4),
13982                    transaction_id: TransactionId(17),
13983                    lot: "east & west".into(),
13984                    calls: vec![ParkingMenuEntry {
13985                        slot: 701,
13986                        caller_name: "Taylor <T>".into(),
13987                        caller_number: "2100".into(),
13988                        connected_name: "Desk".into(),
13989                        connected_number: "1001".into(),
13990                    }],
13991                },
13992            ))
13993            .await
13994            .unwrap();
13995        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
13996        let message = frames
13997            .into_iter()
13998            .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
13999            .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14000            .unwrap();
14001        let ServerMessage::UserToDeviceDataV1(menu) = message else {
14002            panic!("expected parking menu application data");
14003        };
14004        assert_eq!(menu.application_id, PARKING_APPLICATION_ID);
14005        assert_eq!(menu.line_instance, 4);
14006        assert_eq!(menu.call_reference, 0);
14007        assert_eq!(menu.transaction_id, 17);
14008        let xml = String::from_utf8(menu.data).unwrap();
14009        assert!(xml.contains("Taylor &lt;T&gt;"));
14010        assert!(xml.contains("UserCallData:9090:4:0:17:"));
14011        assert!(xml.contains("retrieve/east%20%26%20west/701"));
14012
14013        phone
14014            .write_all(
14015                &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14016                    application_id: PARKING_APPLICATION_ID,
14017                    line_instance: 4,
14018                    call_reference: 0,
14019                    transaction_id: 17,
14020                    sequence_flag: 0,
14021                    display_priority: 0,
14022                    conference_id: 0,
14023                    application_instance_id: 4,
14024                    routing: 0,
14025                    data: b"retrieve/east%20%26%20west/701".to_vec(),
14026                })
14027                .encode(protocol)
14028                .unwrap(),
14029            )
14030            .await
14031            .unwrap();
14032        assert!(matches!(
14033            events.recv().await,
14034            Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ParkingMenuSelection {
14035                lot,
14036                slot: 701,
14037            } })) if actual == device_id && lot == "east & west"
14038        ));
14039        let Some(Event::Device(DeviceEvent {
14040            session_generation: _,
14041            device_id: actual,
14042            event: DeviceEventKind::PhoneServiceResponse { response },
14043        })) = events.recv().await
14044        else {
14045            panic!("expected typed phone-service response");
14046        };
14047        assert_eq!(actual, device_id);
14048        assert_eq!(response.kind, PhoneServiceMessageKind::Data);
14049        assert_eq!(response.routing.application_id, ApplicationId::new(9090));
14050        assert_eq!(response.routing.line_instance, LineInstance::new(4));
14051        assert_eq!(response.routing.call_reference, CallReference::new(0));
14052        assert_eq!(response.routing.transaction_id, TransactionId::new(17));
14053        let PhoneServicePayload::Submission(submission) = response.payload else {
14054            panic!("expected typed menu submission");
14055        };
14056        assert_eq!(submission.route, ["retrieve", "east & west", "701"]);
14057        assert!(submission.values.is_empty());
14058
14059        handle.shutdown().await.unwrap();
14060        task.await.unwrap().unwrap();
14061    }
14062
14063    #[tokio::test]
14064    async fn conference_list_uses_protocol_family_and_routes_typed_actions() {
14065        for (protocol, family) in [
14066            (ProtocolVersion::V3, ConferenceMenuFamily::Menu),
14067            (ProtocolVersion::V22, ConferenceMenuFamily::IconMenu),
14068        ] {
14069            let config = ServerConfig {
14070                bind: "127.0.0.1:0".parse().unwrap(),
14071                advertised_address: Ipv4Addr::LOCALHOST,
14072                ..ServerConfig::default()
14073            };
14074            let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14075            let address = server.local_addr().unwrap();
14076            let task = tokio::spawn(server.run());
14077            let mut phone = TcpStream::connect(address).await.unwrap();
14078            let mut decoder = FrameDecoder::new();
14079            let device_id = DeviceId::new("SEP001122334455").unwrap();
14080
14081            phone.write_all(&register_bytes(protocol)).await.unwrap();
14082            read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14083            assert!(matches!(
14084                events.recv().await,
14085                Some(Event::Device(DeviceEvent {
14086                    session_generation: _,
14087                    device_id: _,
14088                    event: DeviceEventKind::Registered(_)
14089                }))
14090            ));
14091            handle
14092                .send(Command::new(
14093                    device_id.clone(),
14094                    CommandAction::BeginCall {
14095                        line_instance: LineInstance(1),
14096                        call_id: CallId(7001),
14097                        codec: Codec::Pcma,
14098                    },
14099                ))
14100                .await
14101                .unwrap();
14102            read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
14103
14104            handle
14105                .send(Command::new(
14106                    device_id.clone(),
14107                    CommandAction::ShowConferenceList {
14108                        call_id: CallId(7001),
14109                        conference_id: ConferenceId::new(44),
14110                        participants: vec![ConferenceListEntry {
14111                            participant_id: crate::ParticipantId::new(7),
14112                            name: "Taylor <T>".into(),
14113                            number: "2100".into(),
14114                            moderator: false,
14115                            muted: false,
14116                        }],
14117                    },
14118                ))
14119                .await
14120                .unwrap();
14121            let frames =
14122                read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14123            let message = frames
14124                .into_iter()
14125                .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
14126                .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14127                .unwrap();
14128            let ServerMessage::UserToDeviceDataV1(menu) = message else {
14129                panic!("expected conference-list application data");
14130            };
14131            assert_eq!(menu.application_id, ConferenceListAction::APPLICATION_ID);
14132            assert_eq!(menu.conference_id, 44);
14133            let document = ConferenceListDocument::from_xml(&menu.data, family).unwrap();
14134            assert_eq!(
14135                document.actions().collect::<Vec<_>>(),
14136                [
14137                    ConferenceListAction::Participant {
14138                        conference_id: ConferenceId::new(44),
14139                        participant_id: crate::ParticipantId::new(7),
14140                    },
14141                    ConferenceListAction::End {
14142                        conference_id: ConferenceId::new(44),
14143                    },
14144                ]
14145            );
14146            assert!(
14147                String::from_utf8(menu.data)
14148                    .unwrap()
14149                    .contains("Taylor &lt;T&gt;")
14150            );
14151
14152            phone
14153                .write_all(
14154                    &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14155                        application_id: ConferenceListAction::APPLICATION_ID,
14156                        line_instance: 1,
14157                        call_reference: 7001,
14158                        transaction_id: 44,
14159                        sequence_flag: 0,
14160                        display_priority: 0,
14161                        conference_id: 44,
14162                        application_instance_id: 1,
14163                        routing: 0,
14164                        data: b"conference/44/participant/7".to_vec(),
14165                    })
14166                    .encode(protocol)
14167                    .unwrap(),
14168                )
14169                .await
14170                .unwrap();
14171            assert!(matches!(
14172                events.recv().await,
14173                Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ConferenceListAction {
14174                    action: ConferenceListAction::Participant {
14175                        conference_id,
14176                        participant_id,
14177                    },
14178                } })) if actual == device_id
14179                    && conference_id == ConferenceId::new(44)
14180                    && participant_id == crate::ParticipantId::new(7)
14181            ));
14182            assert!(matches!(
14183                events.recv().await,
14184                Some(Event::Device(DeviceEvent {
14185                    session_generation: _,
14186                    device_id: _,
14187                    event: DeviceEventKind::PhoneServiceResponse { .. }
14188                }))
14189            ));
14190
14191            handle
14192                .send(Command::new(
14193                    device_id.clone(),
14194                    CommandAction::ShowConferenceParticipantActions {
14195                        call_id: CallId(7001),
14196                        conference_id: ConferenceId::new(44),
14197                        participant: ConferenceListEntry {
14198                            participant_id: crate::ParticipantId::new(7),
14199                            name: "Taylor <T>".into(),
14200                            number: "2100".into(),
14201                            moderator: false,
14202                            muted: false,
14203                        },
14204                        removable: true,
14205                        demotable: false,
14206                    },
14207                ))
14208                .await
14209                .unwrap();
14210            let frames =
14211                read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14212            let message = frames
14213                .into_iter()
14214                .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
14215                .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14216                .unwrap();
14217            let ServerMessage::UserToDeviceDataV1(menu) = message else {
14218                panic!("expected conference-participant action menu");
14219            };
14220            let document =
14221                ConferenceParticipantActionsDocument::from_xml(&menu.data, family).unwrap();
14222            assert_eq!(
14223                document.actions().collect::<Vec<_>>(),
14224                [
14225                    ConferenceListAction::Mute {
14226                        conference_id: ConferenceId::new(44),
14227                        participant_id: crate::ParticipantId::new(7),
14228                    },
14229                    ConferenceListAction::Remove {
14230                        conference_id: ConferenceId::new(44),
14231                        participant_id: crate::ParticipantId::new(7),
14232                    },
14233                    ConferenceListAction::Promote {
14234                        conference_id: ConferenceId::new(44),
14235                        participant_id: crate::ParticipantId::new(7),
14236                    },
14237                ]
14238            );
14239
14240            phone
14241                .write_all(
14242                    &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14243                        application_id: ConferenceListAction::APPLICATION_ID,
14244                        line_instance: 1,
14245                        call_reference: 7001,
14246                        transaction_id: 44,
14247                        sequence_flag: 0,
14248                        display_priority: 0,
14249                        conference_id: 44,
14250                        application_instance_id: 1,
14251                        routing: 0,
14252                        data: b"conference/44/participant/7/remove".to_vec(),
14253                    })
14254                    .encode(protocol)
14255                    .unwrap(),
14256                )
14257                .await
14258                .unwrap();
14259            assert!(matches!(
14260                events.recv().await,
14261                Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ConferenceListAction {
14262                    action: ConferenceListAction::Remove {
14263                        conference_id,
14264                        participant_id,
14265                    },
14266                } })) if actual == device_id
14267                    && conference_id == ConferenceId::new(44)
14268                    && participant_id == crate::ParticipantId::new(7)
14269            ));
14270            assert!(matches!(
14271                events.recv().await,
14272                Some(Event::Device(DeviceEvent {
14273                    session_generation: _,
14274                    device_id: _,
14275                    event: DeviceEventKind::PhoneServiceResponse { .. }
14276                }))
14277            ));
14278            for (route, expected) in [
14279                (
14280                    b"conference/44/participant/7/mute".as_slice(),
14281                    ConferenceListAction::Mute {
14282                        conference_id: ConferenceId::new(44),
14283                        participant_id: crate::ParticipantId::new(7),
14284                    },
14285                ),
14286                (
14287                    b"conference/44/participant/7/unmute".as_slice(),
14288                    ConferenceListAction::Unmute {
14289                        conference_id: ConferenceId::new(44),
14290                        participant_id: crate::ParticipantId::new(7),
14291                    },
14292                ),
14293                (
14294                    b"conference/44/participant/7/promote".as_slice(),
14295                    ConferenceListAction::Promote {
14296                        conference_id: ConferenceId::new(44),
14297                        participant_id: crate::ParticipantId::new(7),
14298                    },
14299                ),
14300                (
14301                    b"conference/44/participant/7/demote".as_slice(),
14302                    ConferenceListAction::Demote {
14303                        conference_id: ConferenceId::new(44),
14304                        participant_id: crate::ParticipantId::new(7),
14305                    },
14306                ),
14307                (
14308                    b"conference/44/end".as_slice(),
14309                    ConferenceListAction::End {
14310                        conference_id: ConferenceId::new(44),
14311                    },
14312                ),
14313            ] {
14314                phone
14315                    .write_all(
14316                        &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14317                            application_id: ConferenceListAction::APPLICATION_ID,
14318                            line_instance: 1,
14319                            call_reference: 7001,
14320                            transaction_id: 44,
14321                            sequence_flag: 0,
14322                            display_priority: 0,
14323                            conference_id: 44,
14324                            application_instance_id: 1,
14325                            routing: 0,
14326                            data: route.to_vec(),
14327                        })
14328                        .encode(protocol)
14329                        .unwrap(),
14330                    )
14331                    .await
14332                    .unwrap();
14333                assert!(matches!(
14334                    events.recv().await,
14335                    Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ConferenceListAction {
14336                        action,
14337                    } })) if actual == device_id && action == expected
14338                ));
14339                assert!(matches!(
14340                    events.recv().await,
14341                    Some(Event::Device(DeviceEvent {
14342                        session_generation: _,
14343                        device_id: _,
14344                        event: DeviceEventKind::PhoneServiceResponse { .. }
14345                    }))
14346                ));
14347            }
14348
14349            handle
14350                .send(Command::new(
14351                    device_id.clone(),
14352                    CommandAction::ShowConferenceParticipantActions {
14353                        call_id: CallId(7001),
14354                        conference_id: ConferenceId::new(44),
14355                        participant: ConferenceListEntry {
14356                            participant_id: crate::ParticipantId::new(7),
14357                            name: "Taylor <T>".into(),
14358                            number: "2100".into(),
14359                            moderator: true,
14360                            muted: false,
14361                        },
14362                        removable: false,
14363                        demotable: true,
14364                    },
14365                ))
14366                .await
14367                .unwrap();
14368            let frames =
14369                read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14370            let message = frames
14371                .into_iter()
14372                .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
14373                .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14374                .unwrap();
14375            let ServerMessage::UserToDeviceDataV1(menu) = message else {
14376                panic!("expected moderator conference-participant action menu");
14377            };
14378            let document =
14379                ConferenceParticipantActionsDocument::from_xml(&menu.data, family).unwrap();
14380            assert_eq!(
14381                document.actions().collect::<Vec<_>>(),
14382                [ConferenceListAction::Demote {
14383                    conference_id: ConferenceId::new(44),
14384                    participant_id: crate::ParticipantId::new(7),
14385                }]
14386            );
14387
14388            handle.shutdown().await.unwrap();
14389            task.await.unwrap().unwrap();
14390        }
14391    }
14392
14393    #[tokio::test]
14394    async fn phone_service_responses_preserve_legacy_and_extended_routing() {
14395        let config = ServerConfig {
14396            bind: "127.0.0.1:0".parse().unwrap(),
14397            advertised_address: Ipv4Addr::LOCALHOST,
14398            ..ServerConfig::default()
14399        };
14400        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14401        let address = server.local_addr().unwrap();
14402        let task = tokio::spawn(server.run());
14403        let mut phone = TcpStream::connect(address).await.unwrap();
14404        let mut decoder = FrameDecoder::new();
14405        let protocol = ProtocolVersion::V22;
14406        let device_id = DeviceId::new("SEP001122334455").unwrap();
14407
14408        phone.write_all(&register_bytes(protocol)).await.unwrap();
14409        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14410        assert!(matches!(
14411            events.recv().await,
14412            Some(Event::Device(DeviceEvent {
14413                session_generation: _,
14414                device_id: _,
14415                event: DeviceEventKind::Registered(_)
14416            }))
14417        ));
14418
14419        phone
14420            .write_all(
14421                &ClientMessage::DeviceToUserDataResponseV1(UserDataV1Message {
14422                    application_id: 9084,
14423                    line_instance: 2,
14424                    call_reference: 42,
14425                    transaction_id: 73,
14426                    sequence_flag: 1,
14427                    display_priority: 2,
14428                    conference_id: 51,
14429                    application_instance_id: 6,
14430                    routing: 4,
14431                    data: br#"<CiscoIPPhoneResponse><ResponseItem Status="0" Data="ok &amp; ready" URL="Init:Services"/></CiscoIPPhoneResponse>"#.to_vec(),
14432                })
14433                .encode(protocol)
14434                .unwrap(),
14435            )
14436            .await
14437            .unwrap();
14438        let Some(Event::Device(DeviceEvent {
14439            session_generation: _,
14440            device_id: actual,
14441            event: DeviceEventKind::PhoneServiceResponse { response },
14442        })) = events.recv().await
14443        else {
14444            panic!("expected typed execute response");
14445        };
14446        assert_eq!(actual, device_id);
14447        assert_eq!(response.kind, PhoneServiceMessageKind::Response);
14448        assert_eq!(response.routing.application_id, ApplicationId::new(9084));
14449        assert_eq!(response.routing.line_instance, LineInstance::new(2));
14450        assert_eq!(response.routing.call_reference, CallReference::new(42));
14451        assert_eq!(response.routing.transaction_id, TransactionId::new(73));
14452        assert_eq!(
14453            response.extended,
14454            Some(PhoneServiceExtendedRouting {
14455                sequence_flag: 1,
14456                display_priority: 2,
14457                conference_id: 51,
14458                application_instance_id: 6,
14459                routing: 4,
14460            })
14461        );
14462        let PhoneServicePayload::ExecuteResponse(execute) = response.payload else {
14463            panic!("expected typed execute response payload");
14464        };
14465        assert_eq!(execute.items.len(), 1);
14466        assert_eq!(execute.items[0].status.get(), 0);
14467        assert_eq!(execute.items[0].data, "ok & ready");
14468        assert_eq!(execute.items[0].url, "Init:Services");
14469
14470        phone
14471            .write_all(
14472                &ClientMessage::DeviceToUserData(crate::message::UserDataMessage {
14473                    application_id: 9083,
14474                    line_instance: 1,
14475                    call_reference: 43,
14476                    transaction_id: 74,
14477                    data: b"invite?NUMBER=555%2A12&NUMBER=555%2A13&NAME=Fran%C3%A7ois".to_vec(),
14478                })
14479                .encode(protocol)
14480                .unwrap(),
14481            )
14482            .await
14483            .unwrap();
14484        let Some(Event::Device(DeviceEvent {
14485            session_generation: _,
14486            device_id: _,
14487            event: DeviceEventKind::PhoneServiceResponse { response, .. },
14488        })) = events.recv().await
14489        else {
14490            panic!("expected typed input submission");
14491        };
14492        assert_eq!(response.extended, None);
14493        assert_eq!(response.routing.application_id, ApplicationId::new(9083));
14494        assert_eq!(response.routing.line_instance, LineInstance::new(1));
14495        assert_eq!(response.routing.call_reference, CallReference::new(43));
14496        assert_eq!(response.routing.transaction_id, TransactionId::new(74));
14497        let PhoneServicePayload::Submission(submission) = response.payload else {
14498            panic!("expected typed input submission payload");
14499        };
14500        assert_eq!(submission.route, ["invite"]);
14501        assert_eq!(
14502            submission.values_named("NUMBER").collect::<Vec<_>>(),
14503            ["555*12", "555*13"]
14504        );
14505        assert_eq!(
14506            submission.values_named("NAME").collect::<Vec<_>>(),
14507            ["François"]
14508        );
14509
14510        handle.shutdown().await.unwrap();
14511        task.await.unwrap().unwrap();
14512    }
14513
14514    #[tokio::test]
14515    async fn parking_selection_requires_the_pending_envelope_and_survives_malformed_data() {
14516        let config = ServerConfig {
14517            bind: "127.0.0.1:0".parse().unwrap(),
14518            advertised_address: Ipv4Addr::LOCALHOST,
14519            ..ServerConfig::default()
14520        };
14521        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14522        let address = server.local_addr().unwrap();
14523        let task = tokio::spawn(server.run());
14524        let mut phone = TcpStream::connect(address).await.unwrap();
14525        let mut decoder = FrameDecoder::new();
14526        let protocol = ProtocolVersion::V22;
14527        let device_id = DeviceId::new("SEP001122334455").unwrap();
14528
14529        phone.write_all(&register_bytes(protocol)).await.unwrap();
14530        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14531        assert!(matches!(
14532            events.recv().await,
14533            Some(Event::Device(DeviceEvent {
14534                session_generation: _,
14535                device_id: _,
14536                event: DeviceEventKind::Registered(_)
14537            }))
14538        ));
14539        handle
14540            .send(Command::new(
14541                device_id.clone(),
14542                CommandAction::ShowParkingMenu {
14543                    instance: LineInstance::new(4),
14544                    transaction_id: TransactionId(17),
14545                    lot: "main".into(),
14546                    calls: vec![],
14547                },
14548            ))
14549            .await
14550            .unwrap();
14551        read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14552
14553        let response = |application_id,
14554                        line_instance,
14555                        call_reference,
14556                        transaction_id,
14557                        instance,
14558                        data: &[u8]| {
14559            ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14560                application_id,
14561                line_instance,
14562                call_reference,
14563                transaction_id,
14564                sequence_flag: 0,
14565                display_priority: 0,
14566                conference_id: 0,
14567                application_instance_id: instance,
14568                routing: 0,
14569                data: data.to_vec(),
14570            })
14571            .encode(protocol)
14572            .unwrap()
14573        };
14574
14575        for (application_id, line_instance, call_reference, transaction_id, instance) in [
14576            (9083, 4, 0, 17, 4),
14577            (PARKING_APPLICATION_ID, 5, 0, 17, 4),
14578            (PARKING_APPLICATION_ID, 4, 9, 17, 4),
14579            (PARKING_APPLICATION_ID, 4, 0, 18, 4),
14580            (PARKING_APPLICATION_ID, 4, 0, 17, 5),
14581        ] {
14582            phone
14583                .write_all(&response(
14584                    application_id,
14585                    line_instance,
14586                    call_reference,
14587                    transaction_id,
14588                    instance,
14589                    b"retrieve/main/701",
14590                ))
14591                .await
14592                .unwrap();
14593            let Some(Event::Device(DeviceEvent {
14594                session_generation: _,
14595                device_id: _,
14596                event:
14597                    DeviceEventKind::PhoneServiceResponse {
14598                        response: routed, ..
14599                    },
14600            })) = events.recv().await
14601            else {
14602                panic!("expected mismatched response to remain generically routed");
14603            };
14604            assert_eq!(routed.routing.application_id.get(), application_id);
14605            assert_eq!(routed.routing.line_instance.get(), line_instance);
14606            assert_eq!(routed.routing.call_reference.get(), call_reference);
14607            assert_eq!(routed.routing.transaction_id.get(), transaction_id);
14608            assert!(
14609                tokio::time::timeout(Duration::from_millis(25), events.recv())
14610                    .await
14611                    .is_err(),
14612                "mismatched envelope emitted a parking action"
14613            );
14614        }
14615
14616        phone
14617            .write_all(&response(
14618                PARKING_APPLICATION_ID,
14619                4,
14620                0,
14621                17,
14622                4,
14623                b"retrieve/secret%GG/701",
14624            ))
14625            .await
14626            .unwrap();
14627        let Some(Event::ProtocolWarning {
14628            message_id, error, ..
14629        }) = events.recv().await
14630        else {
14631            panic!("expected malformed service-data warning");
14632        };
14633        assert_eq!(message_id, id::DEVICE_TO_USER_DATA_V1);
14634        assert!(!error.contains("secret"));
14635
14636        phone
14637            .write_all(&response(
14638                PARKING_APPLICATION_ID,
14639                4,
14640                0,
14641                17,
14642                4,
14643                b"retrieve/main/701",
14644            ))
14645            .await
14646            .unwrap();
14647        assert!(matches!(
14648            events.recv().await,
14649            Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ParkingMenuSelection {
14650                lot,
14651                slot: 701,
14652            } })) if actual == device_id && lot == "main"
14653        ));
14654        assert!(matches!(
14655            events.recv().await,
14656            Some(Event::Device(DeviceEvent {
14657                session_generation: _,
14658                device_id: _,
14659                event: DeviceEventKind::PhoneServiceResponse { .. }
14660            }))
14661        ));
14662
14663        phone
14664            .write_all(&response(
14665                PARKING_APPLICATION_ID,
14666                4,
14667                0,
14668                17,
14669                4,
14670                b"retrieve/main/701",
14671            ))
14672            .await
14673            .unwrap();
14674        assert!(matches!(
14675            events.recv().await,
14676            Some(Event::Device(DeviceEvent {
14677                session_generation: _,
14678                device_id: _,
14679                event: DeviceEventKind::PhoneServiceResponse { .. }
14680            }))
14681        ));
14682        assert!(
14683            tokio::time::timeout(Duration::from_millis(25), events.recv())
14684                .await
14685                .is_err(),
14686            "replayed selection emitted a second parking action"
14687        );
14688
14689        handle.shutdown().await.unwrap();
14690        task.await.unwrap().unwrap();
14691    }
14692
14693    #[test]
14694    fn parking_menu_xml_is_typed_round_trippable_and_size_bounded() {
14695        let calls = [ParkingMenuEntry {
14696            slot: 701,
14697            caller_name: "Taylor <T> & Co".into(),
14698            caller_number: "2100".into(),
14699            connected_name: "Desk".into(),
14700            connected_number: "1001".into(),
14701        }];
14702        let xml = parking_menu_xml(4, 17, "east & west", &calls).unwrap();
14703        assert!(xml.contains("Taylor &lt;T&gt; &amp; Co"));
14704        let decoded = CiscoIpPhoneMenu::from_xml_with_limit(xml.as_bytes(), 2_000).unwrap();
14705        assert_eq!(decoded.title.as_deref(), Some("Parked calls - east & west"));
14706        assert_eq!(decoded.items.len(), 1);
14707        assert_eq!(
14708            decoded.items[0].url.as_deref(),
14709            Some("UserCallData:9090:4:0:17:retrieve/east%20%26%20west/701")
14710        );
14711
14712        let oversized = [ParkingMenuEntry {
14713            caller_name: "x".repeat(2100),
14714            ..calls[0].clone()
14715        }];
14716        assert!(matches!(
14717            parking_menu_xml(4, 17, "main", &oversized),
14718            Err(ServerError::PhoneXml(PhoneXmlError::InvalidField {
14719                field: "menu item name",
14720                ..
14721            }))
14722        ));
14723        let byte_oversized = vec![
14724            ParkingMenuEntry {
14725                caller_name: "x".repeat(45),
14726                ..calls[0].clone()
14727            };
14728            PARKING_MENU_MAX_ITEMS
14729        ];
14730        assert!(matches!(
14731            parking_menu_xml(4, 17, "main", &byte_oversized),
14732            Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
14733                kind: "phone XML document",
14734                maximum: 2_000,
14735                ..
14736            }))
14737        ));
14738        assert!(matches!(
14739            parking_menu_xml(
14740                4,
14741                17,
14742                "main",
14743                &vec![calls[0].clone(); PARKING_MENU_MAX_ITEMS + 1]
14744            ),
14745            Err(ServerError::PhoneXml(error)) if error.to_string().contains("maximum is 32")
14746        ));
14747        assert!(
14748            CiscoIpPhoneMenu::from_xml_with_limit(b"<CiscoIPPhoneMenu><Title>broken", 2_000,)
14749                .is_err()
14750        );
14751
14752        #[derive(Debug)]
14753        struct FailingWriter;
14754
14755        impl std::fmt::Write for FailingWriter {
14756            fn write_str(&mut self, _value: &str) -> std::fmt::Result {
14757                Err(std::fmt::Error)
14758            }
14759        }
14760
14761        assert!(matches!(
14762            phone_xml::to_writer(FailingWriter, &decoded, 2_000),
14763            Err(PhoneXmlError::Write(_))
14764        ));
14765    }
14766
14767    #[tokio::test]
14768    async fn begin_call_creates_the_reserved_retrieval_identity() {
14769        let config = ServerConfig {
14770            bind: "127.0.0.1:0".parse().unwrap(),
14771            advertised_address: Ipv4Addr::LOCALHOST,
14772            ..ServerConfig::default()
14773        };
14774        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14775        let address = server.local_addr().unwrap();
14776        let task = tokio::spawn(server.run());
14777        let mut phone = TcpStream::connect(address).await.unwrap();
14778        let mut decoder = FrameDecoder::new();
14779        let protocol = ProtocolVersion::V22;
14780        let device_id = DeviceId::new("SEP001122334455").unwrap();
14781
14782        phone.write_all(&register_bytes(protocol)).await.unwrap();
14783        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14784        assert!(matches!(
14785            events.recv().await,
14786            Some(Event::Device(DeviceEvent {
14787                session_generation: _,
14788                device_id: _,
14789                event: DeviceEventKind::Registered(_)
14790            }))
14791        ));
14792        handle
14793            .send(Command::new(
14794                device_id.clone(),
14795                CommandAction::BeginCall {
14796                    line_instance: LineInstance(1),
14797                    call_id: CallId(7001),
14798                    codec: Codec::Pcma,
14799                },
14800            ))
14801            .await
14802            .unwrap();
14803        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
14804        assert!(frames.into_iter().any(|frame| matches!(
14805            ServerMessage::decode(frame, protocol),
14806            Ok(ServerMessage::CallState {
14807                state: CallState::OffHook,
14808                line_instance: 1,
14809                call_reference: 7001,
14810            })
14811        )));
14812
14813        let info = CallInfo {
14814            direction: crate::types::CallDirection::Inbound,
14815            calling_name: "Caller".into(),
14816            calling_number: "2100".into(),
14817            called_name: "Park 701".into(),
14818            called_number: "701".into(),
14819            original_called_name: "Reception".into(),
14820            original_called_number: "2000".into(),
14821            last_redirecting_name: "Front Desk".into(),
14822            last_redirecting_number: "2050".into(),
14823            original_redirect_reason: 2,
14824            last_redirect_reason: 4,
14825            party_restrictions: 0xf,
14826        };
14827        handle
14828            .send(Command::new(
14829                device_id,
14830                CommandAction::SetCallInfo {
14831                    call_id: CallId(7001),
14832                    info: info.clone(),
14833                },
14834            ))
14835            .await
14836            .unwrap();
14837        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_INFO_DYNAMIC).await;
14838        assert!(frames.into_iter().any(|frame| matches!(
14839            ServerMessage::decode(frame, protocol),
14840            Ok(ServerMessage::CallInfo {
14841                info: actual,
14842                line_instance: 1,
14843                call_reference: 7001,
14844            }) if actual == info
14845        )));
14846
14847        handle.shutdown().await.unwrap();
14848        task.await.unwrap().unwrap();
14849    }
14850
14851    #[tokio::test]
14852    async fn unavailable_soft_key_events_and_stimuli_preserve_on_hook_state() {
14853        let mut device = definition();
14854        device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::Redial]);
14855        let config = ServerConfig {
14856            bind: "127.0.0.1:0".parse().unwrap(),
14857            advertised_address: Ipv4Addr::LOCALHOST,
14858            ..ServerConfig::default()
14859        };
14860        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
14861        let address = server.local_addr().unwrap();
14862        let task = tokio::spawn(server.run());
14863        let mut phone = TcpStream::connect(address).await.unwrap();
14864        let mut decoder = FrameDecoder::new();
14865        let protocol = ProtocolVersion::V22;
14866
14867        phone.write_all(&register_bytes(protocol)).await.unwrap();
14868        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14869        assert!(matches!(
14870            events.recv().await,
14871            Some(Event::Device(DeviceEvent {
14872                session_generation: _,
14873                device_id: _,
14874                event: DeviceEventKind::Registered(_)
14875            }))
14876        ));
14877
14878        phone
14879            .write_all(
14880                &[
14881                    ClientMessage::SoftKeyEvent {
14882                        event: SoftKey::NewCall.wire_value(),
14883                        line_instance: 1,
14884                        call_reference: 0,
14885                    }
14886                    .encode(protocol)
14887                    .unwrap(),
14888                    ClientMessage::Stimulus {
14889                        stimulus: Stimulus::NewCall,
14890                        instance: 1,
14891                        call_reference: 0,
14892                        status: 0,
14893                    }
14894                    .encode(protocol)
14895                    .unwrap(),
14896                ]
14897                .concat(),
14898            )
14899            .await
14900            .unwrap();
14901        let mut buffer = [0_u8; 256];
14902        assert!(
14903            tokio::time::timeout(Duration::from_millis(50), phone.read(&mut buffer))
14904                .await
14905                .is_err(),
14906            "unavailable actions unexpectedly changed the handset UI"
14907        );
14908        assert!(
14909            tokio::time::timeout(Duration::from_millis(50), events.recv())
14910                .await
14911                .is_err(),
14912            "unavailable actions unexpectedly emitted an application event"
14913        );
14914
14915        phone
14916            .write_all(
14917                &ClientMessage::Stimulus {
14918                    stimulus: Stimulus::Line,
14919                    instance: 1,
14920                    call_reference: 0,
14921                    status: 0,
14922                }
14923                .encode(protocol)
14924                .unwrap(),
14925            )
14926            .await
14927            .unwrap();
14928        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
14929        assert!(matches!(
14930            events.recv().await,
14931            Some(Event::Device(DeviceEvent {
14932                session_generation: _,
14933                device_id: _,
14934                event: DeviceEventKind::OffHook {
14935                    call_id: CallId(1),
14936                    ..
14937                }
14938            }))
14939        ));
14940
14941        handle.shutdown().await.unwrap();
14942        task.await.unwrap().unwrap();
14943    }
14944
14945    #[tokio::test]
14946    async fn one_way_intercom_uses_restricted_keys_active_identity_and_microphone_frame() {
14947        let config = ServerConfig {
14948            bind: "127.0.0.1:0".parse().unwrap(),
14949            advertised_address: Ipv4Addr::LOCALHOST,
14950            ..ServerConfig::default()
14951        };
14952        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14953        let address = server.local_addr().unwrap();
14954        let task = tokio::spawn(server.run());
14955        let mut phone = TcpStream::connect(address).await.unwrap();
14956        let mut decoder = FrameDecoder::new();
14957        let protocol = ProtocolVersion::V22;
14958
14959        phone.write_all(&register_bytes(protocol)).await.unwrap();
14960        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14961        assert!(matches!(
14962            events.recv().await,
14963            Some(Event::Device(DeviceEvent {
14964                session_generation: _,
14965                device_id: _,
14966                event: DeviceEventKind::Registered(_)
14967            }))
14968        ));
14969
14970        let device_id = DeviceId::new("SEP001122334455").unwrap();
14971        let call_id = CallId(7010);
14972        handle
14973            .send(Command::new(
14974                device_id.clone(),
14975                CommandAction::BeginCall {
14976                    line_instance: LineInstance(1),
14977                    call_id,
14978                    codec: Codec::Pcma,
14979                },
14980            ))
14981            .await
14982            .unwrap();
14983        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
14984        handle
14985            .send(Command::new(
14986                device_id.clone(),
14987                CommandAction::SetCallState {
14988                    call_id,
14989                    state: CallState::IntercomOneWay,
14990                },
14991            ))
14992            .await
14993            .unwrap();
14994        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
14995        assert!(frames.iter().any(|frame| matches!(
14996            ServerMessage::decode(frame.clone(), protocol),
14997            Ok(ServerMessage::CallState {
14998                state: CallState::IntercomOneWay,
14999                line_instance: 1,
15000                call_reference: 7010,
15001            })
15002        )));
15003        assert!(frames.iter().any(|frame| matches!(
15004            ServerMessage::decode(frame.clone(), protocol),
15005            Ok(ServerMessage::SelectSoftKeys {
15006                line_instance: 1,
15007                call_reference: 7010,
15008                set: KeyMode::OffHook,
15009                valid_mask: 1,
15010            })
15011        )));
15012
15013        phone
15014            .write_all(
15015                &ClientMessage::SoftKeyEvent {
15016                    event: SoftKey::NewCall.wire_value(),
15017                    line_instance: 1,
15018                    call_reference: 0,
15019                }
15020                .encode(protocol)
15021                .unwrap(),
15022            )
15023            .await
15024            .unwrap();
15025        assert!(
15026            tokio::time::timeout(Duration::from_millis(50), events.recv())
15027                .await
15028                .is_err()
15029        );
15030        phone
15031            .write_all(
15032                &ClientMessage::SoftKeyEvent {
15033                    event: SoftKey::EndCall.wire_value(),
15034                    line_instance: 1,
15035                    call_reference: 0,
15036                }
15037                .encode(protocol)
15038                .unwrap(),
15039            )
15040            .await
15041            .unwrap();
15042        assert!(matches!(
15043            events.recv().await,
15044            Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::SoftKey {
15045                call_id: Some(CallId(7010)),
15046                line_instance: LineInstance(1),
15047                soft_key: SoftKey::EndCall,
15048            } })) if actual_device == device_id
15049        ));
15050
15051        handle
15052            .send_confirmed(Command::new(
15053                device_id,
15054                CommandAction::SetMicrophoneMode { enabled: false },
15055            ))
15056            .await
15057            .unwrap();
15058        let frames = read_until_message(&mut phone, &mut decoder, id::SET_MICROPHONE_MODE).await;
15059        assert!(frames.into_iter().any(|frame| matches!(
15060            ServerMessage::decode(frame, protocol),
15061            Ok(ServerMessage::SetMicrophoneMode(MicrophoneMode::Off))
15062        )));
15063
15064        handle.shutdown().await.unwrap();
15065        task.await.unwrap().unwrap();
15066    }
15067
15068    #[tokio::test]
15069    async fn outbound_media_writes_receive_then_transmit_without_an_ack_boundary() {
15070        let device = definition();
15071        let device_id = device.id.clone();
15072        let config = ServerConfig {
15073            bind: "127.0.0.1:0".parse().unwrap(),
15074            advertised_address: Ipv4Addr::LOCALHOST,
15075            ..ServerConfig::default()
15076        };
15077        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15078        let address = server.local_addr().unwrap();
15079        let task = tokio::spawn(server.run());
15080        let mut phone = TcpStream::connect(address).await.unwrap();
15081        let mut decoder = FrameDecoder::new();
15082        let protocol = ProtocolVersion::V22;
15083
15084        phone.write_all(&register_bytes(protocol)).await.unwrap();
15085        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15086        assert!(matches!(
15087            events.recv().await,
15088            Some(Event::Device(DeviceEvent {
15089                session_generation: _,
15090                device_id: _,
15091                event: DeviceEventKind::Registered(_)
15092            }))
15093        ));
15094        phone
15095            .write_all(
15096                &ClientMessage::Stimulus {
15097                    stimulus: Stimulus::Line,
15098                    instance: 1,
15099                    call_reference: 0,
15100                    status: 0,
15101                }
15102                .encode(protocol)
15103                .unwrap(),
15104            )
15105            .await
15106            .unwrap();
15107        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15108        assert!(matches!(
15109            events.recv().await,
15110            Some(Event::Device(DeviceEvent {
15111                session_generation: _,
15112                device_id: _,
15113                event: DeviceEventKind::OffHook { .. }
15114            }))
15115        ));
15116
15117        phone
15118            .write_all(
15119                &ClientMessage::KeypadButton {
15120                    button: Digit::Number(2),
15121                    line_instance: 1,
15122                    call_reference: 1,
15123                    wire_layout: None,
15124                }
15125                .encode(protocol)
15126                .unwrap(),
15127            )
15128            .await
15129            .unwrap();
15130        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15131        assert!(matches!(
15132            events.recv().await,
15133            Some(Event::Device(DeviceEvent {
15134                session_generation: _,
15135                device_id: _,
15136                event: DeviceEventKind::Digit { .. }
15137            }))
15138        ));
15139        phone
15140            .write_all(
15141                &ClientMessage::KeypadButton {
15142                    button: Digit::Pound,
15143                    line_instance: 1,
15144                    call_reference: 1,
15145                    wire_layout: None,
15146                }
15147                .encode(protocol)
15148                .unwrap(),
15149            )
15150            .await
15151            .unwrap();
15152        assert!(matches!(
15153            events.recv().await,
15154            Some(Event::Device(DeviceEvent {
15155                session_generation: _,
15156                device_id: _,
15157                event: DeviceEventKind::Digit {
15158                    digit: Digit::Pound,
15159                    ..
15160                }
15161            }))
15162        ));
15163        handle
15164            .send_confirmed(Command::new(
15165                device_id.clone(),
15166                CommandAction::CommitOutboundCall {
15167                    call_id: CallId(1),
15168                    info: CallInfo {
15169                        direction: crate::CallDirection::Outbound,
15170                        called_number: "2".into(),
15171                        ..CallInfo::default()
15172                    },
15173                },
15174            ))
15175            .await
15176            .unwrap();
15177        let prefix = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
15178        let stop_tone = prefix
15179            .iter()
15180            .position(|frame| frame.message_id == id::STOP_TONE)
15181            .expect("outbound route prefix omitted StopTone");
15182        let call_info = prefix
15183            .iter()
15184            .position(|frame| frame.message_id == id::CALL_INFO_DYNAMIC)
15185            .expect("outbound route prefix omitted CallInfo");
15186        let dialed_number = prefix
15187            .iter()
15188            .position(|frame| {
15189                matches!(
15190                    ServerMessage::decode(frame.clone(), protocol),
15191                    Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "2"
15192                )
15193            })
15194            .expect("outbound route prefix omitted DialedNumber");
15195        let proceed = prefix
15196            .iter()
15197            .position(|frame| {
15198                matches!(
15199                    ServerMessage::decode(frame.clone(), protocol),
15200                    Ok(ServerMessage::CallState {
15201                        state: CallState::Proceed,
15202                        ..
15203                    })
15204                )
15205            })
15206            .expect("outbound media prefix omitted Proceed");
15207        assert!(stop_tone < call_info && call_info < dialed_number && dialed_number < proceed);
15208        assert!(prefix[..proceed].iter().all(|frame| {
15209            !matches!(
15210                ServerMessage::decode(frame.clone(), protocol),
15211                Ok(ServerMessage::CallState {
15212                    state: CallState::OffHook,
15213                    ..
15214                })
15215            ) && frame.message_id != id::ACTIVATE_CALL_PLANE
15216        }));
15217
15218        let outbound_info = CallInfo {
15219            direction: crate::CallDirection::Outbound,
15220            called_name: "Remote Party".into(),
15221            called_number: "2".into(),
15222            ..CallInfo::default()
15223        };
15224        handle
15225            .send_confirmed(Command::new(
15226                device_id.clone(),
15227                CommandAction::PresentOutboundProceeding {
15228                    call_id: CallId(1),
15229                    info: outbound_info.clone(),
15230                },
15231            ))
15232            .await
15233            .unwrap();
15234        let proceeding =
15235            read_until_message(&mut phone, &mut decoder, id::DISPLAY_DYNAMIC_PROMPT_STATUS).await;
15236        let proceeding_ids = proceeding
15237            .iter()
15238            .map(|frame| frame.message_id)
15239            .collect::<Vec<_>>();
15240        let stop = proceeding_ids
15241            .iter()
15242            .position(|message_id| *message_id == id::STOP_TONE)
15243            .unwrap();
15244        let state = proceeding_ids
15245            .iter()
15246            .position(|message_id| *message_id == id::CALL_STATE)
15247            .unwrap();
15248        let info = proceeding_ids
15249            .iter()
15250            .position(|message_id| *message_id == id::CALL_INFO_DYNAMIC)
15251            .unwrap();
15252        let prompt = proceeding_ids
15253            .iter()
15254            .position(|message_id| *message_id == id::DISPLAY_DYNAMIC_PROMPT_STATUS)
15255            .unwrap();
15256        assert!(stop < state && state < info && info < prompt);
15257
15258        handle
15259            .send_confirmed(Command::new(
15260                device_id.clone(),
15261                CommandAction::PresentOutboundRinging {
15262                    call_id: CallId(1),
15263                    info: outbound_info,
15264                },
15265            ))
15266            .await
15267            .unwrap();
15268        let ringing = read_until_message(&mut phone, &mut decoder, id::CALL_INFO_DYNAMIC).await;
15269        let ringing_ids = ringing
15270            .iter()
15271            .map(|frame| frame.message_id)
15272            .collect::<Vec<_>>();
15273        let state = ringing_ids
15274            .iter()
15275            .position(|message_id| *message_id == id::CALL_STATE)
15276            .unwrap();
15277        let prompt = ringing_ids
15278            .iter()
15279            .position(|message_id| *message_id == id::DISPLAY_DYNAMIC_PROMPT_STATUS)
15280            .unwrap();
15281        let tone = ringing_ids
15282            .iter()
15283            .position(|message_id| *message_id == id::START_TONE)
15284            .unwrap();
15285        let keys = ringing_ids
15286            .iter()
15287            .position(|message_id| *message_id == id::SELECT_SOFT_KEYS)
15288            .unwrap();
15289        let info = ringing_ids
15290            .iter()
15291            .position(|message_id| *message_id == id::CALL_INFO_DYNAMIC)
15292            .unwrap();
15293        assert!(state < prompt && prompt < tone && tone < keys && keys < info);
15294        assert_eq!(
15295            ringing
15296                .iter()
15297                .filter(|frame| frame.message_id == id::DISPLAY_DYNAMIC_PROMPT_STATUS)
15298                .count(),
15299            1,
15300            "outbound ringing flashed an intermediate prompt"
15301        );
15302
15303        let endpoint = MediaEndpoint {
15304            address: "198.51.100.20".parse().unwrap(),
15305            rtp_port: 6000,
15306            rtcp_port: 6001,
15307            codec: Codec::Pcma,
15308            packet_ms: 20,
15309            max_frames_per_packet: 1,
15310            telephone_event_payload: 0,
15311        };
15312        handle
15313            .send_confirmed(Command::new(
15314                device_id.clone(),
15315                CommandAction::OpenOutboundMedia {
15316                    call_id: CallId(1),
15317                    source: None,
15318                    endpoint,
15319                    codec: Codec::Pcma,
15320                    packet_ms: 20,
15321                    max_frames_per_packet: 1,
15322                    dtmf_mode: DtmfMode::Auto,
15323                    audio_processing: AudioProcessingPolicy::default(),
15324                    traffic_class: MediaTrafficClass::default(),
15325                },
15326            ))
15327            .await
15328            .unwrap();
15329        let frames =
15330            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15331        let receive = frames
15332            .iter()
15333            .position(|frame| frame.message_id == id::OPEN_RECEIVE_CHANNEL)
15334            .expect("coupled transaction omitted OpenReceiveChannel");
15335        let transmit = frames
15336            .iter()
15337            .position(|frame| frame.message_id == id::START_MEDIA_TRANSMISSION)
15338            .expect("coupled transaction omitted StartMediaTransmission");
15339        let first_request_party = coupled_media_request_party(&frames, protocol);
15340        assert_eq!(transmit, receive + 1);
15341        assert!(matches!(
15342            ServerMessage::decode(frames[receive].clone(), protocol).unwrap(),
15343            ServerMessage::OpenReceiveChannel {
15344                source_address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
15345                source_port: 0,
15346                codec: Codec::Pcma,
15347                ..
15348            }
15349        ));
15350        assert!(matches!(
15351            ServerMessage::decode(frames[transmit].clone(), protocol).unwrap(),
15352            ServerMessage::StartMediaTransmission {
15353                endpoint: actual,
15354                ..
15355            } if actual.address == endpoint.address
15356                && actual.rtp_port == endpoint.rtp_port
15357                && actual.codec == endpoint.codec
15358        ));
15359
15360        let receive_peer = MediaEndpoint {
15361            address: "192.0.2.44".parse().unwrap(),
15362            rtp_port: 4000,
15363            rtcp_port: 4001,
15364            codec: Codec::Pcma,
15365            packet_ms: 20,
15366            max_frames_per_packet: 1,
15367            telephone_event_payload: 0,
15368        };
15369        phone
15370            .write_all(
15371                &ClientMessage::OpenReceiveChannelAck {
15372                    status: MediaStatus::Ok,
15373                    address: receive_peer.address,
15374                    port: receive_peer.rtp_port,
15375                    call_reference: 1,
15376                    passthrough_party_id: first_request_party,
15377                }
15378                .encode(protocol)
15379                .unwrap(),
15380            )
15381            .await
15382            .unwrap();
15383        assert!(matches!(
15384            events.recv().await,
15385            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::ReceiveChannelOpened {
15386                call_id: CallId(1),
15387                status: MediaStatus::Ok,
15388                endpoint: actual,
15389                ..
15390            } })) if actual == receive_peer
15391        ));
15392        assert!(matches!(
15393            events.recv().await,
15394            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelImplied {
15395                call_id: CallId(1),
15396                endpoint: actual,
15397                ..
15398            } })) if actual == endpoint
15399        ));
15400
15401        phone
15402            .write_all(
15403                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15404                    conference_id: 1,
15405                    passthrough_party_id: first_request_party,
15406                    call_reference: 1,
15407                    status: MediaStatus::Ok,
15408                    address: endpoint.address,
15409                    port: endpoint.rtp_port,
15410                    wire: None,
15411                })
15412                .encode(protocol)
15413                .unwrap(),
15414            )
15415            .await
15416            .unwrap();
15417        assert!(
15418            tokio::time::timeout(Duration::from_millis(50), events.recv())
15419                .await
15420                .is_err(),
15421            "late explicit transmit acknowledgement re-settled coupled media"
15422        );
15423
15424        handle
15425            .send(Command::new(
15426                device_id.clone(),
15427                CommandAction::OpenOutboundMedia {
15428                    call_id: CallId(1),
15429                    source: None,
15430                    endpoint,
15431                    codec: Codec::Pcma,
15432                    packet_ms: 20,
15433                    max_frames_per_packet: 1,
15434                    dtmf_mode: DtmfMode::Auto,
15435                    audio_processing: AudioProcessingPolicy::default(),
15436                    traffic_class: MediaTrafficClass::default(),
15437                },
15438            ))
15439            .await
15440            .unwrap();
15441        let frames =
15442            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15443        let second_request_party = coupled_media_request_party(&frames, protocol);
15444        assert_ne!(second_request_party, first_request_party);
15445        phone
15446            .write_all(
15447                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15448                    conference_id: 1,
15449                    passthrough_party_id: first_request_party,
15450                    call_reference: 1,
15451                    status: MediaStatus::Ok,
15452                    address: endpoint.address,
15453                    port: endpoint.rtp_port,
15454                    wire: None,
15455                })
15456                .encode(protocol)
15457                .unwrap(),
15458            )
15459            .await
15460            .unwrap();
15461        assert!(
15462            tokio::time::timeout(Duration::from_millis(50), events.recv())
15463                .await
15464                .is_err(),
15465            "a prior media generation settled the reopened transmit request"
15466        );
15467        phone
15468            .write_all(
15469                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15470                    conference_id: 1,
15471                    passthrough_party_id: second_request_party,
15472                    call_reference: 1,
15473                    status: MediaStatus::Ok,
15474                    address: endpoint.address,
15475                    port: endpoint.rtp_port,
15476                    wire: None,
15477                })
15478                .encode(protocol)
15479                .unwrap(),
15480            )
15481            .await
15482            .unwrap();
15483        assert!(matches!(
15484            events.recv().await,
15485            Some(Event::Device(DeviceEvent {
15486                session_generation: _,
15487                device_id: _,
15488                event: DeviceEventKind::TransmitChannelStarted {
15489                    call_id: CallId(1),
15490                    status: MediaStatus::Ok,
15491                    ..
15492                }
15493            }))
15494        ));
15495        phone
15496            .write_all(
15497                &ClientMessage::OpenReceiveChannelAck {
15498                    status: MediaStatus::Ok,
15499                    address: receive_peer.address,
15500                    port: receive_peer.rtp_port,
15501                    call_reference: 1,
15502                    passthrough_party_id: second_request_party,
15503                }
15504                .encode(protocol)
15505                .unwrap(),
15506            )
15507            .await
15508            .unwrap();
15509        assert!(matches!(
15510            events.recv().await,
15511            Some(Event::Device(DeviceEvent {
15512                session_generation: _,
15513                device_id: _,
15514                event: DeviceEventKind::ReceiveChannelOpened {
15515                    call_id: CallId(1),
15516                    status: MediaStatus::Ok,
15517                    ..
15518                }
15519            }))
15520        ));
15521        assert!(
15522            tokio::time::timeout(Duration::from_millis(50), events.recv())
15523                .await
15524                .is_err(),
15525            "receive acknowledgement duplicated an explicitly settled transmit event"
15526        );
15527
15528        handle
15529            .send(Command::new(
15530                device_id.clone(),
15531                CommandAction::OpenOutboundMedia {
15532                    call_id: CallId(1),
15533                    source: None,
15534                    endpoint,
15535                    codec: Codec::Pcma,
15536                    packet_ms: 20,
15537                    max_frames_per_packet: 1,
15538                    dtmf_mode: DtmfMode::Auto,
15539                    audio_processing: AudioProcessingPolicy::default(),
15540                    traffic_class: MediaTrafficClass::default(),
15541                },
15542            ))
15543            .await
15544            .unwrap();
15545        let frames =
15546            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15547        let third_request_party = coupled_media_request_party(&frames, protocol);
15548        assert_ne!(third_request_party, second_request_party);
15549        phone
15550            .write_all(
15551                &ClientMessage::OpenReceiveChannelAck {
15552                    status: MediaStatus::UnspecifiedError,
15553                    address: receive_peer.address,
15554                    port: receive_peer.rtp_port,
15555                    call_reference: 1,
15556                    passthrough_party_id: third_request_party,
15557                }
15558                .encode(protocol)
15559                .unwrap(),
15560            )
15561            .await
15562            .unwrap();
15563        assert!(matches!(
15564            events.recv().await,
15565            Some(Event::Device(DeviceEvent {
15566                session_generation: _,
15567                device_id: _,
15568                event: DeviceEventKind::ReceiveChannelOpened {
15569                    call_id: CallId(1),
15570                    status: MediaStatus::UnspecifiedError,
15571                    ..
15572                }
15573            }))
15574        ));
15575        assert!(
15576            tokio::time::timeout(Duration::from_millis(50), events.recv())
15577                .await
15578                .is_err(),
15579            "failed coupled receive emitted a transmit-success event"
15580        );
15581        phone
15582            .write_all(
15583                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15584                    conference_id: 1,
15585                    passthrough_party_id: third_request_party,
15586                    call_reference: 1,
15587                    status: MediaStatus::Ok,
15588                    address: endpoint.address,
15589                    port: endpoint.rtp_port,
15590                    wire: None,
15591                })
15592                .encode(protocol)
15593                .unwrap(),
15594            )
15595            .await
15596            .unwrap();
15597        assert!(
15598            tokio::time::timeout(Duration::from_millis(50), events.recv())
15599                .await
15600                .is_err(),
15601            "late transmit acknowledgement resurrected a failed coupled transaction"
15602        );
15603
15604        handle
15605            .send(Command::new(
15606                device_id,
15607                CommandAction::OpenOutboundMedia {
15608                    call_id: CallId(1),
15609                    source: None,
15610                    endpoint,
15611                    codec: Codec::Pcma,
15612                    packet_ms: 20,
15613                    max_frames_per_packet: 1,
15614                    dtmf_mode: DtmfMode::Auto,
15615                    audio_processing: AudioProcessingPolicy::default(),
15616                    traffic_class: MediaTrafficClass::default(),
15617                },
15618            ))
15619            .await
15620            .unwrap();
15621        let frames =
15622            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15623        let fourth_request_party = coupled_media_request_party(&frames, protocol);
15624        assert_ne!(fourth_request_party, third_request_party);
15625        phone
15626            .write_all(
15627                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15628                    conference_id: 1,
15629                    passthrough_party_id: fourth_request_party,
15630                    call_reference: 1,
15631                    status: MediaStatus::UnspecifiedError,
15632                    address: endpoint.address,
15633                    port: endpoint.rtp_port,
15634                    wire: None,
15635                })
15636                .encode(protocol)
15637                .unwrap(),
15638            )
15639            .await
15640            .unwrap();
15641        assert!(matches!(
15642            events.recv().await,
15643            Some(Event::Device(DeviceEvent {
15644                session_generation: _,
15645                device_id: _,
15646                event: DeviceEventKind::TransmitChannelStarted {
15647                    call_id: CallId(1),
15648                    status: MediaStatus::UnspecifiedError,
15649                    ..
15650                }
15651            }))
15652        ));
15653        phone
15654            .write_all(
15655                &ClientMessage::OpenReceiveChannelAck {
15656                    status: MediaStatus::Ok,
15657                    address: receive_peer.address,
15658                    port: receive_peer.rtp_port,
15659                    call_reference: 1,
15660                    passthrough_party_id: fourth_request_party,
15661                }
15662                .encode(protocol)
15663                .unwrap(),
15664            )
15665            .await
15666            .unwrap();
15667        assert!(
15668            tokio::time::timeout(Duration::from_millis(50), events.recv())
15669                .await
15670                .is_err(),
15671            "late receive acknowledgement resurrected a failed coupled transaction"
15672        );
15673
15674        handle.shutdown().await.unwrap();
15675        task.await.unwrap().unwrap();
15676    }
15677
15678    #[tokio::test]
15679    async fn invalid_coupled_media_is_rejected_without_disconnect() {
15680        let device = definition();
15681        let device_id = device.id.clone();
15682        let config = ServerConfig {
15683            bind: "127.0.0.1:0".parse().unwrap(),
15684            advertised_address: Ipv4Addr::LOCALHOST,
15685            ..ServerConfig::default()
15686        };
15687        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15688        let address = server.local_addr().unwrap();
15689        let task = tokio::spawn(server.run());
15690        let mut phone = TcpStream::connect(address).await.unwrap();
15691        let mut decoder = FrameDecoder::new();
15692        let protocol = ProtocolVersion::V22;
15693
15694        phone.write_all(&register_bytes(protocol)).await.unwrap();
15695        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15696        assert!(matches!(
15697            events.recv().await,
15698            Some(Event::Device(DeviceEvent {
15699                session_generation: _,
15700                device_id: _,
15701                event: DeviceEventKind::Registered(_)
15702            }))
15703        ));
15704        phone
15705            .write_all(
15706                &ClientMessage::Stimulus {
15707                    stimulus: Stimulus::Line,
15708                    instance: 1,
15709                    call_reference: 0,
15710                    status: 0,
15711                }
15712                .encode(protocol)
15713                .unwrap(),
15714            )
15715            .await
15716            .unwrap();
15717        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15718        assert!(matches!(
15719            events.recv().await,
15720            Some(Event::Device(DeviceEvent {
15721                session_generation: _,
15722                device_id: _,
15723                event: DeviceEventKind::OffHook { .. }
15724            }))
15725        ));
15726
15727        let endpoint = MediaEndpoint {
15728            address: "198.51.100.20".parse().unwrap(),
15729            rtp_port: 6000,
15730            rtcp_port: 6001,
15731            codec: Codec::Pcma,
15732            packet_ms: 20,
15733            max_frames_per_packet: 1,
15734            telephone_event_payload: 0,
15735        };
15736        assert!(matches!(
15737            handle
15738                .send_confirmed(Command::new(device_id.clone(), CommandAction::OpenOutboundMedia {
15739                    call_id: CallId(1),
15740                    source: None,
15741                    endpoint,
15742                    codec: Codec::Pcma,
15743                    packet_ms: 20,
15744                    max_frames_per_packet: 1,
15745                    dtmf_mode: DtmfMode::Auto,
15746                    audio_processing: AudioProcessingPolicy::default(),
15747                    traffic_class: MediaTrafficClass::default(),
15748                }))
15749                .await,
15750            Err(ServerError::CommandWrite(message))
15751                if message.contains("cannot open coupled outbound media while in state OffHook")
15752        ));
15753        assert!(!task.is_finished());
15754
15755        handle
15756            .send_confirmed(Command::new(
15757                device_id,
15758                CommandAction::SetCallState {
15759                    call_id: CallId(1),
15760                    state: CallState::Proceed,
15761                },
15762            ))
15763            .await
15764            .unwrap();
15765        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15766        assert!(frames.iter().any(|frame| matches!(
15767            ServerMessage::decode(frame.clone(), protocol),
15768            Ok(ServerMessage::CallState {
15769                state: CallState::Proceed,
15770                ..
15771            })
15772        )));
15773
15774        handle.shutdown().await.unwrap();
15775        task.await.unwrap().unwrap();
15776    }
15777
15778    #[tokio::test]
15779    async fn stale_public_command_does_not_stop_the_listener() {
15780        let device = definition();
15781        let config = ServerConfig {
15782            bind: "127.0.0.1:0".parse().unwrap(),
15783            advertised_address: Ipv4Addr::LOCALHOST,
15784            ..ServerConfig::default()
15785        };
15786        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15787        let address = server.local_addr().unwrap();
15788        let task = tokio::spawn(server.run());
15789
15790        handle
15791            .send(Command::new(
15792                DeviceId::new("SEPFFFFFFFFFFFF").unwrap(),
15793                CommandAction::SetMwi {
15794                    line_instance: LineInstance(1),
15795                    enabled: true,
15796                },
15797            ))
15798            .await
15799            .unwrap();
15800        tokio::task::yield_now().await;
15801        assert!(!task.is_finished());
15802
15803        let protocol = ProtocolVersion::V22;
15804        let mut phone = TcpStream::connect(address).await.unwrap();
15805        let mut decoder = FrameDecoder::new();
15806        phone.write_all(&register_bytes(protocol)).await.unwrap();
15807        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15808        assert!(matches!(
15809            events.recv().await,
15810            Some(Event::Device(DeviceEvent {
15811                session_generation: _,
15812                device_id: _,
15813                event: DeviceEventKind::Registered(_)
15814            }))
15815        ));
15816
15817        handle.shutdown().await.unwrap();
15818        task.await.unwrap().unwrap();
15819    }
15820
15821    #[tokio::test]
15822    async fn configured_dtmf_mode_selects_rtp_or_signaling_without_duplicate_digits() {
15823        let device = definition();
15824        let device_id = device.id.clone();
15825        let config = ServerConfig {
15826            bind: "127.0.0.1:0".parse().unwrap(),
15827            advertised_address: Ipv4Addr::LOCALHOST,
15828            ..ServerConfig::default()
15829        };
15830        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15831        let address = server.local_addr().unwrap();
15832        let task = tokio::spawn(server.run());
15833        let mut phone = TcpStream::connect(address).await.unwrap();
15834        let mut decoder = FrameDecoder::new();
15835        let protocol = ProtocolVersion::V22;
15836
15837        phone
15838            .write_all(&register_bytes_with_features(
15839                protocol,
15840                PhoneFeatures::RFC2833,
15841            ))
15842            .await
15843            .unwrap();
15844        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15845        assert!(matches!(
15846            events.recv().await,
15847            Some(Event::Device(DeviceEvent {
15848                session_generation: _,
15849                device_id: _,
15850                event: DeviceEventKind::Registered(_)
15851            }))
15852        ));
15853
15854        phone
15855            .write_all(
15856                &ClientMessage::Stimulus {
15857                    stimulus: Stimulus::Line,
15858                    instance: 1,
15859                    call_reference: 0,
15860                    status: 0,
15861                }
15862                .encode(protocol)
15863                .unwrap(),
15864            )
15865            .await
15866            .unwrap();
15867        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15868        assert!(matches!(
15869            events.recv().await,
15870            Some(Event::Device(DeviceEvent {
15871                session_generation: _,
15872                device_id: _,
15873                event: DeviceEventKind::OffHook {
15874                    call_id: CallId(1),
15875                    ..
15876                }
15877            }))
15878        ));
15879
15880        handle
15881            .send(Command::new(
15882                device_id.clone(),
15883                CommandAction::SetCallState {
15884                    call_id: CallId(1),
15885                    state: CallState::Connected,
15886                },
15887            ))
15888            .await
15889            .unwrap();
15890        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15891        handle
15892            .send(Command::new(
15893                device_id.clone(),
15894                CommandAction::OpenReceiveChannel {
15895                    call_id: CallId(1),
15896                    source: Some(MediaEndpoint {
15897                        address: "192.0.2.1".parse().unwrap(),
15898                        rtp_port: 4000,
15899                        rtcp_port: 4001,
15900                        codec: Codec::Pcmu,
15901                        packet_ms: 20,
15902                        max_frames_per_packet: 1,
15903                        telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
15904                    }),
15905                    codec: Codec::Pcmu,
15906                    packet_ms: 20,
15907                    max_frames_per_packet: 1,
15908                    dtmf_mode: DtmfMode::Auto,
15909                    audio_processing: AudioProcessingPolicy {
15910                        echo_cancellation: crate::EchoCancellation::Off,
15911                        silence_suppression: crate::SilenceSuppression::On,
15912                    },
15913                },
15914            ))
15915            .await
15916            .unwrap();
15917        let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
15918        assert_eq!(
15919            frames
15920                .iter()
15921                .filter(|frame| frame.message_id == id::SUBSCRIBE_DTMF_PAYLOAD_REQ)
15922                .count(),
15923            0,
15924            "RFC2833 is negotiated in the media messages, not with an unsolicited subscription"
15925        );
15926        handle
15927            .send(Command::new(
15928                device_id.clone(),
15929                CommandAction::StopMedia { call_id: CallId(1) },
15930            ))
15931            .await
15932            .unwrap();
15933        let frame = frames
15934            .into_iter()
15935            .find(|frame| frame.message_id == id::OPEN_RECEIVE_CHANNEL)
15936            .unwrap();
15937        assert!(matches!(
15938            ServerMessage::decode(frame, protocol).unwrap(),
15939            ServerMessage::OpenReceiveChannel {
15940                echo_cancellation: crate::EchoCancellation::Off,
15941                telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
15942                source_address,
15943                source_port: 4000,
15944                ..
15945            } if source_address == "192.0.2.1".parse::<std::net::IpAddr>().unwrap()
15946        ));
15947        handle
15948            .send(Command::new(
15949                device_id.clone(),
15950                CommandAction::StartMedia {
15951                    call_id: CallId(1),
15952                    endpoint: MediaEndpoint {
15953                        address: IpAddr::V4(Ipv4Addr::LOCALHOST),
15954                        rtp_port: 4000,
15955                        rtcp_port: 4001,
15956                        codec: Codec::Pcmu,
15957                        packet_ms: 20,
15958                        max_frames_per_packet: 1,
15959                        telephone_event_payload: 0,
15960                    },
15961                    dtmf_mode: DtmfMode::Auto,
15962                    audio_processing: AudioProcessingPolicy {
15963                        echo_cancellation: crate::EchoCancellation::Off,
15964                        silence_suppression: crate::SilenceSuppression::On,
15965                    },
15966                    traffic_class: MediaTrafficClass::default(),
15967                },
15968            ))
15969            .await
15970            .unwrap();
15971        let frames =
15972            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15973        assert!(
15974            frames
15975                .iter()
15976                .all(|frame| frame.message_id != id::SUBSCRIBE_DTMF_PAYLOAD_REQ),
15977            "starting the second media direction resubscribed RFC2833"
15978        );
15979        let start_media_party = start_media_request_party(&frames, protocol);
15980        let frame = frames
15981            .into_iter()
15982            .find(|frame| frame.message_id == id::START_MEDIA_TRANSMISSION)
15983            .unwrap();
15984        assert!(matches!(
15985            ServerMessage::decode(frame, protocol).unwrap(),
15986            ServerMessage::StartMediaTransmission {
15987                silence_suppression: crate::SilenceSuppression::On,
15988                endpoint: MediaEndpoint {
15989                    telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
15990                    ..
15991                },
15992                ..
15993            }
15994        ));
15995
15996        phone
15997            .write_all(
15998                &ClientMessage::KeypadButton {
15999                    button: Digit::Number(4),
16000                    line_instance: 1,
16001                    call_reference: 1,
16002                    wire_layout: None,
16003                }
16004                .encode(protocol)
16005                .unwrap(),
16006            )
16007            .await
16008            .unwrap();
16009        assert!(matches!(
16010            events.recv().await,
16011            Some(Event::Device(DeviceEvent {
16012                session_generation: _,
16013                device_id: _,
16014                event: DeviceEventKind::Digit {
16015                    call_id: CallId(1),
16016                    digit: Digit::Number(4),
16017                    ..
16018                }
16019            }))
16020        ));
16021
16022        phone
16023            .write_all(
16024                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16025                    conference_id: 99,
16026                    passthrough_party_id: start_media_party,
16027                    call_reference: 1,
16028                    status: MediaStatus::Ok,
16029                    address: IpAddr::V4(Ipv4Addr::LOCALHOST),
16030                    port: 4998,
16031                    wire: None,
16032                })
16033                .encode(protocol)
16034                .unwrap(),
16035            )
16036            .await
16037            .unwrap();
16038        assert!(
16039            tokio::time::timeout(Duration::from_millis(50), events.recv())
16040                .await
16041                .is_err(),
16042            "mismatched conference identifier was correlated to a call"
16043        );
16044
16045        phone
16046            .write_all(
16047                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16048                    conference_id: 1,
16049                    passthrough_party_id: start_media_party.saturating_add(1),
16050                    call_reference: 1,
16051                    status: MediaStatus::Ok,
16052                    address: IpAddr::V4(Ipv4Addr::LOCALHOST),
16053                    port: 4999,
16054                    wire: None,
16055                })
16056                .encode(protocol)
16057                .unwrap(),
16058            )
16059            .await
16060            .unwrap();
16061        assert!(
16062            tokio::time::timeout(Duration::from_millis(50), events.recv())
16063                .await
16064                .is_err(),
16065            "mismatched media identifiers were correlated to a call"
16066        );
16067
16068        phone
16069            .write_all(
16070                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16071                    conference_id: 0,
16072                    passthrough_party_id: start_media_party,
16073                    call_reference: 1,
16074                    status: MediaStatus::Ok,
16075                    address: "192.168.10.20".parse().unwrap(),
16076                    port: 4000,
16077                    wire: None,
16078                })
16079                .encode(protocol)
16080                .unwrap(),
16081            )
16082            .await
16083            .unwrap();
16084        assert!(matches!(
16085            events.recv().await,
16086            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelStarted {
16087                call_id: CallId(1),
16088                status: MediaStatus::Ok,
16089                endpoint: MediaEndpoint {
16090                    address,
16091                    rtp_port: 4000,
16092                    telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
16093                    ..
16094                },
16095                ..
16096            } })) if address == "192.168.10.20".parse::<IpAddr>().unwrap()
16097        ));
16098
16099        phone
16100            .write_all(
16101                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16102                    conference_id: 1,
16103                    passthrough_party_id: start_media_party,
16104                    call_reference: 1,
16105                    status: MediaStatus::Ok,
16106                    address: IpAddr::V4(Ipv4Addr::LOCALHOST),
16107                    port: 4000,
16108                    wire: None,
16109                })
16110                .encode(protocol)
16111                .unwrap(),
16112            )
16113            .await
16114            .unwrap();
16115        assert!(
16116            tokio::time::timeout(Duration::from_millis(50), events.recv())
16117                .await
16118                .is_err(),
16119            "duplicate transmit acknowledgement emitted a second event"
16120        );
16121
16122        let failed_address = "192.168.10.20".parse().unwrap();
16123        phone
16124            .write_all(
16125                &ClientMessage::MediaTransmissionFailure {
16126                    conference_id: 99,
16127                    passthrough_party_id: start_media_party,
16128                    address: failed_address,
16129                    port: 4000,
16130                    call_reference: 1,
16131                    status: MediaStatus::UnspecifiedError,
16132                }
16133                .encode(protocol)
16134                .unwrap(),
16135            )
16136            .await
16137            .unwrap();
16138        assert!(
16139            tokio::time::timeout(Duration::from_millis(50), events.recv())
16140                .await
16141                .is_err(),
16142            "mismatched conference identifier emitted a media failure"
16143        );
16144        let failure = ClientMessage::MediaTransmissionFailure {
16145            conference_id: 1,
16146            passthrough_party_id: start_media_party,
16147            address: failed_address,
16148            port: 4000,
16149            call_reference: 1,
16150            status: MediaStatus::UnspecifiedError,
16151        };
16152        phone
16153            .write_all(&failure.encode(protocol).unwrap())
16154            .await
16155            .unwrap();
16156        assert!(matches!(
16157            events.recv().await,
16158            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::MediaTransmissionFailed {
16159                call_id: CallId(1),
16160                status: MediaStatus::UnspecifiedError,
16161                endpoint: MediaEndpoint {
16162                    address,
16163                    rtp_port: 4000,
16164                    ..
16165                },
16166                ..
16167            } })) if address == failed_address
16168        ));
16169        phone
16170            .write_all(&failure.encode(protocol).unwrap())
16171            .await
16172            .unwrap();
16173        assert!(
16174            tokio::time::timeout(Duration::from_millis(50), events.recv())
16175                .await
16176                .is_err(),
16177            "duplicate media failure emitted a second event"
16178        );
16179
16180        let recovery_address = IpAddr::V4(Ipv4Addr::LOCALHOST);
16181        handle
16182            .send(Command::new(
16183                device_id.clone(),
16184                CommandAction::StartMedia {
16185                    call_id: CallId(1),
16186                    endpoint: MediaEndpoint {
16187                        address: recovery_address,
16188                        rtp_port: 5000,
16189                        rtcp_port: 5001,
16190                        codec: Codec::Pcmu,
16191                        packet_ms: 20,
16192                        max_frames_per_packet: 1,
16193                        telephone_event_payload: 0,
16194                    },
16195                    dtmf_mode: DtmfMode::Auto,
16196                    audio_processing: AudioProcessingPolicy::default(),
16197                    traffic_class: MediaTrafficClass::default(),
16198                },
16199            ))
16200            .await
16201            .unwrap();
16202        let frames =
16203            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
16204        let recovery_media_party = start_media_request_party(&frames, protocol);
16205        assert_ne!(recovery_media_party, start_media_party);
16206        phone
16207            .write_all(
16208                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16209                    conference_id: 1,
16210                    passthrough_party_id: recovery_media_party,
16211                    call_reference: 1,
16212                    status: MediaStatus::Ok,
16213                    address: recovery_address,
16214                    port: 5000,
16215                    wire: None,
16216                })
16217                .encode(protocol)
16218                .unwrap(),
16219            )
16220            .await
16221            .unwrap();
16222        assert!(matches!(
16223            events.recv().await,
16224            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelStarted {
16225                call_id: CallId(1),
16226                status: MediaStatus::Ok,
16227                endpoint: MediaEndpoint {
16228                    address,
16229                    rtp_port: 5000,
16230                    ..
16231                },
16232                ..
16233            } })) if address == recovery_address
16234        ));
16235
16236        phone
16237            .write_all(
16238                &ClientMessage::KeypadButton {
16239                    button: Digit::Number(5),
16240                    line_instance: 1,
16241                    call_reference: 1,
16242                    wire_layout: None,
16243                }
16244                .encode(protocol)
16245                .unwrap(),
16246            )
16247            .await
16248            .unwrap();
16249        assert!(
16250            tokio::time::timeout(Duration::from_millis(50), events.recv())
16251                .await
16252                .is_err(),
16253            "acknowledged RTP DTMF also emitted a signaling digit"
16254        );
16255
16256        handle
16257            .send(Command::new(
16258                device_id.clone(),
16259                CommandAction::OpenReceiveChannel {
16260                    call_id: CallId(1),
16261                    source: Some(MediaEndpoint {
16262                        address: "192.0.2.1".parse().unwrap(),
16263                        rtp_port: 4000,
16264                        rtcp_port: 4001,
16265                        codec: Codec::Pcmu,
16266                        packet_ms: 20,
16267                        max_frames_per_packet: 1,
16268                        telephone_event_payload: 0,
16269                    }),
16270                    codec: Codec::Pcmu,
16271                    packet_ms: 20,
16272                    max_frames_per_packet: 1,
16273                    dtmf_mode: DtmfMode::Skinny,
16274                    audio_processing: AudioProcessingPolicy::default(),
16275                },
16276            ))
16277            .await
16278            .unwrap();
16279        let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
16280        assert_eq!(
16281            frames
16282                .iter()
16283                .filter(|frame| frame.message_id == id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ)
16284                .count(),
16285            0,
16286            "changing one media direction unsubscribed the remaining RFC2833 stream"
16287        );
16288        let frame = frames
16289            .into_iter()
16290            .find(|frame| frame.message_id == id::OPEN_RECEIVE_CHANNEL)
16291            .unwrap();
16292        assert!(matches!(
16293            ServerMessage::decode(frame, protocol).unwrap(),
16294            ServerMessage::OpenReceiveChannel {
16295                telephone_event_payload: 0,
16296                ..
16297            }
16298        ));
16299        phone
16300            .write_all(
16301                &ClientMessage::KeypadButton {
16302                    button: Digit::Number(6),
16303                    line_instance: 1,
16304                    call_reference: 1,
16305                    wire_layout: None,
16306                }
16307                .encode(protocol)
16308                .unwrap(),
16309            )
16310            .await
16311            .unwrap();
16312        assert!(
16313            tokio::time::timeout(Duration::from_millis(50), events.recv())
16314                .await
16315                .is_err(),
16316            "the remaining RTP direction also emitted a signaling digit"
16317        );
16318
16319        handle
16320            .send(Command::new(
16321                device_id.clone(),
16322                CommandAction::StopMedia { call_id: CallId(1) },
16323            ))
16324            .await
16325            .unwrap();
16326        let frames =
16327            read_until_message(&mut phone, &mut decoder, id::STOP_MEDIA_TRANSMISSION).await;
16328        assert!(
16329            frames
16330                .iter()
16331                .all(|frame| frame.message_id != id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ)
16332        );
16333        handle
16334            .send(Command::new(
16335                device_id.clone(),
16336                CommandAction::StopMedia { call_id: CallId(1) },
16337            ))
16338            .await
16339            .unwrap();
16340        phone
16341            .write_all(
16342                &ClientMessage::KeypadButton {
16343                    button: Digit::Number(7),
16344                    line_instance: 1,
16345                    call_reference: 1,
16346                    wire_layout: None,
16347                }
16348                .encode(protocol)
16349                .unwrap(),
16350            )
16351            .await
16352            .unwrap();
16353        assert!(matches!(
16354            events.recv().await,
16355            Some(Event::Device(DeviceEvent {
16356                session_generation: _,
16357                device_id: _,
16358                event: DeviceEventKind::Digit {
16359                    call_id: CallId(1),
16360                    digit: Digit::Number(7),
16361                    ..
16362                }
16363            }))
16364        ));
16365
16366        handle
16367            .send(Command::new(
16368                device_id.clone(),
16369                CommandAction::CloseReceiveChannel { call_id: CallId(1) },
16370            ))
16371            .await
16372            .unwrap();
16373        let frames = read_until_message(&mut phone, &mut decoder, id::CLOSE_RECEIVE_CHANNEL).await;
16374        assert_eq!(
16375            frames
16376                .iter()
16377                .filter(|frame| frame.message_id == id::CLOSE_RECEIVE_CHANNEL)
16378                .count(),
16379            1
16380        );
16381        handle
16382            .send(Command::new(
16383                device_id.clone(),
16384                CommandAction::CloseReceiveChannel { call_id: CallId(1) },
16385            ))
16386            .await
16387            .unwrap();
16388        handle
16389            .send(Command::new(
16390                device_id.clone(),
16391                CommandAction::CloseCall { call_id: CallId(1) },
16392            ))
16393            .await
16394            .unwrap();
16395        let frames = read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
16396        assert!(frames.iter().all(|frame| !matches!(
16397            frame.message_id,
16398            id::STOP_MEDIA_TRANSMISSION | id::CLOSE_RECEIVE_CHANNEL
16399        )));
16400
16401        handle.shutdown().await.unwrap();
16402        task.await.unwrap().unwrap();
16403    }
16404
16405    #[test]
16406    fn handset_acknowledgement_deadlines_are_bounded_ordered_and_exactly_once() {
16407        let now = Instant::now();
16408        let mut first = session_call(20);
16409        first.media.receive.state = MediaChannelState::Opening;
16410        first.media.receive.deadline = Some(now);
16411        first.media.transmit.state = MediaChannelState::Opening;
16412        first.media.transmit.deadline = Some(now + Duration::from_millis(1));
16413        first.media.coupled_transmit_endpoint = Some(MediaEndpoint {
16414            address: "198.51.100.20".parse().unwrap(),
16415            rtp_port: 6000,
16416            rtcp_port: 6001,
16417            codec: Codec::Pcmu,
16418            packet_ms: 20,
16419            max_frames_per_packet: 1,
16420            telephone_event_payload: 0,
16421        });
16422        let mut second = session_call(10);
16423        second.media.transmit.state = MediaChannelState::Opening;
16424        second.media.transmit.deadline = Some(now);
16425        let mut calls = HashMap::from([(first.call_id, first), (second.call_id, second)]);
16426
16427        assert_eq!(
16428            expire_handset_acknowledgements(&mut calls, now),
16429            [
16430                (CallId(10), HandsetAcknowledgement::StartMediaTransmission,),
16431                (CallId(20), HandsetAcknowledgement::OpenReceiveChannel),
16432            ]
16433        );
16434        assert_eq!(
16435            calls[&CallId(10)].media.transmit.state,
16436            MediaChannelState::Closed
16437        );
16438        assert_eq!(
16439            calls[&CallId(20)].media.receive.state,
16440            MediaChannelState::Closed
16441        );
16442        assert_eq!(
16443            calls[&CallId(20)].media.transmit.state,
16444            MediaChannelState::Closed
16445        );
16446        assert!(calls[&CallId(20)].media.coupled_transmit_endpoint.is_none());
16447        assert!(expire_handset_acknowledgements(&mut calls, now).is_empty());
16448        assert!(
16449            expire_handset_acknowledgements(&mut calls, now + Duration::from_millis(1)).is_empty()
16450        );
16451        assert!(
16452            expire_handset_acknowledgements(&mut calls, now + Duration::from_secs(1)).is_empty()
16453        );
16454    }
16455
16456    #[tokio::test]
16457    async fn unknown_device_type_receives_the_configured_generic_layout() {
16458        let device = mixed_definition();
16459        let expected = button_template(&device);
16460        let config = ServerConfig {
16461            bind: "127.0.0.1:0".parse().unwrap(),
16462            advertised_address: Ipv4Addr::LOCALHOST,
16463            ..ServerConfig::default()
16464        };
16465        let (server, handle, _events) = Server::bind(config, [device]).await.unwrap();
16466        let address = server.local_addr().unwrap();
16467        let task = tokio::spawn(server.run());
16468        let mut phone = TcpStream::connect(address).await.unwrap();
16469        let mut decoder = FrameDecoder::new();
16470
16471        phone
16472            .write_all(&register_bytes_for_device_type(
16473                ProtocolVersion::V22,
16474                0xffff_fffe,
16475            ))
16476            .await
16477            .unwrap();
16478        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16479        phone
16480            .write_all(
16481                &ClientMessage::ButtonTemplateRequest
16482                    .encode(ProtocolVersion::V22)
16483                    .unwrap(),
16484            )
16485            .await
16486            .unwrap();
16487        let frames = read_until_message(&mut phone, &mut decoder, id::BUTTON_TEMPLATE).await;
16488        let frame = frames
16489            .into_iter()
16490            .find(|frame| frame.message_id == id::BUTTON_TEMPLATE)
16491            .unwrap();
16492        assert_eq!(
16493            ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
16494            ServerMessage::ButtonTemplate { buttons: expected }
16495        );
16496
16497        handle.shutdown().await.unwrap();
16498        task.await.unwrap().unwrap();
16499    }
16500
16501    #[tokio::test]
16502    async fn on_hook_enbloc_creates_one_addressable_call_before_atomic_routing() {
16503        let config = ServerConfig {
16504            bind: "127.0.0.1:0".parse().unwrap(),
16505            advertised_address: Ipv4Addr::LOCALHOST,
16506            ..ServerConfig::default()
16507        };
16508        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
16509        let address = server.local_addr().unwrap();
16510        let task = tokio::spawn(server.run());
16511        let mut phone = TcpStream::connect(address).await.unwrap();
16512        let mut decoder = FrameDecoder::new();
16513        let protocol = ProtocolVersion::V22;
16514
16515        phone.write_all(&register_bytes(protocol)).await.unwrap();
16516        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16517        assert!(matches!(
16518            events.recv().await,
16519            Some(Event::Device(DeviceEvent {
16520                session_generation: _,
16521                device_id: _,
16522                event: DeviceEventKind::Registered(_)
16523            }))
16524        ));
16525
16526        phone
16527            .write_all(
16528                &ClientMessage::EnblocCall {
16529                    called_party: "8675309".into(),
16530                    line_instance: 1,
16531                }
16532                .encode(protocol)
16533                .unwrap(),
16534            )
16535            .await
16536            .unwrap();
16537        let initial = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16538        assert!(initial.iter().any(|frame| {
16539            matches!(
16540                ServerMessage::decode(frame.clone(), protocol),
16541                Ok(ServerMessage::CallState {
16542                    state: CallState::OffHook,
16543                    ..
16544                })
16545            )
16546        }));
16547        assert!(
16548            initial
16549                .iter()
16550                .all(|frame| frame.message_id != id::DIALED_NUMBER)
16551        );
16552
16553        let call_id = match events.recv().await {
16554            Some(Event::Device(DeviceEvent {
16555                session_generation: _,
16556                device_id: _,
16557                event:
16558                    DeviceEventKind::OffHook {
16559                        call_id,
16560                        line_instance: LineInstance(1),
16561                        ..
16562                    },
16563            })) => call_id,
16564            event => {
16565                panic!("expected addressable off-hook call before en-bloc routing, got {event:?}")
16566            }
16567        };
16568        assert!(matches!(
16569            events.recv().await,
16570            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall {
16571                call_id: routed_call_id,
16572                line_instance: LineInstance(1),
16573                ref number,
16574                ..
16575            } })) if routed_call_id == call_id && number == "8675309"
16576        ));
16577        handle
16578            .send_confirmed(Command::new(
16579                DeviceId::new("SEP001122334455").unwrap(),
16580                CommandAction::CommitOutboundCall {
16581                    call_id,
16582                    info: CallInfo {
16583                        direction: crate::CallDirection::Outbound,
16584                        called_number: "8675309".into(),
16585                        ..CallInfo::default()
16586                    },
16587                },
16588            ))
16589            .await
16590            .unwrap();
16591        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16592        assert_eq!(
16593            frames
16594                .iter()
16595                .filter(|frame| frame.message_id == id::DIALED_NUMBER)
16596                .count(),
16597            1
16598        );
16599        assert!(frames.iter().any(|frame| matches!(
16600            ServerMessage::decode(frame.clone(), protocol),
16601            Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "8675309"
16602        )));
16603
16604        handle.shutdown().await.unwrap();
16605        task.await.unwrap().unwrap();
16606    }
16607
16608    #[tokio::test]
16609    async fn redial_reuses_the_last_completed_number_on_the_selected_line() {
16610        let config = ServerConfig {
16611            bind: "127.0.0.1:0".parse().unwrap(),
16612            advertised_address: Ipv4Addr::LOCALHOST,
16613            ..ServerConfig::default()
16614        };
16615        let mut device = definition();
16616        device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::Redial, SoftKey::NewCall]);
16617        let ButtonDefinition::Line(line) = &mut device.buttons[0] else {
16618            panic!("test station lost its line button");
16619        };
16620        line.initial_tone = Tone::RecallDial;
16621        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
16622        let address = server.local_addr().unwrap();
16623        let task = tokio::spawn(server.run());
16624        let mut phone = TcpStream::connect(address).await.unwrap();
16625        let mut decoder = FrameDecoder::new();
16626        let protocol = ProtocolVersion::V22;
16627
16628        phone.write_all(&register_bytes(protocol)).await.unwrap();
16629        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16630        assert!(matches!(
16631            events.recv().await,
16632            Some(Event::Device(DeviceEvent {
16633                session_generation: _,
16634                device_id: _,
16635                event: DeviceEventKind::Registered(_)
16636            }))
16637        ));
16638
16639        phone
16640            .write_all(
16641                &ClientMessage::OffHook {
16642                    line_instance: 1,
16643                    call_reference: 0,
16644                }
16645                .encode(protocol)
16646                .unwrap(),
16647            )
16648            .await
16649            .unwrap();
16650        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16651        let speaker = frames
16652            .iter()
16653            .position(|frame| {
16654                matches!(
16655                    ServerMessage::decode(frame.clone(), protocol),
16656                    Ok(ServerMessage::SetSpeakerMode(SpeakerMode::On))
16657                )
16658            })
16659            .expect("physical OffHook did not enable the speaker");
16660        let line_lamp = frames
16661            .iter()
16662            .position(|frame| {
16663                matches!(
16664                    ServerMessage::decode(frame.clone(), protocol),
16665                    Ok(ServerMessage::SetLamp {
16666                        stimulus: ButtonType::Line,
16667                        mode: LampMode::On,
16668                        ..
16669                    })
16670                )
16671            })
16672            .expect("physical OffHook did not enable the line lamp");
16673        let off_hook = frames
16674            .iter()
16675            .position(|frame| {
16676                matches!(
16677                    ServerMessage::decode(frame.clone(), protocol),
16678                    Ok(ServerMessage::CallState {
16679                        state: CallState::OffHook,
16680                        ..
16681                    })
16682                )
16683            })
16684            .expect("physical OffHook did not publish OffHook");
16685        let activate = frames
16686            .iter()
16687            .position(|frame| frame.message_id == id::ACTIVATE_CALL_PLANE)
16688            .expect("physical OffHook did not activate the call plane");
16689        let prompt = frames
16690            .iter()
16691            .position(|frame| {
16692                matches!(
16693                    ServerMessage::decode(frame.clone(), protocol),
16694                    Ok(ServerMessage::DisplayPrompt { ref text, .. }) if text == "Enter number"
16695                )
16696            })
16697            .expect("physical OffHook did not prompt for digits");
16698        let dial_tone = frames
16699            .iter()
16700            .position(|frame| {
16701                matches!(
16702                    ServerMessage::decode(frame.clone(), protocol),
16703                    Ok(ServerMessage::StartTone {
16704                        tone: Tone::RecallDial,
16705                        ..
16706                    })
16707                )
16708            })
16709            .expect("physical OffHook did not start dial tone");
16710        let soft_keys = frames
16711            .iter()
16712            .position(|frame| frame.message_id == id::SELECT_SOFT_KEYS)
16713            .expect("physical OffHook did not select off-hook keys");
16714        assert!(
16715            speaker < line_lamp
16716                && line_lamp < off_hook
16717                && off_hook < activate
16718                && activate < prompt
16719                && prompt < dial_tone
16720                && dial_tone < soft_keys
16721        );
16722        let call_reference = frames
16723            .iter()
16724            .find_map(
16725                |frame| match ServerMessage::decode(frame.clone(), protocol) {
16726                    Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
16727                    _ => None,
16728                },
16729            )
16730            .unwrap();
16731        let first_call_id = match events.recv().await {
16732            Some(Event::Device(DeviceEvent {
16733                session_generation: _,
16734                device_id: _,
16735                event: DeviceEventKind::OffHook { call_id, .. },
16736            })) => call_id,
16737            event => panic!("unexpected first redial OffHook event: {event:?}"),
16738        };
16739
16740        phone
16741            .write_all(
16742                &ClientMessage::EnblocCall {
16743                    called_party: "5551212".into(),
16744                    line_instance: 1,
16745                }
16746                .encode(protocol)
16747                .unwrap(),
16748            )
16749            .await
16750            .unwrap();
16751        assert!(matches!(
16752            events.recv().await,
16753            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall { ref number, .. } })) if number == "5551212"
16754        ));
16755        handle
16756            .send_confirmed(Command::new(
16757                DeviceId::new("SEP001122334455").unwrap(),
16758                CommandAction::CommitOutboundCall {
16759                    call_id: first_call_id,
16760                    info: CallInfo {
16761                        direction: crate::CallDirection::Outbound,
16762                        called_number: "5551212".into(),
16763                        ..CallInfo::default()
16764                    },
16765                },
16766            ))
16767            .await
16768            .unwrap();
16769        read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16770
16771        phone
16772            .write_all(
16773                &ClientMessage::OnHook {
16774                    line_instance: 1,
16775                    call_reference,
16776                }
16777                .encode(protocol)
16778                .unwrap(),
16779            )
16780            .await
16781            .unwrap();
16782        read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
16783        assert!(matches!(
16784            events.recv().await,
16785            Some(Event::Device(DeviceEvent {
16786                session_generation: _,
16787                device_id: _,
16788                event: DeviceEventKind::OnHook { .. }
16789            }))
16790        ));
16791
16792        phone
16793            .write_all(
16794                &ClientMessage::SoftKeyEvent {
16795                    event: SoftKey::Redial.wire_value(),
16796                    line_instance: 1,
16797                    call_reference: 0,
16798                }
16799                .encode(protocol)
16800                .unwrap(),
16801            )
16802            .await
16803            .unwrap();
16804        let initial = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16805        let redial_call_reference =
16806            initial.iter().find_map(
16807                |frame| match ServerMessage::decode(frame.clone(), protocol) {
16808                    Ok(ServerMessage::CallState {
16809                        state: CallState::OffHook,
16810                        call_reference,
16811                        ..
16812                    }) => Some(call_reference),
16813                    _ => None,
16814                },
16815            );
16816        let redial_call_id = match events.recv().await {
16817            Some(Event::Device(DeviceEvent {
16818                session_generation: _,
16819                device_id: _,
16820                event: DeviceEventKind::OffHook { call_id, .. },
16821            })) => call_id,
16822            event => panic!("unexpected redial OffHook event: {event:?}"),
16823        };
16824        assert!(matches!(
16825            events.recv().await,
16826            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall { ref number, .. } })) if number == "5551212"
16827        ));
16828        handle
16829            .send_confirmed(Command::new(
16830                DeviceId::new("SEP001122334455").unwrap(),
16831                CommandAction::CommitOutboundCall {
16832                    call_id: redial_call_id,
16833                    info: CallInfo {
16834                        direction: crate::CallDirection::Outbound,
16835                        called_number: "5551212".into(),
16836                        ..CallInfo::default()
16837                    },
16838                },
16839            ))
16840            .await
16841            .unwrap();
16842        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16843        assert!(frames.into_iter().any(|frame| matches!(
16844            ServerMessage::decode(frame, protocol),
16845            Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "5551212"
16846        )));
16847
16848        phone
16849            .write_all(
16850                &ClientMessage::OnHook {
16851                    line_instance: 1,
16852                    call_reference: redial_call_reference.unwrap(),
16853                }
16854                .encode(protocol)
16855                .unwrap(),
16856            )
16857            .await
16858            .unwrap();
16859        read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
16860        assert!(matches!(
16861            events.recv().await,
16862            Some(Event::Device(DeviceEvent {
16863                session_generation: _,
16864                device_id: _,
16865                event: DeviceEventKind::OnHook { .. }
16866            }))
16867        ));
16868
16869        phone
16870            .write_all(
16871                &ClientMessage::Stimulus {
16872                    stimulus: Stimulus::LastNumberRedial,
16873                    instance: 1,
16874                    call_reference: 0,
16875                    status: 0,
16876                }
16877                .encode(protocol)
16878                .unwrap(),
16879            )
16880            .await
16881            .unwrap();
16882        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16883        let stimulus_call_id = match events.recv().await {
16884            Some(Event::Device(DeviceEvent {
16885                session_generation: _,
16886                device_id: _,
16887                event: DeviceEventKind::OffHook { call_id, .. },
16888            })) => call_id,
16889            event => panic!("unexpected stimulus-redial OffHook event: {event:?}"),
16890        };
16891        assert!(matches!(
16892            events.recv().await,
16893            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall { ref number, .. } })) if number == "5551212"
16894        ));
16895        handle
16896            .send_confirmed(Command::new(
16897                DeviceId::new("SEP001122334455").unwrap(),
16898                CommandAction::CommitOutboundCall {
16899                    call_id: stimulus_call_id,
16900                    info: CallInfo {
16901                        direction: crate::CallDirection::Outbound,
16902                        called_number: "5551212".into(),
16903                        ..CallInfo::default()
16904                    },
16905                },
16906            ))
16907            .await
16908            .unwrap();
16909        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16910        assert!(frames.into_iter().any(|frame| matches!(
16911            ServerMessage::decode(frame, protocol),
16912            Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "5551212"
16913        )));
16914
16915        handle.shutdown().await.unwrap();
16916        task.await.unwrap().unwrap();
16917    }
16918
16919    #[tokio::test]
16920    async fn configured_redial_menu_uses_typed_native_action_with_legacy_fallback_policy() {
16921        assert!(!placed_calls_menu_supported(ProtocolVersion::V3));
16922        assert!(placed_calls_menu_supported(ProtocolVersion::V8));
16923        assert!(placed_calls_menu_supported(ProtocolVersion::V22));
16924
16925        let config = ServerConfig {
16926            bind: "127.0.0.1:0".parse().unwrap(),
16927            advertised_address: Ipv4Addr::LOCALHOST,
16928            ..ServerConfig::default()
16929        };
16930        let mut device = definition();
16931        device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::Redial]);
16932        device.ui.placed_calls_redial_menu = true;
16933        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
16934        let address = server.local_addr().unwrap();
16935        let task = tokio::spawn(server.run());
16936        let mut phone = TcpStream::connect(address).await.unwrap();
16937        let mut decoder = FrameDecoder::new();
16938        let protocol = ProtocolVersion::V22;
16939
16940        phone.write_all(&register_bytes(protocol)).await.unwrap();
16941        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16942        assert!(matches!(
16943            events.recv().await,
16944            Some(Event::Device(DeviceEvent {
16945                session_generation: _,
16946                device_id: _,
16947                event: DeviceEventKind::Registered(_)
16948            }))
16949        ));
16950
16951        phone
16952            .write_all(
16953                &ClientMessage::SoftKeyEvent {
16954                    event: SoftKey::Redial.wire_value(),
16955                    line_instance: 1,
16956                    call_reference: 0,
16957                }
16958                .encode(protocol)
16959                .unwrap(),
16960            )
16961            .await
16962            .unwrap();
16963        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
16964        let message = frames
16965            .into_iter()
16966            .find_map(|frame| match ServerMessage::decode(frame, protocol) {
16967                Ok(ServerMessage::UserToDeviceDataV1(message)) => Some(message),
16968                _ => None,
16969            })
16970            .expect("placed-calls execute envelope");
16971        let document = CiscoIpPhoneExecute::from_xml(&message.data).unwrap();
16972        assert_eq!(
16973            document,
16974            CiscoIpPhoneExecute::new(vec![
16975                CiscoIpPhoneExecuteItem::new("Application:PlacedCalls").unwrap()
16976            ])
16977            .unwrap()
16978        );
16979        assert_eq!(message.line_instance, 1);
16980        assert_eq!(message.call_reference, 0);
16981        assert!(
16982            tokio::time::timeout(Duration::from_millis(50), events.recv())
16983                .await
16984                .is_err(),
16985            "opening the native placed-calls menu must not create or route a call"
16986        );
16987
16988        handle.shutdown().await.unwrap();
16989        task.await.unwrap().unwrap();
16990    }
16991
16992    #[tokio::test]
16993    async fn new_call_key_and_stimulus_support_dial_and_backspace() {
16994        let config = ServerConfig {
16995            bind: "127.0.0.1:0".parse().unwrap(),
16996            advertised_address: Ipv4Addr::LOCALHOST,
16997            ..ServerConfig::default()
16998        };
16999        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
17000        let address = server.local_addr().unwrap();
17001        let task = tokio::spawn(server.run());
17002        let mut phone = TcpStream::connect(address).await.unwrap();
17003        let mut decoder = FrameDecoder::new();
17004        let protocol = ProtocolVersion::V22;
17005
17006        phone.write_all(&register_bytes(protocol)).await.unwrap();
17007        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17008        assert!(matches!(
17009            events.recv().await,
17010            Some(Event::Device(DeviceEvent {
17011                session_generation: _,
17012                device_id: _,
17013                event: DeviceEventKind::Registered(_)
17014            }))
17015        ));
17016
17017        phone
17018            .write_all(
17019                &ClientMessage::SoftKeyEvent {
17020                    event: SoftKey::NewCall.wire_value(),
17021                    line_instance: 1,
17022                    call_reference: 0,
17023                }
17024                .encode(protocol)
17025                .unwrap(),
17026            )
17027            .await
17028            .unwrap();
17029        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17030        let call_reference = frames
17031            .into_iter()
17032            .find_map(|frame| match ServerMessage::decode(frame, protocol) {
17033                Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
17034                _ => None,
17035            })
17036            .unwrap();
17037        let new_call_id = match events.recv().await {
17038            Some(Event::Device(DeviceEvent {
17039                session_generation: _,
17040                device_id: _,
17041                event: DeviceEventKind::OffHook { call_id, .. },
17042            })) => call_id,
17043            event => panic!("unexpected new-call event: {event:?}"),
17044        };
17045        assert!(matches!(
17046            events.recv().await,
17047            Some(Event::Device(DeviceEvent {
17048                session_generation: _,
17049                device_id: _,
17050                event: DeviceEventKind::SoftKey {
17051                    call_id: Some(_),
17052                    soft_key: SoftKey::NewCall,
17053                    ..
17054                }
17055            }))
17056        ));
17057
17058        for (index, digit) in [Digit::Number(1), Digit::Number(2)].into_iter().enumerate() {
17059            phone
17060                .write_all(
17061                    &ClientMessage::KeypadButton {
17062                        button: digit,
17063                        line_instance: 1,
17064                        call_reference,
17065                        wire_layout: None,
17066                    }
17067                    .encode(protocol)
17068                    .unwrap(),
17069                )
17070                .await
17071                .unwrap();
17072            if index == 0 {
17073                let frames =
17074                    read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17075                assert!(frames.iter().any(|frame| frame.message_id == id::STOP_TONE));
17076                assert!(
17077                    frames
17078                        .iter()
17079                        .all(|frame| frame.message_id != id::DIALED_NUMBER)
17080                );
17081            }
17082            assert!(matches!(
17083                events.recv().await,
17084                Some(Event::Device(DeviceEvent {
17085                    session_generation: _,
17086                    device_id: _,
17087                    event: DeviceEventKind::Digit { .. }
17088                }))
17089            ));
17090            if index == 1 {
17091                assert!(
17092                    tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
17093                        .await
17094                        .is_err(),
17095                    "a repeated digit emitted redundant station UI"
17096                );
17097            }
17098        }
17099
17100        phone
17101            .write_all(
17102                &ClientMessage::SoftKeyEvent {
17103                    event: SoftKey::Backspace.wire_value(),
17104                    line_instance: 1,
17105                    call_reference,
17106                }
17107                .encode(protocol)
17108                .unwrap(),
17109            )
17110            .await
17111            .unwrap();
17112        let frames = read_until_message(&mut phone, &mut decoder, id::BACKSPACE_RESPONSE).await;
17113        assert!(
17114            frames
17115                .iter()
17116                .all(|frame| frame.message_id != id::DIALED_NUMBER)
17117        );
17118        assert!(matches!(
17119            events.recv().await,
17120            Some(Event::Device(DeviceEvent {
17121                session_generation: _,
17122                device_id: _,
17123                event: DeviceEventKind::SoftKey {
17124                    soft_key: SoftKey::Backspace,
17125                    ..
17126                }
17127            }))
17128        ));
17129
17130        phone
17131            .write_all(
17132                &ClientMessage::SoftKeyEvent {
17133                    event: SoftKey::Dial.wire_value(),
17134                    line_instance: 1,
17135                    call_reference,
17136                }
17137                .encode(protocol)
17138                .unwrap(),
17139            )
17140            .await
17141            .unwrap();
17142        assert!(matches!(
17143            events.recv().await,
17144            Some(Event::Device(DeviceEvent {
17145                session_generation: _,
17146                device_id: _,
17147                event: DeviceEventKind::SoftKey {
17148                    soft_key: SoftKey::Dial,
17149                    ..
17150                }
17151            }))
17152        ));
17153
17154        handle
17155            .send(Command::new(
17156                DeviceId::new("SEP001122334455").unwrap(),
17157                CommandAction::CommitOutboundCall {
17158                    call_id: new_call_id,
17159                    info: CallInfo {
17160                        direction: crate::CallDirection::Outbound,
17161                        called_number: "1".into(),
17162                        ..CallInfo::default()
17163                    },
17164                },
17165            ))
17166            .await
17167            .unwrap();
17168        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
17169        let stop_tone = frames
17170            .iter()
17171            .position(|frame| frame.message_id == id::STOP_TONE)
17172            .expect("dial commit did not stop tone");
17173        let dialed_number = frames
17174            .iter()
17175            .position(|frame| {
17176                matches!(
17177                    ServerMessage::decode(frame.clone(), protocol),
17178                    Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "1"
17179                )
17180            })
17181            .expect("dial commit did not publish the complete number");
17182        let proceed = frames
17183            .iter()
17184            .position(|frame| {
17185                matches!(
17186                    ServerMessage::decode(frame.clone(), protocol),
17187                    Ok(ServerMessage::CallState {
17188                        state: CallState::Proceed,
17189                        ..
17190                    })
17191                )
17192            })
17193            .expect("dial commit did not publish Proceed");
17194        assert!(stop_tone < dialed_number && dialed_number < proceed);
17195        assert_eq!(
17196            frames
17197                .iter()
17198                .filter(|frame| frame.message_id == id::DIALED_NUMBER)
17199                .count(),
17200            1
17201        );
17202
17203        phone
17204            .write_all(
17205                &ClientMessage::OnHook {
17206                    line_instance: 1,
17207                    call_reference,
17208                }
17209                .encode(protocol)
17210                .unwrap(),
17211            )
17212            .await
17213            .unwrap();
17214        read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
17215        assert!(matches!(
17216            events.recv().await,
17217            Some(Event::Device(DeviceEvent {
17218                session_generation: _,
17219                device_id: _,
17220                event: DeviceEventKind::OnHook { .. }
17221            }))
17222        ));
17223
17224        phone
17225            .write_all(
17226                &ClientMessage::Stimulus {
17227                    stimulus: Stimulus::NewCall,
17228                    instance: 1,
17229                    call_reference: 0,
17230                    status: 0,
17231                }
17232                .encode(protocol)
17233                .unwrap(),
17234            )
17235            .await
17236            .unwrap();
17237        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17238        assert!(matches!(
17239            events.recv().await,
17240            Some(Event::Device(DeviceEvent {
17241                session_generation: _,
17242                device_id: _,
17243                event: DeviceEventKind::OffHook { .. }
17244            }))
17245        ));
17246        assert!(matches!(
17247            events.recv().await,
17248            Some(Event::Device(DeviceEvent {
17249                session_generation: _,
17250                device_id: _,
17251                event: DeviceEventKind::SoftKey {
17252                    call_id: Some(_),
17253                    soft_key: SoftKey::NewCall,
17254                    ..
17255                }
17256            }))
17257        ));
17258
17259        handle.shutdown().await.unwrap();
17260        task.await.unwrap().unwrap();
17261    }
17262
17263    #[tokio::test]
17264    async fn pickup_key_and_stimulus_create_an_addressable_call_before_dispatch() {
17265        let config = ServerConfig {
17266            bind: "127.0.0.1:0".parse().unwrap(),
17267            advertised_address: Ipv4Addr::LOCALHOST,
17268            ..ServerConfig::default()
17269        };
17270        let mut device = definition();
17271        device.soft_keys =
17272            profile_with(KeyMode::OnHook, vec![SoftKey::Pickup, SoftKey::GroupPickup]);
17273        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17274        let address = server.local_addr().unwrap();
17275        let task = tokio::spawn(server.run());
17276        let mut phone = TcpStream::connect(address).await.unwrap();
17277        let mut decoder = FrameDecoder::new();
17278        let protocol = ProtocolVersion::V22;
17279
17280        phone.write_all(&register_bytes(protocol)).await.unwrap();
17281        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17282        assert!(matches!(
17283            events.recv().await,
17284            Some(Event::Device(DeviceEvent {
17285                session_generation: _,
17286                device_id: _,
17287                event: DeviceEventKind::Registered(_)
17288            }))
17289        ));
17290
17291        phone
17292            .write_all(
17293                &ClientMessage::SoftKeyEvent {
17294                    event: SoftKey::Pickup.wire_value(),
17295                    line_instance: 1,
17296                    call_reference: 0,
17297                }
17298                .encode(protocol)
17299                .unwrap(),
17300            )
17301            .await
17302            .unwrap();
17303        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17304        let call_reference = frames
17305            .into_iter()
17306            .find_map(|frame| match ServerMessage::decode(frame, protocol) {
17307                Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
17308                _ => None,
17309            })
17310            .unwrap();
17311        let call_id = match events.recv().await {
17312            Some(Event::Device(DeviceEvent {
17313                session_generation: _,
17314                device_id: _,
17315                event: DeviceEventKind::OffHook { call_id, .. },
17316            })) => call_id,
17317            event => panic!("expected pickup OffHook event, got {event:?}"),
17318        };
17319        assert!(matches!(
17320            events.recv().await,
17321            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17322                call_id: Some(event_call_id),
17323                soft_key: SoftKey::Pickup,
17324                ..
17325            } })) if event_call_id == call_id
17326        ));
17327
17328        phone
17329            .write_all(
17330                &ClientMessage::OnHook {
17331                    line_instance: 1,
17332                    call_reference,
17333                }
17334                .encode(protocol)
17335                .unwrap(),
17336            )
17337            .await
17338            .unwrap();
17339        read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
17340        assert!(matches!(
17341            events.recv().await,
17342            Some(Event::Device(DeviceEvent {
17343                session_generation: _,
17344                device_id: _,
17345                event: DeviceEventKind::OnHook { .. }
17346            }))
17347        ));
17348
17349        phone
17350            .write_all(
17351                &ClientMessage::Stimulus {
17352                    stimulus: Stimulus::GroupCallPickup,
17353                    instance: 1,
17354                    call_reference: 0,
17355                    status: 0,
17356                }
17357                .encode(protocol)
17358                .unwrap(),
17359            )
17360            .await
17361            .unwrap();
17362        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17363        let call_id = match events.recv().await {
17364            Some(Event::Device(DeviceEvent {
17365                session_generation: _,
17366                device_id: _,
17367                event: DeviceEventKind::OffHook { call_id, .. },
17368            })) => call_id,
17369            event => panic!("expected group-pickup OffHook event, got {event:?}"),
17370        };
17371        assert!(matches!(
17372            events.recv().await,
17373            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17374                call_id: Some(event_call_id),
17375                soft_key: SoftKey::GroupPickup,
17376                ..
17377            } })) if event_call_id == call_id
17378        ));
17379
17380        handle.shutdown().await.unwrap();
17381        task.await.unwrap().unwrap();
17382    }
17383
17384    #[tokio::test]
17385    async fn configured_voicemail_button_creates_an_exact_line_call_before_routing() {
17386        let config = ServerConfig {
17387            bind: "127.0.0.1:0".parse().unwrap(),
17388            advertised_address: Ipv4Addr::LOCALHOST,
17389            ..ServerConfig::default()
17390        };
17391        let mut device = definition();
17392        device
17393            .buttons
17394            .push(ButtonDefinition::Feature(FeatureDefinition {
17395                instance: 1,
17396                label: "Messages".into(),
17397                feature: ButtonType::Voicemail,
17398            }));
17399        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17400        let address = server.local_addr().unwrap();
17401        let task = tokio::spawn(server.run());
17402        let mut phone = TcpStream::connect(address).await.unwrap();
17403        let mut decoder = FrameDecoder::new();
17404        let protocol = ProtocolVersion::V22;
17405
17406        phone.write_all(&register_bytes(protocol)).await.unwrap();
17407        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17408        assert!(matches!(
17409            events.recv().await,
17410            Some(Event::Device(DeviceEvent {
17411                session_generation: _,
17412                device_id: _,
17413                event: DeviceEventKind::Registered(_)
17414            }))
17415        ));
17416        phone
17417            .write_all(
17418                &ClientMessage::Stimulus {
17419                    stimulus: Stimulus::Voicemail,
17420                    instance: 1,
17421                    call_reference: 0,
17422                    status: 0,
17423                }
17424                .encode(protocol)
17425                .unwrap(),
17426            )
17427            .await
17428            .unwrap();
17429        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17430        let call_id = match events.recv().await {
17431            Some(Event::Device(DeviceEvent {
17432                session_generation: _,
17433                device_id: _,
17434                event:
17435                    DeviceEventKind::OffHook {
17436                        call_id,
17437                        line_instance: LineInstance(1),
17438                        ..
17439                    },
17440            })) => call_id,
17441            event => panic!("expected voicemail OffHook event, got {event:?}"),
17442        };
17443        assert!(matches!(
17444            events.recv().await,
17445            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::VoicemailButton {
17446                call_id: routed_call,
17447                line_instance: LineInstance(1),
17448                ..
17449            } })) if routed_call == call_id
17450        ));
17451
17452        handle.shutdown().await.unwrap();
17453        task.await.unwrap().unwrap();
17454    }
17455
17456    #[tokio::test]
17457    async fn meetme_key_and_stimulus_reserve_a_distinct_addressable_call() {
17458        let config = ServerConfig {
17459            bind: "127.0.0.1:0".parse().unwrap(),
17460            advertised_address: Ipv4Addr::LOCALHOST,
17461            ..ServerConfig::default()
17462        };
17463        let mut device = definition();
17464        device.soft_keys = SoftKeyProfile::new(
17465            KeyMode::ALL_KNOWN
17466                .iter()
17467                .copied()
17468                .map(|mode| (mode, vec![SoftKey::MeetMe])),
17469        )
17470        .unwrap();
17471        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17472        let address = server.local_addr().unwrap();
17473        let task = tokio::spawn(server.run());
17474        let mut phone = TcpStream::connect(address).await.unwrap();
17475        let mut decoder = FrameDecoder::new();
17476        let protocol = ProtocolVersion::V22;
17477
17478        phone.write_all(&register_bytes(protocol)).await.unwrap();
17479        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17480        assert!(matches!(
17481            events.recv().await,
17482            Some(Event::Device(DeviceEvent {
17483                session_generation: _,
17484                device_id: _,
17485                event: DeviceEventKind::Registered(_)
17486            }))
17487        ));
17488
17489        phone
17490            .write_all(
17491                &ClientMessage::SoftKeyEvent {
17492                    event: SoftKey::MeetMe.wire_value(),
17493                    line_instance: 1,
17494                    call_reference: 0,
17495                }
17496                .encode(protocol)
17497                .unwrap(),
17498            )
17499            .await
17500            .unwrap();
17501        let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17502        let first_reference = frames
17503            .into_iter()
17504            .find_map(|frame| match ServerMessage::decode(frame, protocol) {
17505                Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
17506                _ => None,
17507            })
17508            .unwrap();
17509        let first_call = match events.recv().await {
17510            Some(Event::Device(DeviceEvent {
17511                session_generation: _,
17512                device_id: _,
17513                event: DeviceEventKind::OffHook { call_id, .. },
17514            })) => call_id,
17515            event => panic!("expected conference-destination OffHook event, got {event:?}"),
17516        };
17517        assert!(matches!(
17518            events.recv().await,
17519            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17520                call_id: Some(call_id),
17521                soft_key: SoftKey::MeetMe,
17522                ..
17523            } })) if call_id == first_call
17524        ));
17525
17526        handle
17527            .send(Command::new(
17528                DeviceId::new("SEP001122334455").unwrap(),
17529                CommandAction::SetCallState {
17530                    call_id: first_call,
17531                    state: CallState::Connected,
17532                },
17533            ))
17534            .await
17535            .unwrap();
17536        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17537        phone
17538            .write_all(
17539                &ClientMessage::Stimulus {
17540                    stimulus: Stimulus::MeetMeConference,
17541                    instance: 1,
17542                    call_reference: first_reference,
17543                    status: 0,
17544                }
17545                .encode(protocol)
17546                .unwrap(),
17547            )
17548            .await
17549            .unwrap();
17550        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17551        let second_call = match events.recv().await {
17552            Some(Event::Device(DeviceEvent {
17553                session_generation: _,
17554                device_id: _,
17555                event: DeviceEventKind::OffHook { call_id, .. },
17556            })) => call_id,
17557            event => panic!("expected a new conference-destination OffHook event, got {event:?}"),
17558        };
17559        assert_ne!(second_call, first_call);
17560        assert!(matches!(
17561            events.recv().await,
17562            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17563                call_id: Some(call_id),
17564                soft_key: SoftKey::MeetMe,
17565                ..
17566            } })) if call_id == second_call
17567        ));
17568
17569        handle.shutdown().await.unwrap();
17570        task.await.unwrap().unwrap();
17571    }
17572
17573    #[tokio::test]
17574    async fn registered_handset_routes_every_configured_conference_control_with_exact_call() {
17575        let config = ServerConfig {
17576            bind: "127.0.0.1:0".parse().unwrap(),
17577            advertised_address: Ipv4Addr::LOCALHOST,
17578            ..ServerConfig::default()
17579        };
17580        let conference_keys = vec![
17581            SoftKey::Conference,
17582            SoftKey::Join,
17583            SoftKey::ConferenceList,
17584            SoftKey::Select,
17585            SoftKey::Hold,
17586            SoftKey::Resume,
17587            SoftKey::EndCall,
17588        ];
17589        let mut device = definition();
17590        device.soft_keys = SoftKeyProfile::new(
17591            KeyMode::ALL_KNOWN
17592                .iter()
17593                .copied()
17594                .map(|mode| (mode, conference_keys.clone())),
17595        )
17596        .unwrap();
17597        let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17598        let address = server.local_addr().unwrap();
17599        let task = tokio::spawn(server.run());
17600        let mut phone = TcpStream::connect(address).await.unwrap();
17601        let mut decoder = FrameDecoder::new();
17602        let protocol = ProtocolVersion::V22;
17603        let device_id = DeviceId::new("SEP001122334455").unwrap();
17604        let call_id = CallId(7001);
17605
17606        phone.write_all(&register_bytes(protocol)).await.unwrap();
17607        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17608        assert!(matches!(
17609            events.recv().await,
17610            Some(Event::Device(DeviceEvent {
17611                session_generation: _,
17612                device_id: _,
17613                event: DeviceEventKind::Registered(_)
17614            }))
17615        ));
17616        handle
17617            .send(Command::new(
17618                device_id.clone(),
17619                CommandAction::BeginCall {
17620                    line_instance: LineInstance(1),
17621                    call_id,
17622                    codec: Codec::Pcma,
17623                },
17624            ))
17625            .await
17626            .unwrap();
17627        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17628        handle
17629            .send(Command::new(
17630                device_id.clone(),
17631                CommandAction::SetCallState {
17632                    call_id,
17633                    state: CallState::Connected,
17634                },
17635            ))
17636            .await
17637            .unwrap();
17638        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17639
17640        for soft_key in conference_keys {
17641            phone
17642                .write_all(
17643                    &ClientMessage::SoftKeyEvent {
17644                        event: soft_key.wire_value(),
17645                        line_instance: 1,
17646                        call_reference: 7001,
17647                    }
17648                    .encode(protocol)
17649                    .unwrap(),
17650                )
17651                .await
17652                .unwrap();
17653            assert!(matches!(
17654                events.recv().await,
17655                Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::SoftKey {
17656                    line_instance: LineInstance(1),
17657                    call_id: Some(actual_call),
17658                    soft_key: actual_key,
17659                } })) if actual_device == device_id
17660                    && actual_call == call_id
17661                    && actual_key == soft_key
17662            ));
17663        }
17664
17665        for (stimulus, soft_key) in [
17666            (Stimulus::Conference, SoftKey::Conference),
17667            (Stimulus::ConferenceList, SoftKey::ConferenceList),
17668        ] {
17669            phone
17670                .write_all(
17671                    &ClientMessage::Stimulus {
17672                        stimulus,
17673                        instance: 1,
17674                        call_reference: 7001,
17675                        status: 0,
17676                    }
17677                    .encode(protocol)
17678                    .unwrap(),
17679                )
17680                .await
17681                .unwrap();
17682            assert!(matches!(
17683                events.recv().await,
17684                Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::SoftKey {
17685                    line_instance: LineInstance(1),
17686                    call_id: Some(actual_call),
17687                    soft_key: actual_key,
17688                } })) if actual_device == device_id
17689                    && actual_call == call_id
17690                    && actual_key == soft_key
17691            ));
17692        }
17693
17694        handle.shutdown().await.unwrap();
17695        task.await.unwrap().unwrap();
17696    }
17697
17698    #[tokio::test]
17699    async fn legacy_phone_receives_static_button_status_layouts() {
17700        let config = ServerConfig {
17701            bind: "127.0.0.1:0".parse().unwrap(),
17702            advertised_address: Ipv4Addr::LOCALHOST,
17703            ..ServerConfig::default()
17704        };
17705        let (server, handle, _events) = Server::bind(config, [mixed_definition()]).await.unwrap();
17706        let address = server.local_addr().unwrap();
17707        let task = tokio::spawn(server.run());
17708        let mut phone = TcpStream::connect(address).await.unwrap();
17709        let mut decoder = FrameDecoder::new();
17710
17711        phone
17712            .write_all(&register_bytes(ProtocolVersion::V3))
17713            .await
17714            .unwrap();
17715        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17716        let requests = [
17717            ClientMessage::SpeedDialStatusRequest {
17718                speed_dial_instance: 1,
17719            }
17720            .encode(ProtocolVersion::V3)
17721            .unwrap(),
17722            ClientMessage::FeatureStatusRequest {
17723                index: 1,
17724                capabilities: 0,
17725            }
17726            .encode(ProtocolVersion::V3)
17727            .unwrap(),
17728            ClientMessage::ServiceUrlStatusRequest { index: 1 }
17729                .encode(ProtocolVersion::V3)
17730                .unwrap(),
17731        ]
17732        .concat();
17733        phone.write_all(&requests).await.unwrap();
17734        let frames = read_until_message(&mut phone, &mut decoder, id::SERVICE_URL_STAT).await;
17735
17736        for (message_id, expected) in [
17737            (
17738                id::SPEED_DIAL_STAT,
17739                ServerMessage::SpeedDialStatus {
17740                    instance: 1,
17741                    number: "2001".into(),
17742                    display_name: "Reception".into(),
17743                },
17744            ),
17745            (
17746                id::FEATURE_STAT,
17747                ServerMessage::FeatureStatus {
17748                    instance: 1,
17749                    button_type: ButtonType::DoNotDisturb,
17750                    label: "DND".into(),
17751                    state: 0,
17752                },
17753            ),
17754            (
17755                id::SERVICE_URL_STAT,
17756                ServerMessage::ServiceUrlStatus {
17757                    index: 1,
17758                    url: "http://services.invalid/directory".into(),
17759                    label: "Directory".into(),
17760                    extension_text: String::new(),
17761                },
17762            ),
17763        ] {
17764            let frame = frames
17765                .iter()
17766                .find(|frame| frame.message_id == message_id)
17767                .cloned()
17768                .unwrap();
17769            assert_eq!(
17770                ServerMessage::decode(frame, ProtocolVersion::V3).unwrap(),
17771                expected
17772            );
17773        }
17774
17775        handle.shutdown().await.unwrap();
17776        task.await.unwrap().unwrap();
17777    }
17778
17779    fn register_bytes(protocol: ProtocolVersion) -> Vec<u8> {
17780        register_bytes_for_device_type(protocol, 115)
17781    }
17782
17783    fn register_bytes_with_features(protocol: ProtocolVersion, features: PhoneFeatures) -> Vec<u8> {
17784        register_bytes_for_device_with_features(protocol, 115, "SEP001122334455", features)
17785    }
17786
17787    fn register_bytes_for_device_type(protocol: ProtocolVersion, device_type: u32) -> Vec<u8> {
17788        register_bytes_for_device(protocol, device_type, "SEP001122334455")
17789    }
17790
17791    fn register_bytes_for_device(
17792        protocol: ProtocolVersion,
17793        device_type: u32,
17794        device_id: &str,
17795    ) -> Vec<u8> {
17796        register_bytes_for_device_with_features(
17797            protocol,
17798            device_type,
17799            device_id,
17800            PhoneFeatures::empty(),
17801        )
17802    }
17803
17804    fn register_bytes_for_device_with_features(
17805        protocol: ProtocolVersion,
17806        device_type: u32,
17807        device_id: &str,
17808        features: PhoneFeatures,
17809    ) -> Vec<u8> {
17810        let mut payload = vec![0_u8; 124];
17811        let device_id = device_id.as_bytes();
17812        assert!(device_id.len() <= 16);
17813        payload[..device_id.len()].copy_from_slice(device_id);
17814        payload[24..28].copy_from_slice(&[127, 0, 0, 1]);
17815        payload[28..32].copy_from_slice(&device_type.to_le_bytes());
17816        payload[40..44].copy_from_slice(&(protocol.wire() | features.bits()).to_le_bytes());
17817        payload[92..101].copy_from_slice(b"SCCP42.9-");
17818        Frame::new(0, id::REGISTER, payload).encode().unwrap()
17819    }
17820
17821    fn capability_update_bytes(
17822        protocol: ProtocolVersion,
17823        audio_codec: Codec,
17824        video_codec: Codec,
17825        marker: u32,
17826    ) -> Vec<u8> {
17827        const AUDIO_OFFSET: usize = 312;
17828        const VIDEO_OFFSET: usize = 600;
17829
17830        fn put(payload: &mut [u8], offset: usize, value: u32) {
17831            payload[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
17832        }
17833
17834        let mut payload = vec![0; 2_380];
17835        put(&mut payload, 0, 1);
17836        put(&mut payload, 4, 1);
17837        put(&mut payload, AUDIO_OFFSET, audio_codec.wire_value());
17838        put(&mut payload, AUDIO_OFFSET + 4, marker);
17839        payload[AUDIO_OFFSET + 8..AUDIO_OFFSET + 16]
17840            .copy_from_slice(&marker.to_le_bytes().repeat(2));
17841
17842        put(&mut payload, VIDEO_OFFSET, video_codec.wire_value());
17843        put(
17844            &mut payload,
17845            VIDEO_OFFSET + 4,
17846            (ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT).bits(),
17847        );
17848        put(&mut payload, VIDEO_OFFSET + 8, 1);
17849        for (index, value) in [marker, 5, 4_000, 128, 2, 7].into_iter().enumerate() {
17850            put(&mut payload, VIDEO_OFFSET + 12 + index * 4, value);
17851        }
17852        put(
17853            &mut payload,
17854            VIDEO_OFFSET + 108,
17855            u32::from(EncryptionCapability::Capable),
17856        );
17857        for (index, value) in [66, 31, 120, 240, 360, marker].into_iter().enumerate() {
17858            put(&mut payload, VIDEO_OFFSET + 112 + index * 4, value);
17859        }
17860        put(
17861            &mut payload,
17862            VIDEO_OFFSET + 136,
17863            u32::from(IpAddressType::Ipv4AndIpv6),
17864        );
17865        Frame::new(protocol.wire(), id::UPDATE_CAPABILITIES_V3, payload)
17866            .encode()
17867            .unwrap()
17868    }
17869
17870    #[tokio::test]
17871    async fn capability_snapshots_replace_atomically_and_remain_session_scoped() {
17872        let config = ServerConfig {
17873            bind: "127.0.0.1:0".parse().unwrap(),
17874            advertised_address: Ipv4Addr::LOCALHOST,
17875            ..ServerConfig::default()
17876        };
17877        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
17878        let address = server.local_addr().unwrap();
17879        let task = tokio::spawn(server.run());
17880        let protocol = ProtocolVersion::V22;
17881
17882        let mut first_phone = TcpStream::connect(address).await.unwrap();
17883        let mut first_decoder = FrameDecoder::new();
17884        first_phone
17885            .write_all(&register_bytes(protocol))
17886            .await
17887            .unwrap();
17888        read_until_message(&mut first_phone, &mut first_decoder, id::CAPABILITIES_REQ).await;
17889        let first_generation = match events.recv().await {
17890            Some(Event::Device(DeviceEvent {
17891                session_generation,
17892                event: DeviceEventKind::Registered(_),
17893                ..
17894            })) => session_generation,
17895            event => panic!("expected first registration, got {event:?}"),
17896        };
17897
17898        first_phone
17899            .write_all(&capability_update_bytes(
17900                protocol,
17901                Codec::Pcmu,
17902                Codec::H264,
17903                11,
17904            ))
17905            .await
17906            .unwrap();
17907        let first_capabilities = match events.recv().await {
17908            Some(Event::Device(DeviceEvent {
17909                session_generation,
17910                event: DeviceEventKind::Capabilities { capabilities },
17911                ..
17912            })) => {
17913                assert_eq!(session_generation, first_generation);
17914                capabilities
17915            }
17916            event => panic!("expected first capability update, got {event:?}"),
17917        };
17918        assert_eq!(first_capabilities.audio()[0].codec, Codec::Pcmu);
17919        assert_eq!(first_capabilities.video()[0].codec, Codec::H264);
17920        assert_eq!(
17921            first_capabilities.video()[0].direction,
17922            ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT
17923        );
17924        assert_eq!(
17925            first_capabilities.video()[0].encryption_capability,
17926            Some(EncryptionCapability::Capable)
17927        );
17928        assert_eq!(
17929            first_capabilities.video()[0].address_type,
17930            Some(IpAddressType::Ipv4AndIpv6)
17931        );
17932        assert_eq!(first_capabilities.video()[0].codec_parameters[5], 11);
17933
17934        first_phone
17935            .write_all(&capability_update_bytes(
17936                protocol,
17937                Codec::G72264k,
17938                Codec::H263,
17939                22,
17940            ))
17941            .await
17942            .unwrap();
17943        let replacement_capabilities = match events.recv().await {
17944            Some(Event::Device(DeviceEvent {
17945                session_generation,
17946                event: DeviceEventKind::Capabilities { capabilities },
17947                ..
17948            })) => {
17949                assert_eq!(session_generation, first_generation);
17950                capabilities
17951            }
17952            event => panic!("expected replacement capability update, got {event:?}"),
17953        };
17954        assert_eq!(replacement_capabilities.audio().len(), 1);
17955        assert_eq!(replacement_capabilities.audio()[0].codec, Codec::G72264k);
17956        assert_eq!(replacement_capabilities.video().len(), 1);
17957        assert_eq!(replacement_capabilities.video()[0].codec, Codec::H263);
17958        assert_eq!(replacement_capabilities.video()[0].codec_parameters[5], 22);
17959        assert_eq!(first_capabilities.video()[0].codec, Codec::H264);
17960
17961        let mut second_phone = TcpStream::connect(address).await.unwrap();
17962        let mut second_decoder = FrameDecoder::new();
17963        second_phone
17964            .write_all(&register_bytes(protocol))
17965            .await
17966            .unwrap();
17967        read_until_message(&mut second_phone, &mut second_decoder, id::CAPABILITIES_REQ).await;
17968        let second_generation = match events.recv().await {
17969            Some(Event::Device(DeviceEvent {
17970                session_generation,
17971                event: DeviceEventKind::Registered(_),
17972                ..
17973            })) => session_generation,
17974            event => panic!("expected replacement registration, got {event:?}"),
17975        };
17976        assert!(second_generation > first_generation);
17977        assert!(
17978            tokio::time::timeout(Duration::from_millis(25), events.recv())
17979                .await
17980                .is_err(),
17981            "replaced session emitted a late disconnect"
17982        );
17983
17984        second_phone
17985            .write_all(
17986                &ClientMessage::CapabilitiesResponse(vec![MediaCapability {
17987                    codec: Codec::Pcma,
17988                    max_frames_per_packet: 2,
17989                    codec_parameters: [0; 8],
17990                }])
17991                .encode(protocol)
17992                .unwrap(),
17993            )
17994            .await
17995            .unwrap();
17996        match events.recv().await {
17997            Some(Event::Device(DeviceEvent {
17998                session_generation,
17999                event: DeviceEventKind::Capabilities { capabilities },
18000                ..
18001            })) => {
18002                assert_eq!(session_generation, second_generation);
18003                assert_eq!(capabilities.audio()[0].codec, Codec::Pcma);
18004                assert!(capabilities.video().is_empty());
18005            }
18006            event => panic!("expected reconnect capability response, got {event:?}"),
18007        }
18008        assert_ne!(first_generation, second_generation);
18009
18010        handle.shutdown().await.unwrap();
18011        task.await.unwrap().unwrap();
18012    }
18013
18014    async fn read_until_message(
18015        phone: &mut dyn StationIo,
18016        decoder: &mut FrameDecoder,
18017        message_id: u32,
18018    ) -> Vec<Frame> {
18019        let mut frames = Vec::new();
18020        let mut buffer = [0_u8; 2048];
18021        while !frames
18022            .iter()
18023            .any(|frame: &Frame| frame.message_id == message_id)
18024        {
18025            let count = tokio::time::timeout(Duration::from_secs(1), phone.read(&mut buffer))
18026                .await
18027                .expect("timed out waiting for SCCP response")
18028                .expect("could not read SCCP response");
18029            assert_ne!(count, 0, "SCCP session closed while waiting for response");
18030            frames.extend(decoder.push(&buffer[..count]).unwrap());
18031        }
18032        frames
18033    }
18034
18035    async fn read_until_server_message(
18036        phone: &mut dyn StationIo,
18037        decoder: &mut FrameDecoder,
18038        protocol: ProtocolVersion,
18039        predicate: impl Fn(&ServerMessage) -> bool,
18040    ) -> Vec<ServerMessage> {
18041        let mut messages = Vec::new();
18042        let mut buffer = [0_u8; 2048];
18043        while !messages.iter().any(&predicate) {
18044            let count = tokio::time::timeout(Duration::from_secs(1), phone.read(&mut buffer))
18045                .await
18046                .expect("timed out waiting for SCCP response")
18047                .expect("could not read SCCP response");
18048            assert_ne!(count, 0, "SCCP session closed while waiting for response");
18049            messages.extend(
18050                decoder
18051                    .push(&buffer[..count])
18052                    .unwrap()
18053                    .into_iter()
18054                    .map(|frame| ServerMessage::decode(frame, protocol).unwrap()),
18055            );
18056        }
18057        messages
18058    }
18059
18060    fn open_receive_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
18061        frames
18062            .iter()
18063            .find_map(
18064                |frame| match ServerMessage::decode(frame.clone(), protocol).ok()? {
18065                    ServerMessage::OpenReceiveChannel {
18066                        passthrough_party_id,
18067                        ..
18068                    } => Some(passthrough_party_id),
18069                    _ => None,
18070                },
18071            )
18072            .expect("transaction omitted OpenReceiveChannel")
18073    }
18074
18075    fn start_media_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
18076        frames
18077            .iter()
18078            .find_map(
18079                |frame| match ServerMessage::decode(frame.clone(), protocol).ok()? {
18080                    ServerMessage::StartMediaTransmission {
18081                        passthrough_party_id,
18082                        ..
18083                    } => Some(passthrough_party_id),
18084                    _ => None,
18085                },
18086            )
18087            .expect("transaction omitted StartMediaTransmission")
18088    }
18089
18090    fn coupled_media_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
18091        let receive = open_receive_request_party(frames, protocol);
18092        let transmit = start_media_request_party(frames, protocol);
18093        assert_ne!(receive, 0);
18094        assert_eq!(receive, transmit, "coupled request identities diverged");
18095        receive
18096    }
18097
18098    fn test_connection_statistics(
18099        directory_number: &str,
18100        call_reference: u32,
18101    ) -> ConnectionStatistics {
18102        ConnectionStatistics {
18103            directory_number: directory_number.into(),
18104            call_reference,
18105            processing: StatisticsProcessing::Clear,
18106            packets_sent: 120,
18107            octets_sent: 9_600,
18108            packets_received: 118,
18109            octets_received: 9_440,
18110            packets_lost: 2,
18111            jitter_millis: 6,
18112            latency_millis: 17,
18113            quality: crate::ConnectionQualityStatistics::new(b"MLQK=4.4".to_vec()).unwrap(),
18114        }
18115    }
18116
18117    #[tokio::test]
18118    async fn hangup_statistics_are_exactly_correlated_retained_and_not_replayed() {
18119        let config = ServerConfig {
18120            bind: "127.0.0.1:0".parse().unwrap(),
18121            advertised_address: Ipv4Addr::LOCALHOST,
18122            ..ServerConfig::default()
18123        };
18124        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
18125        let address = server.local_addr().unwrap();
18126        let task = tokio::spawn(server.run());
18127        let mut phone = TcpStream::connect(address).await.unwrap();
18128        let mut decoder = FrameDecoder::new();
18129        let protocol = ProtocolVersion::V22;
18130        let device_id = DeviceId::new("SEP001122334455").unwrap();
18131        let call_id = CallId(7001);
18132
18133        phone.write_all(&register_bytes(protocol)).await.unwrap();
18134        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
18135        assert!(matches!(
18136            events.recv().await,
18137            Some(Event::Device(DeviceEvent {
18138                session_generation: _,
18139                device_id: _,
18140                event: DeviceEventKind::Registered(_)
18141            }))
18142        ));
18143        handle
18144            .send(Command::new(
18145                device_id.clone(),
18146                CommandAction::BeginCall {
18147                    line_instance: LineInstance(1),
18148                    call_id,
18149                    codec: Codec::Pcma,
18150                },
18151            ))
18152            .await
18153            .unwrap();
18154        read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
18155        handle
18156            .send(Command::new(
18157                device_id.clone(),
18158                CommandAction::SetCallInfo {
18159                    call_id,
18160                    info: CallInfo {
18161                        direction: crate::types::CallDirection::Outbound,
18162                        called_number: "2002".into(),
18163                        ..CallInfo::default()
18164                    },
18165                },
18166            ))
18167            .await
18168            .unwrap();
18169        handle
18170            .send(Command::new(
18171                device_id.clone(),
18172                CommandAction::OpenReceiveChannel {
18173                    call_id,
18174                    source: Some(MediaEndpoint {
18175                        address: "192.0.2.1".parse().unwrap(),
18176                        rtp_port: 5000,
18177                        rtcp_port: 5001,
18178                        codec: Codec::Pcma,
18179                        packet_ms: 30,
18180                        max_frames_per_packet: 2,
18181                        telephone_event_payload: 0,
18182                    }),
18183                    codec: Codec::Pcma,
18184                    packet_ms: 30,
18185                    max_frames_per_packet: 2,
18186                    dtmf_mode: DtmfMode::Skinny,
18187                    audio_processing: AudioProcessingPolicy::default(),
18188                },
18189            ))
18190            .await
18191            .unwrap();
18192        let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
18193        let receive_media_party = open_receive_request_party(&frames, protocol);
18194        let receive_peer = MediaEndpoint {
18195            address: "192.0.2.10".parse().unwrap(),
18196            rtp_port: 4000,
18197            rtcp_port: 4001,
18198            codec: Codec::Pcma,
18199            packet_ms: 30,
18200            max_frames_per_packet: 2,
18201            telephone_event_payload: 0,
18202        };
18203        phone
18204            .write_all(
18205                &ClientMessage::OpenReceiveChannelAck {
18206                    status: MediaStatus::Ok,
18207                    address: receive_peer.address,
18208                    port: receive_peer.rtp_port,
18209                    call_reference: 7001,
18210                    passthrough_party_id: receive_media_party,
18211                }
18212                .encode(protocol)
18213                .unwrap(),
18214            )
18215            .await
18216            .unwrap();
18217        assert!(matches!(
18218            events.recv().await,
18219            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::ReceiveChannelOpened { endpoint, .. } })) if endpoint == receive_peer
18220        ));
18221        handle
18222            .send(Command::new(
18223                device_id.clone(),
18224                CommandAction::StartMedia {
18225                    call_id,
18226                    endpoint: MediaEndpoint {
18227                        address: "198.51.100.20".parse().unwrap(),
18228                        rtp_port: 6000,
18229                        rtcp_port: 6001,
18230                        codec: Codec::Pcma,
18231                        packet_ms: 30,
18232                        max_frames_per_packet: 2,
18233                        telephone_event_payload: 0,
18234                    },
18235                    dtmf_mode: DtmfMode::Skinny,
18236                    audio_processing: AudioProcessingPolicy::default(),
18237                    traffic_class: MediaTrafficClass::default(),
18238                },
18239            ))
18240            .await
18241            .unwrap();
18242        let frames =
18243            read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
18244        let transmit_media_party = start_media_request_party(&frames, protocol);
18245        let transmit_peer = MediaEndpoint {
18246            address: "2001:db8::20".parse().unwrap(),
18247            rtp_port: 5000,
18248            rtcp_port: 5001,
18249            codec: Codec::Pcma,
18250            packet_ms: 30,
18251            max_frames_per_packet: 2,
18252            telephone_event_payload: 0,
18253        };
18254        phone
18255            .write_all(
18256                &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
18257                    conference_id: 7001,
18258                    passthrough_party_id: transmit_media_party,
18259                    call_reference: 7001,
18260                    status: MediaStatus::Ok,
18261                    address: transmit_peer.address,
18262                    port: transmit_peer.rtp_port,
18263                    wire: None,
18264                })
18265                .encode(protocol)
18266                .unwrap(),
18267            )
18268            .await
18269            .unwrap();
18270        assert!(matches!(
18271            events.recv().await,
18272            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelStarted { endpoint, .. } })) if endpoint == transmit_peer
18273        ));
18274        handle
18275            .send(Command::new(
18276                device_id.clone(),
18277                CommandAction::CloseReceiveChannel { call_id },
18278            ))
18279            .await
18280            .unwrap();
18281        handle
18282            .send(Command::new(
18283                device_id.clone(),
18284                CommandAction::StopMedia { call_id },
18285            ))
18286            .await
18287            .unwrap();
18288        handle
18289            .send(Command::new(
18290                device_id.clone(),
18291                CommandAction::CloseCall { call_id },
18292            ))
18293            .await
18294            .unwrap();
18295        let frames =
18296            read_until_message(&mut phone, &mut decoder, id::CONNECTION_STATISTICS_REQ).await;
18297        let trailing = if frames
18298            .iter()
18299            .any(|frame| frame.message_id == id::SET_RINGER)
18300        {
18301            Vec::new()
18302        } else {
18303            read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await
18304        };
18305        assert_eq!(
18306            frames
18307                .iter()
18308                .chain(&trailing)
18309                .filter(|frame| frame.message_id == id::STOP_MEDIA_TRANSMISSION)
18310                .count(),
18311            1
18312        );
18313        assert_eq!(
18314            frames
18315                .iter()
18316                .chain(&trailing)
18317                .filter(|frame| frame.message_id == id::CLOSE_RECEIVE_CHANNEL)
18318                .count(),
18319            1
18320        );
18321        let close_receive = frames
18322            .iter()
18323            .position(|frame| frame.message_id == id::CLOSE_RECEIVE_CHANNEL)
18324            .expect("hangup did not close receive media");
18325        let stop_media = frames
18326            .iter()
18327            .position(|frame| frame.message_id == id::STOP_MEDIA_TRANSMISSION)
18328            .expect("hangup did not stop transmit media");
18329        let on_hook = frames
18330            .iter()
18331            .position(|frame| {
18332                matches!(
18333                    ServerMessage::decode(frame.clone(), protocol),
18334                    Ok(ServerMessage::CallState {
18335                        state: CallState::OnHook,
18336                        ..
18337                    })
18338                )
18339            })
18340            .expect("hangup did not publish OnHook");
18341        let statistics = frames
18342            .iter()
18343            .position(|frame| frame.message_id == id::CONNECTION_STATISTICS_REQ)
18344            .expect("hangup did not request connection statistics");
18345        assert!(close_receive < stop_media && stop_media < on_hook && on_hook < statistics);
18346        assert!(
18347            frames
18348                .iter()
18349                .all(|frame| frame.message_id != id::CALL_HISTORY_DISPOSITION)
18350        );
18351        assert!(frames.iter().any(|frame| matches!(
18352            ServerMessage::decode(frame.clone(), protocol),
18353            Ok(ServerMessage::ConnectionStatisticsRequest {
18354                directory_number,
18355                call_reference: 7001,
18356                processing: StatisticsProcessing::Clear,
18357            }) if directory_number == "2002"
18358        )));
18359
18360        phone
18361            .write_all(
18362                &ClientMessage::ConnectionStatisticsResponse(test_connection_statistics(
18363                    "wrong", 7001,
18364                ))
18365                .encode(protocol)
18366                .unwrap(),
18367            )
18368            .await
18369            .unwrap();
18370        assert!(
18371            tokio::time::timeout(Duration::from_millis(25), events.recv())
18372                .await
18373                .is_err()
18374        );
18375
18376        let mut unknown_processing = test_connection_statistics("2002", 7001);
18377        unknown_processing.processing = StatisticsProcessing::Unknown(9);
18378        phone
18379            .write_all(
18380                &ClientMessage::ConnectionStatisticsResponse(unknown_processing)
18381                    .encode(protocol)
18382                    .unwrap(),
18383            )
18384            .await
18385            .unwrap();
18386        assert!(
18387            tokio::time::timeout(Duration::from_millis(25), events.recv())
18388                .await
18389                .is_err()
18390        );
18391
18392        let expected = test_connection_statistics("2002", 7001);
18393        phone
18394            .write_all(
18395                &ClientMessage::ConnectionStatisticsResponse(expected.clone())
18396                    .encode(protocol)
18397                    .unwrap(),
18398            )
18399            .await
18400            .unwrap();
18401        let Some(Event::Device(DeviceEvent {
18402            session_generation: _,
18403            device_id: actual_device,
18404            event: DeviceEventKind::ConnectionStatisticsCollected { snapshot },
18405        })) = events.recv().await
18406        else {
18407            panic!("expected a correlated statistics event");
18408        };
18409        assert_eq!(actual_device, device_id);
18410        assert_eq!(snapshot.call_id, call_id);
18411        assert_eq!(snapshot.line_instance, LineInstance::new(1));
18412        assert_eq!(snapshot.codec, Codec::Pcma);
18413        assert_eq!(snapshot.packet_ms, 30);
18414        assert_eq!(snapshot.max_frames_per_packet, 2);
18415        assert_eq!(snapshot.receive_peer, Some(receive_peer));
18416        assert_eq!(snapshot.transmit_peer, Some(transmit_peer));
18417        assert_eq!(snapshot.packets_sent, expected.packets_sent);
18418        assert_eq!(snapshot.octets_sent, expected.octets_sent);
18419        assert_eq!(snapshot.packets_received, expected.packets_received);
18420        assert_eq!(snapshot.octets_received, expected.octets_received);
18421        assert_eq!(snapshot.packets_lost, expected.packets_lost);
18422        assert_eq!(snapshot.jitter_millis, expected.jitter_millis);
18423        assert_eq!(snapshot.latency_millis, expected.latency_millis);
18424        assert_eq!(
18425            snapshot.quality_byte_count,
18426            expected.quality.as_bytes().len()
18427        );
18428        let debug = format!("{snapshot:?}");
18429        assert!(!debug.contains("2002"));
18430        assert!(!debug.contains("MLQK"));
18431        assert_eq!(
18432            handle.latest_media_statistics(&device_id),
18433            Some(snapshot.clone())
18434        );
18435        assert_eq!(
18436            handle.media_statistics(),
18437            vec![(device_id.clone(), snapshot.clone())]
18438        );
18439
18440        phone
18441            .write_all(
18442                &ClientMessage::ConnectionStatisticsResponse(expected)
18443                    .encode(protocol)
18444                    .unwrap(),
18445            )
18446            .await
18447            .unwrap();
18448        assert!(
18449            tokio::time::timeout(Duration::from_millis(25), events.recv())
18450                .await
18451                .is_err(),
18452            "a duplicate response emitted a second event"
18453        );
18454
18455        handle.shutdown().await.unwrap();
18456        task.await.unwrap().unwrap();
18457    }
18458
18459    #[test]
18460    fn expired_statistics_requests_are_pruned_at_the_deadline() {
18461        let now = Instant::now();
18462        let mut pending = HashMap::from([(
18463            42,
18464            PendingConnectionStatistics {
18465                session_generation: SessionGeneration::new(1).unwrap(),
18466                request_generation: 2,
18467                call_id: CallId(3),
18468                line_instance: 1,
18469                codec: Codec::Pcmu,
18470                packet_ms: 20,
18471                max_frames_per_packet: 1,
18472                receive_peer: None,
18473                transmit_peer: None,
18474                directory_number: "2002".into(),
18475                processing: StatisticsProcessing::Clear,
18476                expires_at: now,
18477            },
18478        )]);
18479        prune_connection_statistics(&mut pending, now);
18480        assert!(pending.is_empty());
18481    }
18482
18483    #[test]
18484    fn statistics_directory_follows_the_call_direction() {
18485        let inbound = CallInfo {
18486            direction: crate::types::CallDirection::Inbound,
18487            calling_number: "inbound-peer".into(),
18488            called_number: "local-line".into(),
18489            ..CallInfo::default()
18490        };
18491        let outbound = CallInfo {
18492            direction: crate::types::CallDirection::Outbound,
18493            calling_number: "local-line".into(),
18494            called_number: "outbound-peer".into(),
18495            ..CallInfo::default()
18496        };
18497        assert_eq!(statistics_directory_for_call_info(&inbound), "inbound-peer");
18498        assert_eq!(
18499            statistics_directory_for_call_info(&outbound),
18500            "outbound-peer"
18501        );
18502    }
18503
18504    #[test]
18505    fn replacement_calls_and_media_requests_never_reuse_identifiers() {
18506        let device = definition();
18507        let mut state = SessionState {
18508            registration: DeviceRegistration {
18509                id: device.id.clone(),
18510                peer: "127.0.0.1:2000".parse().unwrap(),
18511                transport: StationTransport::Clear,
18512                reported_address: Some(Ipv4Addr::LOCALHOST),
18513                reported_ipv6_address: None,
18514                device_type: DeviceType::Cisco7962,
18515                protocol: ProtocolVersion::V22,
18516                firmware: "test".into(),
18517            },
18518            device,
18519            features: PhoneFeatures::empty(),
18520            generation: SessionGeneration::new(1).unwrap(),
18521            calls_by_id: HashMap::new(),
18522            calls_by_wire: HashMap::new(),
18523            media_capabilities: StationMediaCapabilities::default(),
18524            next_media_token: MediaRequestToken::new(1),
18525            next_multicast_generation: 0,
18526            multicast: HashMap::new(),
18527            pending_connection_statistics: HashMap::new(),
18528            statistics_references: HashSet::from([42]),
18529            cancelled_calls: HashSet::new(),
18530            last_number_by_line: HashMap::new(),
18531            forwarding_by_line: HashMap::new(),
18532            feature_states: HashMap::new(),
18533            mwi_by_line: HashMap::new(),
18534            mobility_appearances: HashMap::new(),
18535            active_key_mode: KeyMode::OnHook,
18536            active_call_id: None,
18537            pending_parking_menu: None,
18538            persistent_status_message: false,
18539            headset_enabled: false,
18540            media_path_states: HashMap::new(),
18541            pending_media_path_release: None,
18542        };
18543        let replacement = insert_call(&mut state, CallId(42), 1, Codec::Pcmu, CallState::OffHook);
18544        assert_eq!(replacement.wire_reference, 43);
18545        assert_eq!(state.calls_by_wire.get(&42), None);
18546        assert_eq!(state.calls_by_wire.get(&43), Some(&CallId(42)));
18547
18548        state.next_media_token = MediaRequestToken::new(u32::MAX);
18549        let final_identity = allocate_media_request_identity(&mut state, CallId(42)).unwrap();
18550        assert_eq!(final_identity.token().get(), u32::MAX);
18551        assert!(state.next_media_token.is_none());
18552        let generation_after_final_token = state.calls_by_id[&CallId(42)].media.generation;
18553        assert!(matches!(
18554            allocate_media_request_identity(&mut state, CallId(42)),
18555            Err(ServerError::MediaRequestIdentityExhausted)
18556        ));
18557        assert_eq!(
18558            state.calls_by_id[&CallId(42)].media.generation,
18559            generation_after_final_token,
18560            "failed allocation mutated the call generation"
18561        );
18562
18563        state.next_media_token = MediaRequestToken::new(7);
18564        state
18565            .calls_by_id
18566            .get_mut(&CallId(42))
18567            .unwrap()
18568            .media
18569            .generation = u64::MAX;
18570        assert!(matches!(
18571            allocate_media_request_identity(&mut state, CallId(42)),
18572            Err(ServerError::MediaRequestIdentityExhausted)
18573        ));
18574        assert_eq!(state.next_media_token.unwrap().get(), 7);
18575    }
18576
18577    #[test]
18578    fn omitted_call_reference_uses_active_then_configured_answer_order() {
18579        let device = definition();
18580        let mut state = SessionState {
18581            registration: DeviceRegistration {
18582                id: device.id.clone(),
18583                peer: "127.0.0.1:2000".parse().unwrap(),
18584                transport: StationTransport::Clear,
18585                reported_address: Some(Ipv4Addr::LOCALHOST),
18586                reported_ipv6_address: None,
18587                device_type: DeviceType::Cisco7962,
18588                protocol: ProtocolVersion::V22,
18589                firmware: "test".into(),
18590            },
18591            device,
18592            features: PhoneFeatures::empty(),
18593            generation: SessionGeneration::new(1).unwrap(),
18594            calls_by_id: HashMap::new(),
18595            calls_by_wire: HashMap::new(),
18596            media_capabilities: StationMediaCapabilities::default(),
18597            next_media_token: MediaRequestToken::new(1),
18598            next_multicast_generation: 0,
18599            multicast: HashMap::new(),
18600            pending_connection_statistics: HashMap::new(),
18601            statistics_references: HashSet::new(),
18602            cancelled_calls: HashSet::new(),
18603            last_number_by_line: HashMap::new(),
18604            forwarding_by_line: HashMap::new(),
18605            feature_states: HashMap::new(),
18606            mwi_by_line: HashMap::new(),
18607            mobility_appearances: HashMap::new(),
18608            active_key_mode: KeyMode::RingIn,
18609            active_call_id: None,
18610            pending_parking_menu: None,
18611            persistent_status_message: false,
18612            headset_enabled: false,
18613            media_path_states: HashMap::new(),
18614            pending_media_path_release: None,
18615        };
18616        let first = insert_call(
18617            &mut state,
18618            CallId(10),
18619            1,
18620            Codec::Pcmu,
18621            CallState::CallWaiting,
18622        );
18623        let last = insert_call(&mut state, CallId(20), 2, Codec::Pcma, CallState::RingIn);
18624
18625        assert_eq!(
18626            find_answer_call(&state, 0, 0, CallSelectionOrder::OldestFirst)
18627                .map(|call| call.call_id),
18628            Some(CallId(10))
18629        );
18630        assert_eq!(
18631            find_answer_call(&state, 0, 0, CallSelectionOrder::LastFirst).map(|call| call.call_id),
18632            Some(CallId(20))
18633        );
18634        assert_eq!(
18635            find_answer_call(
18636                &state,
18637                first.wire_reference,
18638                1,
18639                CallSelectionOrder::LastFirst,
18640            )
18641            .map(|call| call.call_id),
18642            Some(CallId(10))
18643        );
18644        assert!(
18645            find_answer_call(
18646                &state,
18647                last.wire_reference,
18648                1,
18649                CallSelectionOrder::LastFirst,
18650            )
18651            .is_none()
18652        );
18653        assert_eq!(
18654            find_answer_call(&state, 0, 1, CallSelectionOrder::LastFirst).map(|call| call.call_id),
18655            Some(CallId(10))
18656        );
18657
18658        state.active_call_id = Some(last.call_id);
18659        assert_eq!(
18660            find_call(&state, 0).map(|call| call.call_id),
18661            Some(CallId(20))
18662        );
18663        assert_eq!(
18664            find_answer_call(&state, 0, 0, CallSelectionOrder::OldestFirst)
18665                .map(|call| call.call_id),
18666            Some(CallId(20))
18667        );
18668        remove_call(&mut state, CallId(20));
18669        assert_eq!(state.active_call_id, None);
18670        assert_eq!(
18671            find_call(&state, 0).map(|call| call.call_id),
18672            Some(CallId(10))
18673        );
18674    }
18675
18676    #[test]
18677    fn distinct_and_urgent_ring_modes_preserve_exact_waiting_semantics() {
18678        assert_eq!(
18679            incoming_ringer(Some(IncomingRing::default()), CallState::RingIn),
18680            Some(IncomingRing {
18681                mode: RingerMode::Inside,
18682                duration: RingDuration::Normal,
18683            })
18684        );
18685        assert_eq!(
18686            incoming_ringer(
18687                Some(IncomingRing {
18688                    mode: RingerMode::Bellcore4,
18689                    duration: RingDuration::Normal,
18690                }),
18691                CallState::RingIn,
18692            ),
18693            Some(IncomingRing {
18694                mode: RingerMode::Bellcore4,
18695                duration: RingDuration::Normal,
18696            })
18697        );
18698        assert_eq!(
18699            incoming_ringer(
18700                Some(IncomingRing {
18701                    mode: RingerMode::Bellcore4,
18702                    duration: RingDuration::Normal,
18703                }),
18704                CallState::CallWaiting,
18705            ),
18706            Some(IncomingRing {
18707                mode: RingerMode::Silent,
18708                duration: RingDuration::Single,
18709            })
18710        );
18711        assert_eq!(
18712            incoming_ringer(
18713                Some(IncomingRing {
18714                    mode: RingerMode::Urgent,
18715                    duration: RingDuration::Normal,
18716                }),
18717                CallState::CallWaiting,
18718            ),
18719            Some(IncomingRing {
18720                mode: RingerMode::Urgent,
18721                duration: RingDuration::Single,
18722            })
18723        );
18724        assert_eq!(incoming_ringer(None, CallState::CallWaiting), None);
18725    }
18726
18727    #[test]
18728    fn answer_order_reload_updates_the_shared_policy_without_replacing_sessions() {
18729        let (command_tx, _command_rx) = mpsc::channel(1);
18730        let order = Arc::new(RwLock::new(CallSelectionOrder::OldestFirst));
18731        let handle = ServerHandle {
18732            command_tx,
18733            next_call_id: Arc::new(AtomicU64::new(1)),
18734            latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
18735            call_answer_order: Arc::clone(&order),
18736        };
18737        handle.set_call_answer_order(CallSelectionOrder::LastFirst);
18738        assert_eq!(
18739            *order.read().expect("test answer-order lock poisoned"),
18740            CallSelectionOrder::LastFirst
18741        );
18742    }
18743
18744    #[test]
18745    fn calendar_conversion_is_stable() {
18746        assert_eq!(civil_from_days(0), (1970, 1, 1));
18747        assert_eq!(civil_from_days(19_358), (2023, 1, 1));
18748        assert_eq!(
18749            time_date_message_at(UNIX_EPOCH + Duration::from_secs(23 * 3_600 + 45 * 60), 30),
18750            ServerMessage::TimeDate {
18751                year: 1970,
18752                month: 1,
18753                weekday: 6,
18754                day: 2,
18755                hour: 0,
18756                minute: 15,
18757                second: 0,
18758                milliseconds: 0,
18759                unix_seconds: 87_300,
18760            }
18761        );
18762    }
18763
18764    #[test]
18765    fn mwi_policy_projects_configured_cadence_and_on_call_visibility() {
18766        let hidden_on_call = crate::types::StationUiPolicy {
18767            mwi_lamp_mode: LampMode::Flash,
18768            mwi_on_call: false,
18769            ..Default::default()
18770        };
18771        assert_eq!(
18772            projected_mwi_lamp(hidden_on_call, false, true),
18773            LampMode::Flash
18774        );
18775        assert_eq!(
18776            projected_mwi_lamp(hidden_on_call, true, true),
18777            LampMode::Off
18778        );
18779        assert_eq!(
18780            projected_mwi_lamp(hidden_on_call, false, false),
18781            LampMode::Off
18782        );
18783
18784        let visible_on_call = crate::types::StationUiPolicy {
18785            mwi_lamp_mode: LampMode::Blink,
18786            mwi_on_call: true,
18787            ..Default::default()
18788        };
18789        assert_eq!(
18790            projected_mwi_lamp(visible_on_call, true, true),
18791            LampMode::Blink
18792        );
18793    }
18794
18795    #[test]
18796    fn call_history_distinguishes_answered_missed_and_elsewhere_answered() {
18797        assert_eq!(
18798            updated_history_disposition(CallHistoryDisposition::Missed, CallState::Connected),
18799            CallHistoryDisposition::Received
18800        );
18801        assert_eq!(
18802            updated_history_disposition(CallHistoryDisposition::Missed, CallState::OnHook),
18803            CallHistoryDisposition::Missed
18804        );
18805        assert_eq!(
18806            updated_history_disposition(CallHistoryDisposition::Missed, CallState::RemoteMultiline,),
18807            CallHistoryDisposition::Ignore
18808        );
18809        assert_eq!(
18810            updated_history_disposition(CallHistoryDisposition::Placed, CallState::Connected),
18811            CallHistoryDisposition::Placed
18812        );
18813    }
18814
18815    #[test]
18816    fn reconfiguration_classifies_added_changed_removed_and_unchanged_devices() {
18817        let unchanged = definition_for("SEP001122334455");
18818        let mut changed = definition_for("SEP112233445566");
18819        let removed = definition_for("SEP223344556677");
18820        let added = definition_for("SEP334455667788");
18821        let current = HashMap::from([
18822            (unchanged.id.clone(), unchanged.clone()),
18823            (changed.id.clone(), changed.clone()),
18824            (removed.id.clone(), removed),
18825        ]);
18826        changed.description = "Changed station".into();
18827        let next = HashMap::from([
18828            (unchanged.id.clone(), unchanged),
18829            (changed.id.clone(), changed),
18830            (added.id.clone(), added),
18831        ]);
18832
18833        assert_eq!(
18834            reconfigure_result(&current, &next, &HashSet::new()),
18835            ReconfigureResult {
18836                added: vec![DeviceId::new("SEP334455667788").unwrap()],
18837                changed: vec![DeviceId::new("SEP112233445566").unwrap()],
18838                removed: vec![DeviceId::new("SEP223344556677").unwrap()],
18839            }
18840        );
18841        assert!(reconfigure_result(&current, &current, &HashSet::new()).is_unchanged());
18842        assert_eq!(
18843            reconfigure_result(
18844                &current,
18845                &current,
18846                &HashSet::from([DeviceId::new("SEP001122334455").unwrap()]),
18847            )
18848            .changed,
18849            vec![DeviceId::new("SEP001122334455").unwrap()]
18850        );
18851    }
18852
18853    #[tokio::test]
18854    async fn reconfiguration_preserves_unchanged_session_calls_and_rolls_back_invalid_candidates() {
18855        let original = definition();
18856        let config = ServerConfig {
18857            bind: "127.0.0.1:0".parse().unwrap(),
18858            advertised_address: Ipv4Addr::LOCALHOST,
18859            ..ServerConfig::default()
18860        };
18861        let (server, handle, mut events) = Server::bind(config, [original.clone()]).await.unwrap();
18862        let address = server.local_addr().unwrap();
18863        let task = tokio::spawn(server.run());
18864        let mut phone = TcpStream::connect(address).await.unwrap();
18865        let mut decoder = FrameDecoder::new();
18866        let protocol = ProtocolVersion::V22;
18867
18868        phone.write_all(&register_bytes(protocol)).await.unwrap();
18869        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
18870        assert!(matches!(
18871            events.recv().await,
18872            Some(Event::Device(DeviceEvent {
18873                session_generation: _,
18874                device_id: _,
18875                event: DeviceEventKind::Registered(_)
18876            }))
18877        ));
18878        phone
18879            .write_all(
18880                &ClientMessage::OffHook {
18881                    line_instance: 1,
18882                    call_reference: 0,
18883                }
18884                .encode(protocol)
18885                .unwrap(),
18886            )
18887            .await
18888            .unwrap();
18889        let call_id = match events.recv().await {
18890            Some(Event::Device(DeviceEvent {
18891                session_generation: _,
18892                device_id: _,
18893                event: DeviceEventKind::OffHook { call_id, .. },
18894            })) => call_id,
18895            event => panic!("unexpected event: {event:?}"),
18896        };
18897
18898        let added = definition_for("SEP112233445566");
18899        assert_eq!(
18900            handle
18901                .reconfigure([original.clone(), added.clone()])
18902                .await
18903                .unwrap(),
18904            ReconfigureResult {
18905                added: vec![added.id],
18906                ..ReconfigureResult::default()
18907            }
18908        );
18909        assert!(
18910            tokio::time::timeout(Duration::from_millis(50), events.recv())
18911                .await
18912                .is_err()
18913        );
18914
18915        let mut invalid = original.clone();
18916        let ButtonDefinition::Line(line) = &mut invalid.buttons[0] else {
18917            panic!("expected line button");
18918        };
18919        line.instance = 0;
18920        assert!(handle.reconfigure([invalid]).await.is_err());
18921
18922        phone
18923            .write_all(
18924                &ClientMessage::KeypadButton {
18925                    button: Digit::Number(7),
18926                    line_instance: 1,
18927                    call_reference: 0,
18928                    wire_layout: None,
18929                }
18930                .encode(protocol)
18931                .unwrap(),
18932            )
18933            .await
18934            .unwrap();
18935        assert!(matches!(
18936            events.recv().await,
18937            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Digit {
18938                call_id: same_call,
18939                digit: Digit::Number(7),
18940                ..
18941            } })) if same_call == call_id
18942        ));
18943
18944        let mut changed = original;
18945        changed.description = "Changed station".into();
18946        let report = handle.reconfigure([changed]).await.unwrap();
18947        assert_eq!(
18948            report.changed,
18949            vec![DeviceId::new("SEP001122334455").unwrap()]
18950        );
18951        assert_eq!(
18952            report.removed,
18953            vec![DeviceId::new("SEP112233445566").unwrap()]
18954        );
18955        assert!(matches!(
18956            tokio::time::timeout(Duration::from_secs(1), events.recv()).await,
18957            Ok(Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::Disconnected {} })))
18958                if device_id == DeviceId::new("SEP001122334455").unwrap()
18959        ));
18960
18961        handle.shutdown().await.unwrap();
18962        task.await.unwrap().unwrap();
18963    }
18964
18965    #[tokio::test]
18966    async fn concurrent_registration_and_disconnect_storm_retires_every_session_once() {
18967        const PHONE_COUNT: usize = 48;
18968        let device_ids = (0..PHONE_COUNT)
18969            .map(|index| format!("SEP{index:012X}"))
18970            .collect::<Vec<_>>();
18971        let definitions = device_ids
18972            .iter()
18973            .map(|device_id| definition_for(device_id))
18974            .collect::<Vec<_>>();
18975        let config = ServerConfig {
18976            bind: "127.0.0.1:0".parse().unwrap(),
18977            advertised_address: Ipv4Addr::LOCALHOST,
18978            ..ServerConfig::default()
18979        };
18980        let (server, handle, mut events) = Server::bind(config, definitions).await.unwrap();
18981        let address = server.local_addr().unwrap();
18982        let server_task = tokio::spawn(server.run());
18983        let barrier = Arc::new(tokio::sync::Barrier::new(PHONE_COUNT));
18984        let mut registrations = tokio::task::JoinSet::new();
18985        for device_id in &device_ids {
18986            let barrier = Arc::clone(&barrier);
18987            let device_id = device_id.clone();
18988            registrations.spawn(async move {
18989                let mut phone = TcpStream::connect(address).await.unwrap();
18990                let mut decoder = FrameDecoder::new();
18991                barrier.wait().await;
18992                phone
18993                    .write_all(&register_bytes_for_device(
18994                        ProtocolVersion::V22,
18995                        115,
18996                        &device_id,
18997                    ))
18998                    .await
18999                    .unwrap();
19000                read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
19001                (device_id, phone)
19002            });
19003        }
19004        let mut phones = Vec::with_capacity(PHONE_COUNT);
19005        while let Some(result) =
19006            tokio::time::timeout(Duration::from_secs(5), registrations.join_next())
19007                .await
19008                .expect("registration storm exceeded its bound")
19009        {
19010            phones.push(result.unwrap());
19011        }
19012        assert_eq!(phones.len(), PHONE_COUNT);
19013
19014        let mut registered = HashSet::new();
19015        while registered.len() < PHONE_COUNT {
19016            let event = tokio::time::timeout(Duration::from_secs(5), events.recv())
19017                .await
19018                .expect("registration events exceeded their bound")
19019                .expect("server stopped during registration storm");
19020            if let Event::Device(DeviceEvent {
19021                session_generation: _,
19022                device_id: _,
19023                event: DeviceEventKind::Registered(registration),
19024            }) = event
19025            {
19026                assert!(registered.insert(registration.id));
19027            }
19028        }
19029        drop(phones);
19030
19031        let mut disconnected = HashSet::new();
19032        while disconnected.len() < PHONE_COUNT {
19033            let event = tokio::time::timeout(Duration::from_secs(5), events.recv())
19034                .await
19035                .expect("disconnect events exceeded their bound")
19036                .expect("server stopped during disconnect storm");
19037            if let Event::Device(DeviceEvent {
19038                session_generation: _,
19039                device_id,
19040                event: DeviceEventKind::Disconnected {},
19041            }) = event
19042            {
19043                assert!(disconnected.insert(device_id));
19044            }
19045        }
19046        assert_eq!(registered, disconnected);
19047
19048        handle.shutdown().await.unwrap();
19049        server_task.await.unwrap().unwrap();
19050    }
19051
19052    #[tokio::test]
19053    async fn repeated_server_load_and_unload_releases_all_shared_runtime_state() {
19054        const CYCLES: usize = 32;
19055        for _ in 0..CYCLES {
19056            let config = ServerConfig {
19057                bind: "127.0.0.1:0".parse().unwrap(),
19058                advertised_address: Ipv4Addr::LOCALHOST,
19059                ..ServerConfig::default()
19060            };
19061            let (server, handle, events) = Server::bind(config, [definition()]).await.unwrap();
19062            let sessions = Arc::downgrade(&server.sessions);
19063            let call_ids = Arc::downgrade(&handle.next_call_id);
19064            let statistics = Arc::downgrade(&handle.latest_media_statistics);
19065            let answer_order = Arc::downgrade(&handle.call_answer_order);
19066            let server_task = tokio::spawn(server.run());
19067
19068            handle.shutdown().await.unwrap();
19069            server_task.await.unwrap().unwrap();
19070            drop(events);
19071            drop(handle);
19072
19073            assert!(sessions.upgrade().is_none());
19074            assert!(call_ids.upgrade().is_none());
19075            assert!(statistics.upgrade().is_none());
19076            assert!(answer_order.upgrade().is_none());
19077        }
19078    }
19079
19080    #[tokio::test]
19081    async fn reconfiguration_disconnects_a_removed_device_without_touching_its_peer() {
19082        let retained = definition_for("SEP001122334455");
19083        let removed = definition_for("SEP112233445566");
19084        let config = ServerConfig {
19085            bind: "127.0.0.1:0".parse().unwrap(),
19086            advertised_address: Ipv4Addr::LOCALHOST,
19087            ..ServerConfig::default()
19088        };
19089        let (server, handle, mut events) =
19090            Server::bind(config, [retained.clone(), removed.clone()])
19091                .await
19092                .unwrap();
19093        let address = server.local_addr().unwrap();
19094        let task = tokio::spawn(server.run());
19095        let protocol = ProtocolVersion::V22;
19096
19097        let mut retained_phone = TcpStream::connect(address).await.unwrap();
19098        let mut retained_decoder = FrameDecoder::new();
19099        retained_phone
19100            .write_all(&register_bytes_for_device(
19101                protocol,
19102                115,
19103                retained.id.as_str(),
19104            ))
19105            .await
19106            .unwrap();
19107        read_until_message(
19108            &mut retained_phone,
19109            &mut retained_decoder,
19110            id::CAPABILITIES_REQ,
19111        )
19112        .await;
19113        assert!(matches!(
19114            events.recv().await,
19115            Some(Event::Device(DeviceEvent {
19116                session_generation: _,
19117                device_id: _,
19118                event: DeviceEventKind::Registered(_)
19119            }))
19120        ));
19121
19122        let mut removed_phone = TcpStream::connect(address).await.unwrap();
19123        let mut removed_decoder = FrameDecoder::new();
19124        removed_phone
19125            .write_all(&register_bytes_for_device(
19126                protocol,
19127                115,
19128                removed.id.as_str(),
19129            ))
19130            .await
19131            .unwrap();
19132        read_until_message(
19133            &mut removed_phone,
19134            &mut removed_decoder,
19135            id::CAPABILITIES_REQ,
19136        )
19137        .await;
19138        assert!(matches!(
19139            events.recv().await,
19140            Some(Event::Device(DeviceEvent {
19141                session_generation: _,
19142                device_id: _,
19143                event: DeviceEventKind::Registered(_)
19144            }))
19145        ));
19146
19147        let report = handle.reconfigure([retained.clone()]).await.unwrap();
19148        assert_eq!(report.removed, vec![removed.id.clone()]);
19149        assert!(matches!(
19150            tokio::time::timeout(Duration::from_secs(1), events.recv()).await,
19151            Ok(Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::Disconnected {} }))) if device_id == removed.id
19152        ));
19153
19154        retained_phone
19155            .write_all(
19156                &Frame::new(protocol.wire(), id::KEEP_ALIVE, Vec::new())
19157                    .encode()
19158                    .unwrap(),
19159            )
19160            .await
19161            .unwrap();
19162        read_until_message(
19163            &mut retained_phone,
19164            &mut retained_decoder,
19165            id::KEEP_ALIVE_ACK,
19166        )
19167        .await;
19168        assert!(
19169            tokio::time::timeout(Duration::from_millis(50), events.recv())
19170                .await
19171                .is_err()
19172        );
19173
19174        handle.shutdown().await.unwrap();
19175        task.await.unwrap().unwrap();
19176    }
19177
19178    #[test]
19179    fn server_response_uses_the_accepted_local_interface_with_configured_fallback() {
19180        assert_eq!(
19181            server_response_address(
19182                "10.20.30.40".parse().unwrap(),
19183                "192.0.2.10".parse().unwrap(),
19184                Some("2001:db8::10".parse().unwrap()),
19185            ),
19186            "10.20.30.40".parse::<IpAddr>().unwrap()
19187        );
19188        assert_eq!(
19189            server_response_address(
19190                "2001:db8::20".parse().unwrap(),
19191                "192.0.2.10".parse().unwrap(),
19192                Some("2001:db8::10".parse().unwrap()),
19193            ),
19194            "2001:db8::20".parse::<IpAddr>().unwrap()
19195        );
19196        assert_eq!(
19197            server_response_address(
19198                "0.0.0.0".parse().unwrap(),
19199                "192.0.2.10".parse().unwrap(),
19200                Some("2001:db8::10".parse().unwrap()),
19201            ),
19202            "192.0.2.10".parse::<IpAddr>().unwrap()
19203        );
19204        assert_eq!(
19205            server_response_address(
19206                "::".parse().unwrap(),
19207                "192.0.2.10".parse().unwrap(),
19208                Some("2001:db8::10".parse().unwrap()),
19209            ),
19210            "2001:db8::10".parse::<IpAddr>().unwrap()
19211        );
19212    }
19213
19214    #[test]
19215    fn synchronous_offer_and_hangup_commands_cannot_overtake_each_other() {
19216        let (command_tx, mut command_rx) = mpsc::channel(4);
19217        let handle = ServerHandle {
19218            command_tx,
19219            next_call_id: Arc::new(AtomicU64::new(1)),
19220            latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19221            call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19222        };
19223        let device_id = DeviceId::new("SEP001122334455").unwrap();
19224        let call_id = CallId(42);
19225        handle
19226            .try_offer_incoming_call_with_id(
19227                device_id.clone(),
19228                LineInstance::new(1),
19229                call_id,
19230                CallInfo {
19231                    direction: crate::types::CallDirection::Inbound,
19232                    calling_name: "Caller".into(),
19233                    calling_number: "1002".into(),
19234                    called_name: "Desk".into(),
19235                    called_number: "1001".into(),
19236                    ..CallInfo::default()
19237                },
19238            )
19239            .unwrap();
19240        handle
19241            .try_send(Command::new(
19242                device_id.clone(),
19243                CommandAction::CloseCall { call_id },
19244            ))
19245            .unwrap();
19246
19247        assert!(matches!(
19248            command_rx.try_recv().unwrap(),
19249            ServerCommand::OfferIncoming {
19250                device_id: offered_device,
19251                call_id: offered_call,
19252                ..
19253            } if offered_device == device_id && offered_call == call_id
19254        ));
19255        assert!(matches!(
19256            command_rx.try_recv().unwrap(),
19257            ServerCommand::Public(command)
19258                if matches!(command.as_ref(), Command {
19259                    device_id: closed_device,
19260                    action: CommandAction::CloseCall {
19261                    call_id: closed_call,
19262                } } if closed_device == &device_id && *closed_call == call_id)
19263        ));
19264    }
19265
19266    #[test]
19267    fn synchronous_command_queue_reports_saturation_and_recovers_after_drain() {
19268        let (command_tx, mut command_rx) = mpsc::channel(1);
19269        let handle = ServerHandle {
19270            command_tx,
19271            next_call_id: Arc::new(AtomicU64::new(1)),
19272            latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19273            call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19274        };
19275        let device_id = DeviceId::new("SEP001122334455").unwrap();
19276
19277        handle
19278            .try_send(Command::new(
19279                device_id.clone(),
19280                CommandAction::DisconnectDevice {},
19281            ))
19282            .unwrap();
19283        assert!(matches!(
19284            handle.try_send(Command::new(
19285                device_id.clone(),
19286                CommandAction::DisconnectDevice {}
19287            )),
19288            Err(ServerError::CommandQueueFull)
19289        ));
19290        assert!(matches!(
19291            handle.try_offer_incoming_call_with_id(
19292                device_id.clone(),
19293                LineInstance::new(1),
19294                CallId(42),
19295                CallInfo {
19296                    direction: crate::types::CallDirection::Inbound,
19297                    calling_name: "Caller".into(),
19298                    calling_number: "1002".into(),
19299                    called_name: "Desk".into(),
19300                    called_number: "1001".into(),
19301                    ..CallInfo::default()
19302                },
19303            ),
19304            Err(ServerError::CommandQueueFull)
19305        ));
19306
19307        assert!(matches!(
19308            command_rx.try_recv().unwrap(),
19309            ServerCommand::Public(command)
19310                if matches!(command.as_ref(), Command {
19311                    device_id: queued_device,
19312                    action: CommandAction::DisconnectDevice { .. },
19313                } if queued_device == &device_id)
19314        ));
19315        handle
19316            .try_send(Command::new(
19317                device_id.clone(),
19318                CommandAction::DisconnectDevice {},
19319            ))
19320            .unwrap();
19321        assert!(matches!(
19322            command_rx.try_recv().unwrap(),
19323            ServerCommand::Public(command)
19324                if matches!(command.as_ref(), Command {
19325                    device_id: queued_device,
19326                    action: CommandAction::DisconnectDevice { .. },
19327                } if queued_device == &device_id)
19328        ));
19329    }
19330
19331    #[tokio::test]
19332    async fn confirmed_command_waits_for_device_write_and_propagates_failure() {
19333        let (command_tx, mut command_rx) = mpsc::channel(2);
19334        let handle = ServerHandle {
19335            command_tx,
19336            next_call_id: Arc::new(AtomicU64::new(1)),
19337            latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19338            call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19339        };
19340        let device_id = DeviceId::new("SEP001122334455").unwrap();
19341
19342        let success = tokio::spawn({
19343            let handle = handle.clone();
19344            let device_id = device_id.clone();
19345            async move {
19346                handle
19347                    .send_confirmed(Command::new(
19348                        device_id,
19349                        CommandAction::StopAnnouncement {
19350                            conference_id: ConferenceId::new(44),
19351                        },
19352                    ))
19353                    .await
19354            }
19355        });
19356        let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19357            panic!("expected a confirmed command")
19358        };
19359        assert!(!success.is_finished());
19360        written.send(Ok(())).unwrap();
19361        assert!(success.await.unwrap().is_ok());
19362
19363        let failure = tokio::spawn({
19364            let handle = handle.clone();
19365            async move {
19366                handle
19367                    .send_confirmed(Command::new(
19368                        device_id,
19369                        CommandAction::SetMicrophoneMode { enabled: false },
19370                    ))
19371                    .await
19372            }
19373        });
19374        let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19375            panic!("expected a confirmed command")
19376        };
19377        written.send(Err("socket closed".into())).unwrap();
19378        assert!(matches!(
19379            failure.await.unwrap(),
19380            Err(ServerError::CommandWrite(message)) if message == "socket closed"
19381        ));
19382    }
19383
19384    #[tokio::test(start_paused = true)]
19385    async fn ordering_acknowledgement_timeout_bounds_a_stalled_writer_and_retires_sender() {
19386        let (command_tx, mut command_rx) = mpsc::channel(1);
19387        let handle = ServerHandle {
19388            command_tx,
19389            next_call_id: Arc::new(AtomicU64::new(1)),
19390            latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19391            call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19392        };
19393        let pending = tokio::spawn(async move {
19394            handle
19395                .send_confirmed(Command::new(
19396                    DeviceId::new("SEP001122334455").unwrap(),
19397                    CommandAction::SetMicrophoneMode { enabled: false },
19398                ))
19399                .await
19400        });
19401        let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19402            panic!("expected a confirmed command")
19403        };
19404
19405        tokio::time::advance(ORDERING_ACKNOWLEDGEMENT_TIMEOUT).await;
19406        assert!(matches!(
19407            pending.await.unwrap(),
19408            Err(ServerError::CommandAcknowledgementTimeout)
19409        ));
19410        assert!(written.send(Ok(())).is_err());
19411    }
19412
19413    #[tokio::test(start_paused = true)]
19414    async fn expired_confirmed_commands_are_retired_at_both_queue_boundaries() {
19415        let device = definition();
19416        let device_id = device.id.clone();
19417        let config = ServerConfig {
19418            bind: "127.0.0.1:0".parse().unwrap(),
19419            advertised_address: Ipv4Addr::LOCALHOST,
19420            ..ServerConfig::default()
19421        };
19422        let (mut server, handle, _events) = Server::bind(config, [device]).await.unwrap();
19423        let (session_tx, mut session_rx) = mpsc::channel(2);
19424        server.sessions.lock().await.insert(
19425            device_id.clone(),
19426            SessionSender {
19427                generation: SessionGeneration::new(1).unwrap(),
19428                anonymous_hotline: false,
19429                tx: session_tx,
19430            },
19431        );
19432
19433        let server_queued = tokio::spawn({
19434            let handle = handle.clone();
19435            let device_id = device_id.clone();
19436            async move {
19437                handle
19438                    .send_confirmed(Command::new(
19439                        device_id,
19440                        CommandAction::SetMicrophoneMode { enabled: false },
19441                    ))
19442                    .await
19443            }
19444        });
19445        let ServerCommand::Confirmed {
19446            command,
19447            written,
19448            expires_at,
19449        } = server.command_rx.recv().await.unwrap()
19450        else {
19451            panic!("expected a server-queued confirmed command")
19452        };
19453        tokio::time::advance(ORDERING_ACKNOWLEDGEMENT_TIMEOUT).await;
19454        assert!(matches!(
19455            server_queued.await.unwrap(),
19456            Err(ServerError::CommandAcknowledgementTimeout)
19457        ));
19458        server
19459            .dispatch_confirmed(command, written, expires_at)
19460            .await;
19461        assert!(matches!(
19462            session_rx.try_recv(),
19463            Err(mpsc::error::TryRecvError::Empty)
19464        ));
19465
19466        let session_queued = tokio::spawn({
19467            let handle = handle.clone();
19468            async move {
19469                handle
19470                    .send_confirmed(Command::new(
19471                        device_id,
19472                        CommandAction::SetMicrophoneMode { enabled: true },
19473                    ))
19474                    .await
19475            }
19476        });
19477        let ServerCommand::Confirmed {
19478            command,
19479            written,
19480            expires_at,
19481        } = server.command_rx.recv().await.unwrap()
19482        else {
19483            panic!("expected another server-queued confirmed command")
19484        };
19485        server
19486            .dispatch_confirmed(command, written, expires_at)
19487            .await;
19488        let queued = session_rx.recv().await.unwrap();
19489        tokio::time::advance(ORDERING_ACKNOWLEDGEMENT_TIMEOUT).await;
19490        assert!(matches!(
19491            session_queued.await.unwrap(),
19492            Err(ServerError::CommandAcknowledgementTimeout)
19493        ));
19494        assert!(prepare_session_command(queued).is_none());
19495    }
19496
19497    #[tokio::test]
19498    async fn forwarding_collection_commands_propagate_confirmed_writer_failures() {
19499        let (command_tx, mut command_rx) = mpsc::channel(3);
19500        let handle = ServerHandle {
19501            command_tx,
19502            next_call_id: Arc::new(AtomicU64::new(1)),
19503            latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19504            call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19505        };
19506        let device_id = DeviceId::new("SEP001122334455").unwrap();
19507        let commands = [
19508            Command::new(
19509                device_id.clone(),
19510                CommandAction::BeginCall {
19511                    line_instance: LineInstance(1),
19512                    call_id: CallId(42),
19513                    codec: Codec::Pcmu,
19514                },
19515            ),
19516            Command::new(
19517                device_id.clone(),
19518                CommandAction::DisplayPrompt {
19519                    call_id: CallId(42),
19520                    timeout_seconds: 0,
19521                    text: "Enter forwarding destination".into(),
19522                },
19523            ),
19524            Command::new(
19525                device_id,
19526                CommandAction::CloseCall {
19527                    call_id: CallId(42),
19528                },
19529            ),
19530        ];
19531
19532        for (index, command) in commands.into_iter().enumerate() {
19533            let pending = tokio::spawn({
19534                let handle = handle.clone();
19535                async move { handle.send_confirmed(command).await }
19536            });
19537            let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19538                panic!("expected a confirmed forwarding command")
19539            };
19540            assert!(!pending.is_finished());
19541            written
19542                .send(Err(format!("forwarding writer failed at stage {index}")))
19543                .unwrap();
19544            assert!(matches!(
19545                pending.await.unwrap(),
19546                Err(ServerError::CommandWrite(message))
19547                    if message == format!("forwarding writer failed at stage {index}")
19548            ));
19549        }
19550    }
19551
19552    #[tokio::test]
19553    async fn two_phone_shared_offer_honors_ring_policy_and_remote_control_events() {
19554        let protocol = ProtocolVersion::V22;
19555        let first_id = DeviceId::new("SEP001122334455").unwrap();
19556        let second_id = DeviceId::new("SEP112233445566").unwrap();
19557        let config = ServerConfig {
19558            bind: "127.0.0.1:0".parse().unwrap(),
19559            advertised_address: Ipv4Addr::LOCALHOST,
19560            ..ServerConfig::default()
19561        };
19562        let mut first_definition = definition_for(first_id.as_str());
19563        let mut second_definition = definition_for(second_id.as_str());
19564        for definition in [&mut first_definition, &mut second_definition] {
19565            definition.soft_keys = profile_with(
19566                KeyMode::OnHookStealable,
19567                vec![
19568                    SoftKey::Intercept,
19569                    SoftKey::Barge,
19570                    SoftKey::Conference,
19571                    SoftKey::NewCall,
19572                ],
19573            );
19574        }
19575        let stealable_mask = second_definition
19576            .soft_keys
19577            .valid_mask(KeyMode::OnHookStealable);
19578        let (server, handle, mut events) =
19579            Server::bind(config, [first_definition, second_definition])
19580                .await
19581                .unwrap();
19582        let address = server.local_addr().unwrap();
19583        let task = tokio::spawn(server.run());
19584        let mut first = TcpStream::connect(address).await.unwrap();
19585        let mut second = TcpStream::connect(address).await.unwrap();
19586        let mut first_decoder = FrameDecoder::new();
19587        let mut second_decoder = FrameDecoder::new();
19588        first
19589            .write_all(&register_bytes_for_device(protocol, 115, first_id.as_str()))
19590            .await
19591            .unwrap();
19592        second
19593            .write_all(&register_bytes_for_device(
19594                protocol,
19595                115,
19596                second_id.as_str(),
19597            ))
19598            .await
19599            .unwrap();
19600        read_until_message(&mut first, &mut first_decoder, id::REGISTER_ACK).await;
19601        read_until_message(&mut second, &mut second_decoder, id::REGISTER_ACK).await;
19602        let mut registered = HashSet::new();
19603        while registered.len() < 2 {
19604            if let Some(Event::Device(DeviceEvent {
19605                session_generation: _,
19606                device_id: _,
19607                event: DeviceEventKind::Registered(registration),
19608            })) = events.recv().await
19609            {
19610                registered.insert(registration.id);
19611            }
19612        }
19613        assert_eq!(
19614            registered,
19615            HashSet::from([first_id.clone(), second_id.clone()])
19616        );
19617
19618        let info = CallInfo {
19619            direction: crate::types::CallDirection::Inbound,
19620            calling_name: "Caller".into(),
19621            calling_number: "1002".into(),
19622            called_name: "Shared desk".into(),
19623            called_number: "1001".into(),
19624            ..CallInfo::default()
19625        };
19626        let first_call = CallId(101);
19627        let second_call = CallId(102);
19628        handle
19629            .try_offer_incoming_call_with_id_and_ring(
19630                first_id.clone(),
19631                LineInstance::new(1),
19632                first_call,
19633                info.clone(),
19634                true,
19635            )
19636            .unwrap();
19637        handle
19638            .try_offer_incoming_call_with_id_and_ring(
19639                second_id.clone(),
19640                LineInstance::new(1),
19641                second_call,
19642                info,
19643                false,
19644            )
19645            .unwrap();
19646        let first_frames = read_until_message(
19647            &mut first,
19648            &mut first_decoder,
19649            id::DISPLAY_DYNAMIC_PROMPT_STATUS,
19650        )
19651        .await;
19652        let second_frames = read_until_message(
19653            &mut second,
19654            &mut second_decoder,
19655            id::DISPLAY_DYNAMIC_PROMPT_STATUS,
19656        )
19657        .await;
19658        assert!(first_frames.iter().any(|frame| matches!(
19659            ServerMessage::decode(frame.clone(), protocol),
19660            Ok(ServerMessage::SetRinger {
19661                mode: RingerMode::Inside,
19662                ..
19663            })
19664        )));
19665        assert!(!second_frames.iter().any(|frame| matches!(
19666            ServerMessage::decode(frame.clone(), protocol),
19667            Ok(ServerMessage::SetRinger {
19668                mode: RingerMode::Inside,
19669                ..
19670            })
19671        )));
19672
19673        for (device_id, call_id) in [
19674            (first_id.clone(), first_call),
19675            (second_id.clone(), second_call),
19676        ] {
19677            handle
19678                .send(Command::new(
19679                    device_id,
19680                    CommandAction::SetCallState {
19681                        call_id,
19682                        state: CallState::RemoteMultiline,
19683                    },
19684                ))
19685                .await
19686                .unwrap();
19687        }
19688        let first_frames =
19689            read_until_message(&mut first, &mut first_decoder, id::SELECT_SOFT_KEYS).await;
19690        let second_frames =
19691            read_until_message(&mut second, &mut second_decoder, id::SELECT_SOFT_KEYS).await;
19692        assert!(first_frames.iter().any(|frame| matches!(
19693            ServerMessage::decode(frame.clone(), protocol),
19694            Ok(ServerMessage::SetRinger {
19695                mode: RingerMode::Off,
19696                ..
19697            })
19698        )));
19699        assert!(first_frames.iter().any(|frame| matches!(
19700            ServerMessage::decode(frame.clone(), protocol),
19701            Ok(ServerMessage::SetLamp {
19702                stimulus: ButtonType::Line,
19703                mode: LampMode::On,
19704                ..
19705            })
19706        )));
19707        assert!(second_frames.iter().any(|frame| matches!(
19708            ServerMessage::decode(frame.clone(), protocol),
19709            Ok(ServerMessage::SelectSoftKeys {
19710                set: KeyMode::OnHookStealable,
19711                valid_mask,
19712                ..
19713            }) if valid_mask == stealable_mask
19714        )));
19715
19716        second
19717            .write_all(
19718                &ClientMessage::SoftKeyEvent {
19719                    event: SoftKey::Intercept.wire_value(),
19720                    line_instance: 1,
19721                    call_reference: 0,
19722                }
19723                .encode(protocol)
19724                .unwrap(),
19725            )
19726            .await
19727            .unwrap();
19728        assert!(matches!(
19729            events.recv().await,
19730            Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::SoftKey {
19731                call_id: Some(call_id),
19732                soft_key: SoftKey::Intercept,
19733                ..
19734            } })) if device_id == second_id && call_id == second_call
19735        ));
19736        second
19737            .write_all(
19738                &ClientMessage::SoftKeyEvent {
19739                    event: SoftKey::Barge.wire_value(),
19740                    line_instance: 1,
19741                    call_reference: 0,
19742                }
19743                .encode(protocol)
19744                .unwrap(),
19745            )
19746            .await
19747            .unwrap();
19748        assert!(matches!(
19749            events.recv().await,
19750            Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::SoftKey {
19751                call_id: Some(call_id),
19752                soft_key: SoftKey::Barge,
19753                ..
19754            } })) if device_id == second_id && call_id == second_call
19755        ));
19756        second
19757            .write_all(
19758                &ClientMessage::Stimulus {
19759                    stimulus: Stimulus::Conference,
19760                    instance: 1,
19761                    call_reference: 0,
19762                    status: 0,
19763                }
19764                .encode(protocol)
19765                .unwrap(),
19766            )
19767            .await
19768            .unwrap();
19769        assert!(matches!(
19770            events.recv().await,
19771            Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::SoftKey {
19772                call_id: Some(call_id),
19773                soft_key: SoftKey::Conference,
19774                ..
19775            } })) if device_id == second_id && call_id == second_call
19776        ));
19777        second
19778            .write_all(
19779                &ClientMessage::Stimulus {
19780                    stimulus: Stimulus::Line,
19781                    instance: 1,
19782                    call_reference: 0,
19783                    status: 0,
19784                }
19785                .encode(protocol)
19786                .unwrap(),
19787            )
19788            .await
19789            .unwrap();
19790        assert!(matches!(
19791            events.recv().await,
19792            Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::LineButton {
19793                call_id: Some(call_id),
19794                ..
19795            } })) if device_id == second_id && call_id == second_call
19796        ));
19797
19798        second
19799            .write_all(
19800                &ClientMessage::OnHook {
19801                    line_instance: 1,
19802                    call_reference: 0,
19803                }
19804                .encode(protocol)
19805                .unwrap(),
19806            )
19807            .await
19808            .unwrap();
19809        read_until_message(&mut second, &mut second_decoder, id::DEFINE_TIME_DATE).await;
19810        assert!(matches!(
19811            events.recv().await,
19812            Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::OnHook {
19813                call_id,
19814                ..
19815            } })) if device_id == second_id && call_id == second_call
19816        ));
19817        handle
19818            .send(Command::new(
19819                second_id.clone(),
19820                CommandAction::SetCallState {
19821                    call_id: second_call,
19822                    state: CallState::RemoteMultiline,
19823                },
19824            ))
19825            .await
19826            .unwrap();
19827        let restored =
19828            read_until_message(&mut second, &mut second_decoder, id::SELECT_SOFT_KEYS).await;
19829        assert!(restored.iter().any(|frame| matches!(
19830            ServerMessage::decode(frame.clone(), protocol),
19831            Ok(ServerMessage::CallState {
19832                state: CallState::RemoteMultiline,
19833                ..
19834            })
19835        )));
19836
19837        for (device_id, call_id) in [
19838            (first_id.clone(), first_call),
19839            (second_id.clone(), second_call),
19840        ] {
19841            handle
19842                .send(Command::new(
19843                    device_id,
19844                    CommandAction::CloseCall { call_id },
19845                ))
19846                .await
19847                .unwrap();
19848        }
19849        let first_close = read_until_message(&mut first, &mut first_decoder, id::CALL_STATE).await;
19850        let second_close =
19851            read_until_message(&mut second, &mut second_decoder, id::CALL_STATE).await;
19852        for frames in [first_close, second_close] {
19853            assert!(frames.iter().any(|frame| matches!(
19854                ServerMessage::decode(frame.clone(), protocol),
19855                Ok(ServerMessage::CallState {
19856                    state: CallState::OnHook,
19857                    ..
19858                })
19859            )));
19860        }
19861
19862        handle.shutdown().await.unwrap();
19863        task.await.unwrap().unwrap();
19864    }
19865
19866    #[tokio::test]
19867    async fn standalone_server_registers_and_serves_line_status() {
19868        for protocol in [
19869            ProtocolVersion::V3,
19870            ProtocolVersion::V17,
19871            ProtocolVersion::V22,
19872        ] {
19873            let config = ServerConfig {
19874                bind: "127.0.0.1:0".parse().unwrap(),
19875                advertised_address: Ipv4Addr::LOCALHOST,
19876                ..ServerConfig::default()
19877            };
19878            let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
19879            let address = server.local_addr().unwrap();
19880            let task = tokio::spawn(server.run());
19881            let mut phone = TcpStream::connect(address).await.unwrap();
19882            let mut alarm_payload = vec![0; 2_000];
19883            let alarm = b"<?xml version=\"1.0\"?><x-cisco-alarm></x-cisco-alarm>";
19884            alarm_payload[..alarm.len()].copy_from_slice(alarm);
19885            phone
19886                .write_all(
19887                    &Frame::new(0, id::XML_ALARM, alarm_payload)
19888                        .encode()
19889                        .unwrap(),
19890                )
19891                .await
19892                .unwrap();
19893            phone.write_all(&register_bytes(protocol)).await.unwrap();
19894            let mut decoder = FrameDecoder::new();
19895            let mut buffer = [0_u8; 1024];
19896            let frames = read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
19897            let ack = frames
19898                .iter()
19899                .find(|frame| frame.message_id == id::REGISTER_ACK)
19900                .cloned()
19901                .unwrap();
19902            assert_eq!(
19903                ServerMessage::decode(ack, protocol).unwrap(),
19904                ServerMessage::RegisterAck {
19905                    keepalive_seconds: 30,
19906                    secondary_keepalive_seconds: 30,
19907                    protocol,
19908                    features: PhoneFeatures::empty(),
19909                    date_template: Default::default(),
19910                }
19911            );
19912            assert!(
19913                frames
19914                    .iter()
19915                    .any(|frame| frame.message_id == id::CAPABILITIES_REQ)
19916            );
19917            assert!(matches!(
19918                events.recv().await,
19919                Some(Event::Device(DeviceEvent {
19920                    session_generation: _,
19921                    device_id: _,
19922                    event: DeviceEventKind::Registered(_)
19923                }))
19924            ));
19925
19926            let malformed = Frame::new(protocol.wire(), id::IP_PORT, vec![0, 1])
19927                .encode()
19928                .unwrap();
19929            let keepalive = Frame::new(protocol.wire(), id::KEEP_ALIVE, vec![0; 4])
19930                .encode()
19931                .unwrap();
19932            phone
19933                .write_all(&[malformed, keepalive].concat())
19934                .await
19935                .unwrap();
19936            let count = phone.read(&mut buffer).await.unwrap();
19937            let frames = decoder.push(&buffer[..count]).unwrap();
19938            assert!(
19939                frames
19940                    .iter()
19941                    .any(|frame| frame.message_id == id::KEEP_ALIVE_ACK),
19942                "session did not survive a malformed application message"
19943            );
19944            assert!(matches!(
19945                events.recv().await,
19946                Some(Event::ProtocolWarning {
19947                    message_id: id::IP_PORT,
19948                    ..
19949                })
19950            ));
19951
19952            phone
19953                .write_all(
19954                    &Frame::new(
19955                        protocol.wire(),
19956                        id::LINE_STAT_REQ,
19957                        1_u32.to_le_bytes().to_vec(),
19958                    )
19959                    .encode()
19960                    .unwrap(),
19961                )
19962                .await
19963                .unwrap();
19964            let line_message_id = if protocol >= ProtocolVersion::V17 {
19965                id::LINE_STAT_DYNAMIC
19966            } else {
19967                id::LINE_STAT
19968            };
19969            let frames = read_until_message(&mut phone, &mut decoder, line_message_id).await;
19970            let line = frames
19971                .iter()
19972                .find(|frame| frame.message_id == line_message_id)
19973                .unwrap();
19974            assert!(matches!(
19975                ServerMessage::decode(line.clone(), protocol).unwrap(),
19976                ServerMessage::LineStatus { number, .. } if number == "1001"
19977            ));
19978
19979            phone
19980                .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
19981                .await
19982                .unwrap();
19983            let frames = read_until_message(&mut phone, &mut decoder, id::SERVER_RES).await;
19984            let response = frames
19985                .into_iter()
19986                .find(|frame| frame.message_id == id::SERVER_RES)
19987                .unwrap();
19988            assert_eq!(
19989                ServerMessage::decode(response, protocol).unwrap(),
19990                ServerMessage::ServerResponse {
19991                    servers: vec![SignalingServerEndpoint {
19992                        name: "sccp-protocol".into(),
19993                        address: address.ip(),
19994                        port: NonZeroU16::new(address.port()).unwrap(),
19995                    }],
19996                }
19997            );
19998
19999            let cancelled_call = CallId(9_001);
20000            handle
20001                .try_send(Command::new(
20002                    DeviceId::new("SEP001122334455").unwrap(),
20003                    CommandAction::CloseCall {
20004                        call_id: cancelled_call,
20005                    },
20006                ))
20007                .unwrap();
20008            handle
20009                .try_offer_incoming_call_with_id(
20010                    DeviceId::new("SEP001122334455").unwrap(),
20011                    LineInstance::new(1),
20012                    cancelled_call,
20013                    CallInfo {
20014                        direction: crate::types::CallDirection::Inbound,
20015                        calling_name: "Cancelled caller".into(),
20016                        calling_number: "1009".into(),
20017                        called_name: "Desk".into(),
20018                        called_number: "1001".into(),
20019                        ..CallInfo::default()
20020                    },
20021                )
20022                .unwrap();
20023            handle
20024                .try_send(Command::new(
20025                    DeviceId::new("SEP001122334455").unwrap(),
20026                    CommandAction::SetCallState {
20027                        call_id: cancelled_call,
20028                        state: CallState::Connected,
20029                    },
20030                ))
20031                .unwrap();
20032            assert!(
20033                tokio::time::timeout(Duration::from_millis(50), phone.read(&mut buffer))
20034                    .await
20035                    .is_err(),
20036                "a call cancelled before its offer still rang the phone"
20037            );
20038
20039            let incoming = handle
20040                .offer_incoming_call(
20041                    DeviceId::new("SEP001122334455").unwrap(),
20042                    LineInstance::new(1),
20043                    CallInfo {
20044                        direction: crate::types::CallDirection::Inbound,
20045                        calling_name: "Caller".into(),
20046                        calling_number: "1002".into(),
20047                        called_name: "Desk".into(),
20048                        called_number: "1001".into(),
20049                        ..CallInfo::default()
20050                    },
20051                )
20052                .await
20053                .unwrap();
20054            let frames = read_until_message(
20055                &mut phone,
20056                &mut decoder,
20057                if protocol >= ProtocolVersion::V8 {
20058                    id::DISPLAY_DYNAMIC_PROMPT_STATUS
20059                } else {
20060                    id::DISPLAY_PROMPT_STATUS
20061                },
20062            )
20063            .await;
20064            assert!(
20065                frames
20066                    .iter()
20067                    .all(|frame| frame.message_id != id::ACTIVATE_CALL_PLANE),
20068                "RingIn activated the call plane before answer"
20069            );
20070
20071            phone
20072                .write_all(
20073                    &ClientMessage::SoftKeyEvent {
20074                        event: SoftKey::Answer.wire_value(),
20075                        line_instance: 0,
20076                        call_reference: 0,
20077                    }
20078                    .encode(protocol)
20079                    .unwrap(),
20080                )
20081                .await
20082                .unwrap();
20083            let frames = read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
20084            let off_hook = frames
20085                .iter()
20086                .position(|frame| {
20087                    matches!(
20088                        ServerMessage::decode(frame.clone(), protocol),
20089                        Ok(ServerMessage::CallState {
20090                            state: CallState::OffHook,
20091                            ..
20092                        })
20093                    )
20094                })
20095                .expect("answer did not transition through OffHook");
20096            let activate = frames
20097                .iter()
20098                .position(|frame| frame.message_id == id::ACTIVATE_CALL_PLANE)
20099                .expect("answer did not activate the call plane");
20100            assert!(
20101                off_hook < activate,
20102                "OffHook must precede call-plane activation"
20103            );
20104            let answer_event = events.recv().await;
20105            assert!(
20106                matches!(
20107                    answer_event,
20108                    Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20109                        call_id: Some(answered),
20110                        soft_key: SoftKey::Answer,
20111                        ..
20112                    } })) if answered == incoming
20113                ),
20114                "unexpected answer event: {answer_event:?}"
20115            );
20116
20117            handle
20118                .send(Command::new(
20119                    DeviceId::new("SEP001122334455").unwrap(),
20120                    CommandAction::SetCallState {
20121                        call_id: incoming,
20122                        state: CallState::Connected,
20123                    },
20124                ))
20125                .await
20126                .unwrap();
20127            let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
20128            assert!(
20129                frames
20130                    .iter()
20131                    .any(|frame| frame.message_id == id::SET_SPEAKER_MODE),
20132                "Connected did not enable the active audio accessory"
20133            );
20134            assert!(frames.iter().any(|frame| {
20135                matches!(
20136                    ServerMessage::decode(frame.clone(), protocol),
20137                    Ok(ServerMessage::DisplayPrompt { text, .. }) if text == "Connected"
20138                )
20139            }));
20140
20141            phone
20142                .write_all(
20143                    &ClientMessage::OffHook {
20144                        line_instance: 1,
20145                        call_reference: incoming.0 as u32,
20146                    }
20147                    .encode(protocol)
20148                    .unwrap(),
20149                )
20150                .await
20151                .unwrap();
20152            assert!(
20153                tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
20154                    .await
20155                    .is_err(),
20156                "duplicate OffHook rewrote a connected call's handset UI"
20157            );
20158            assert!(
20159                tokio::time::timeout(Duration::from_millis(50), events.recv())
20160                    .await
20161                    .is_err(),
20162                "duplicate OffHook emitted a second application event"
20163            );
20164
20165            phone
20166                .write_all(
20167                    &ClientMessage::SoftKeyEvent {
20168                        event: SoftKey::Hold.wire_value(),
20169                        line_instance: 1,
20170                        call_reference: 0,
20171                    }
20172                    .encode(protocol)
20173                    .unwrap(),
20174                )
20175                .await
20176                .unwrap();
20177            assert!(matches!(
20178                events.recv().await,
20179                Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20180                    call_id: Some(held),
20181                    soft_key: SoftKey::Hold,
20182                    ..
20183                } })) if held == incoming
20184            ));
20185            handle
20186                .send(Command::new(
20187                    DeviceId::new("SEP001122334455").unwrap(),
20188                    CommandAction::SetCallState {
20189                        call_id: incoming,
20190                        state: CallState::Hold,
20191                    },
20192                ))
20193                .await
20194                .unwrap();
20195            let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
20196            assert!(frames.iter().any(|frame| matches!(
20197                ServerMessage::decode(frame.clone(), protocol),
20198                Ok(ServerMessage::CallState {
20199                    state: CallState::Hold,
20200                    ..
20201                })
20202            )));
20203            assert!(frames.iter().any(|frame| matches!(
20204                ServerMessage::decode(frame.clone(), protocol),
20205                Ok(ServerMessage::SetLamp {
20206                    mode: LampMode::Wink,
20207                    ..
20208                })
20209            )));
20210            assert!(frames.iter().any(|frame| matches!(
20211                ServerMessage::decode(frame.clone(), protocol),
20212                Ok(ServerMessage::SetSpeakerMode(SpeakerMode::Off))
20213            )));
20214            assert!(frames.iter().any(|frame| matches!(
20215                ServerMessage::decode(frame.clone(), protocol),
20216                Ok(ServerMessage::SelectSoftKeys {
20217                    set: KeyMode::OnHold,
20218                    ..
20219                })
20220            )));
20221
20222            phone
20223                .write_all(
20224                    &ClientMessage::SoftKeyEvent {
20225                        event: SoftKey::Resume.wire_value(),
20226                        line_instance: 1,
20227                        call_reference: 0,
20228                    }
20229                    .encode(protocol)
20230                    .unwrap(),
20231                )
20232                .await
20233                .unwrap();
20234            assert!(matches!(
20235                events.recv().await,
20236                Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20237                    call_id: Some(resumed),
20238                    soft_key: SoftKey::Resume,
20239                    ..
20240                } })) if resumed == incoming
20241            ));
20242            handle
20243                .send(Command::new(
20244                    DeviceId::new("SEP001122334455").unwrap(),
20245                    CommandAction::SetCallState {
20246                        call_id: incoming,
20247                        state: CallState::Connected,
20248                    },
20249                ))
20250                .await
20251                .unwrap();
20252            let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
20253            assert!(frames.iter().any(|frame| matches!(
20254                ServerMessage::decode(frame.clone(), protocol),
20255                Ok(ServerMessage::CallState {
20256                    state: CallState::Connected,
20257                    ..
20258                })
20259            )));
20260            assert!(frames.iter().any(|frame| matches!(
20261                ServerMessage::decode(frame.clone(), protocol),
20262                Ok(ServerMessage::SetSpeakerMode(SpeakerMode::On))
20263            )));
20264            assert!(frames.iter().any(|frame| matches!(
20265                ServerMessage::decode(frame.clone(), protocol),
20266                Ok(ServerMessage::SelectSoftKeys {
20267                    set: KeyMode::Connected,
20268                    ..
20269                })
20270            )));
20271
20272            phone
20273                .write_all(
20274                    &ClientMessage::KeypadButton {
20275                        button: Digit::Number(5),
20276                        line_instance: 1,
20277                        call_reference: 0,
20278                        wire_layout: None,
20279                    }
20280                    .encode(protocol)
20281                    .unwrap(),
20282                )
20283                .await
20284                .unwrap();
20285            assert!(matches!(
20286                events.recv().await,
20287                Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Digit {
20288                    call_id,
20289                    digit: Digit::Number(5),
20290                    ..
20291                } })) if call_id == incoming
20292            ));
20293            assert!(
20294                tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
20295                    .await
20296                    .is_err(),
20297                "connected DTMF emitted dial-collection UI"
20298            );
20299
20300            phone
20301                .write_all(
20302                    &ClientMessage::SoftKeyEvent {
20303                        event: SoftKey::EndCall.wire_value(),
20304                        line_instance: 1,
20305                        call_reference: 0,
20306                    }
20307                    .encode(protocol)
20308                    .unwrap(),
20309                )
20310                .await
20311                .unwrap();
20312            assert!(matches!(
20313                events.recv().await,
20314                Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20315                    call_id: Some(ended),
20316                    soft_key: SoftKey::EndCall,
20317                    ..
20318                } })) if ended == incoming
20319            ));
20320
20321            handle
20322                .send(Command::new(
20323                    DeviceId::new("SEP001122334455").unwrap(),
20324                    CommandAction::CloseCall { call_id: incoming },
20325                ))
20326                .await
20327                .unwrap();
20328            let frames = read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
20329            let on_hook = frames
20330                .iter()
20331                .position(|frame| {
20332                    matches!(
20333                        ServerMessage::decode(frame.clone(), protocol),
20334                        Ok(ServerMessage::CallState {
20335                            state: CallState::OnHook,
20336                            ..
20337                        })
20338                    )
20339                })
20340                .expect("close did not send OnHook");
20341            let ringer_off = frames
20342                .iter()
20343                .position(|frame| {
20344                    matches!(
20345                        ServerMessage::decode(frame.clone(), protocol),
20346                        Ok(ServerMessage::SetRinger {
20347                            mode: RingerMode::Off,
20348                            ..
20349                        })
20350                    )
20351                })
20352                .expect("close did not stop the ringer");
20353            assert!(
20354                on_hook < ringer_off,
20355                "79x1 phones require OnHook before the final ringer-off indication"
20356            );
20357
20358            let mut updated = definition();
20359            let crate::types::ButtonDefinition::Line(line) = &mut updated.buttons[0] else {
20360                panic!("expected line button");
20361            };
20362            line.label = Some("Updated desk".into());
20363            handle.reconfigure([updated]).await.unwrap();
20364            assert!(matches!(
20365                tokio::time::timeout(Duration::from_secs(1), events.recv()).await,
20366                Ok(Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::Disconnected {} })))
20367                    if device_id.as_str() == "SEP001122334455"
20368            ));
20369
20370            handle.shutdown().await.unwrap();
20371            task.await.unwrap().unwrap();
20372        }
20373    }
20374
20375    #[tokio::test]
20376    async fn injected_streams_enforce_station_transport_requirements() {
20377        for (requirement, transport, accepted) in [
20378            (
20379                StationTransportRequirement::Clear,
20380                StationTransport::Clear,
20381                true,
20382            ),
20383            (
20384                StationTransportRequirement::Clear,
20385                StationTransport::Secure,
20386                false,
20387            ),
20388            (
20389                StationTransportRequirement::Secure,
20390                StationTransport::Secure,
20391                true,
20392            ),
20393            (
20394                StationTransportRequirement::Secure,
20395                StationTransport::Clear,
20396                false,
20397            ),
20398            (
20399                StationTransportRequirement::Either,
20400                StationTransport::Clear,
20401                true,
20402            ),
20403            (
20404                StationTransportRequirement::Either,
20405                StationTransport::Secure,
20406                true,
20407            ),
20408        ] {
20409            let mut station = definition();
20410            station.transport = requirement;
20411            let config = ServerConfig {
20412                bind: "127.0.0.1:0".parse().unwrap(),
20413                advertised_address: Ipv4Addr::LOCALHOST,
20414                ..ServerConfig::default()
20415            };
20416            let (server, handle, mut events, ingress) =
20417                Server::with_ingress(config, [station]).unwrap();
20418            let task = tokio::spawn(server.run());
20419            let (server_stream, mut phone) = tokio::io::duplex(8_192);
20420            let peer = SocketAddr::from(([127, 0, 0, 1], 40_000));
20421            let local = SocketAddr::from(([127, 0, 0, 1], 2_000));
20422            ingress
20423                .accept(server_stream, peer, local, transport)
20424                .await
20425                .unwrap();
20426            phone
20427                .write_all(&register_bytes(ProtocolVersion::V22))
20428                .await
20429                .unwrap();
20430
20431            let mut decoder = FrameDecoder::new();
20432            let expected = if accepted {
20433                id::REGISTER_ACK
20434            } else {
20435                id::REGISTER_REJECT
20436            };
20437            let frames = read_until_message(&mut phone, &mut decoder, expected).await;
20438            assert!(frames.iter().any(|frame| frame.message_id == expected));
20439            if accepted {
20440                assert!(matches!(
20441                    events.recv().await,
20442                    Some(Event::Device(DeviceEvent { session_generation: _,
20443                        event: DeviceEventKind::Registered(registration),
20444                        ..
20445                    })) if registration.transport == transport
20446                ));
20447            }
20448
20449            handle.shutdown().await.unwrap();
20450            task.await.unwrap().unwrap();
20451        }
20452    }
20453
20454    #[tokio::test]
20455    async fn registration_tokens_apply_transport_priority_parity_and_configured_backoff() {
20456        let cases = [
20457            (
20458                RegistrationFallback::Reject,
20459                1,
20460                "SEP001122334455",
20461                StationTransport::Clear,
20462                false,
20463            ),
20464            (
20465                RegistrationFallback::ReturnToPrimary,
20466                1,
20467                "SEP001122334455",
20468                StationTransport::Clear,
20469                true,
20470            ),
20471            (
20472                RegistrationFallback::ReturnToPrimary,
20473                2,
20474                "SEP001122334455",
20475                StationTransport::Clear,
20476                false,
20477            ),
20478            (
20479                RegistrationFallback::DeviceIdOdd,
20480                2,
20481                "SEP001122334455",
20482                StationTransport::Clear,
20483                true,
20484            ),
20485            (
20486                RegistrationFallback::DeviceIdEven,
20487                2,
20488                "SEP001122334455",
20489                StationTransport::Clear,
20490                false,
20491            ),
20492        ];
20493        for (fallback, server_priority, device_id, transport, accepted) in cases {
20494            let station = definition();
20495            let config = ServerConfig {
20496                registration_tokens: RegistrationTokenPolicy {
20497                    fallback,
20498                    backoff: Duration::from_secs(75),
20499                    server_priority,
20500                },
20501                ..ServerConfig::default()
20502            };
20503            let (server, handle, _events, ingress) =
20504                Server::with_ingress(config, [station]).unwrap();
20505            let task = tokio::spawn(server.run());
20506            let (server_stream, mut phone) = tokio::io::duplex(2_048);
20507            ingress
20508                .accept(
20509                    server_stream,
20510                    SocketAddr::from(([127, 0, 0, 1], 40_000)),
20511                    SocketAddr::from(([127, 0, 0, 1], 2_000)),
20512                    transport,
20513                )
20514                .await
20515                .unwrap();
20516            phone
20517                .write_all(
20518                    &ClientMessage::RegisterToken(crate::message::RegisterTokenMessage {
20519                        device_id: DeviceId::new(device_id).unwrap(),
20520                        device_instance: 1,
20521                        address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20522                        device_type: DeviceType::from(115),
20523                        flags: 0,
20524                    })
20525                    .encode(ProtocolVersion::V17)
20526                    .unwrap(),
20527                )
20528                .await
20529                .unwrap();
20530            let expected = if accepted {
20531                id::REGISTER_TOKEN_ACK
20532            } else {
20533                id::REGISTER_TOKEN_REJECT
20534            };
20535            let mut decoder = FrameDecoder::new();
20536            let frames = read_until_message(&mut phone, &mut decoder, expected).await;
20537            let response = frames
20538                .into_iter()
20539                .find(|frame| frame.message_id == expected)
20540                .unwrap();
20541            if accepted {
20542                assert_eq!(
20543                    ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20544                    ServerMessage::RegisterTokenAck
20545                );
20546            } else {
20547                assert_eq!(
20548                    ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20549                    ServerMessage::RegisterTokenReject {
20550                        backoff_seconds: 75,
20551                    }
20552                );
20553            }
20554            handle.shutdown().await.unwrap();
20555            task.await.unwrap().unwrap();
20556        }
20557
20558        let mut secure_station = definition();
20559        secure_station.transport = StationTransportRequirement::Secure;
20560        let config = ServerConfig {
20561            registration_tokens: RegistrationTokenPolicy {
20562                fallback: RegistrationFallback::ReturnToPrimary,
20563                backoff: Duration::from_secs(90),
20564                server_priority: 1,
20565            },
20566            ..ServerConfig::default()
20567        };
20568        let (server, handle, _events, ingress) =
20569            Server::with_ingress(config, [secure_station]).unwrap();
20570        let task = tokio::spawn(server.run());
20571        let (server_stream, mut phone) = tokio::io::duplex(2_048);
20572        ingress
20573            .accept(
20574                server_stream,
20575                SocketAddr::from(([127, 0, 0, 1], 40_001)),
20576                SocketAddr::from(([127, 0, 0, 1], 2_000)),
20577                StationTransport::Clear,
20578            )
20579            .await
20580            .unwrap();
20581        phone
20582            .write_all(
20583                &ClientMessage::RegisterToken(crate::message::RegisterTokenMessage {
20584                    device_id: DeviceId::new("SEP001122334455").unwrap(),
20585                    device_instance: 1,
20586                    address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20587                    device_type: DeviceType::from(115),
20588                    flags: 0,
20589                })
20590                .encode(ProtocolVersion::V17)
20591                .unwrap(),
20592            )
20593            .await
20594            .unwrap();
20595        let mut decoder = FrameDecoder::new();
20596        let response = read_until_message(&mut phone, &mut decoder, id::REGISTER_TOKEN_REJECT)
20597            .await
20598            .into_iter()
20599            .find(|frame| frame.message_id == id::REGISTER_TOKEN_REJECT)
20600            .unwrap();
20601        assert_eq!(
20602            ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20603            ServerMessage::RegisterTokenReject {
20604                backoff_seconds: 90,
20605            }
20606        );
20607        handle.shutdown().await.unwrap();
20608        task.await.unwrap().unwrap();
20609    }
20610
20611    #[test]
20612    fn registration_token_parity_requires_a_canonical_sep_mac_identity() {
20613        let policy = |fallback| RegistrationTokenPolicy {
20614            fallback,
20615            server_priority: 2,
20616            ..RegistrationTokenPolicy::default()
20617        };
20618        assert!(
20619            policy(RegistrationFallback::DeviceIdOdd)
20620                .accepts(&DeviceId::new("SEP001122334455").unwrap())
20621        );
20622        assert!(
20623            policy(RegistrationFallback::DeviceIdEven)
20624                .accepts(&DeviceId::new("SEP001122334454").unwrap())
20625        );
20626        for device_id in ["ALICE1", "SEP1", "SEP00112233445Z"] {
20627            let device_id = DeviceId::new(device_id).unwrap();
20628            assert!(!policy(RegistrationFallback::DeviceIdOdd).accepts(&device_id));
20629            assert!(!policy(RegistrationFallback::DeviceIdEven).accepts(&device_id));
20630        }
20631        let return_to_primary = RegistrationTokenPolicy {
20632            fallback: RegistrationFallback::ReturnToPrimary,
20633            server_priority: 1,
20634            ..RegistrationTokenPolicy::default()
20635        };
20636        assert!(return_to_primary.accepts(&DeviceId::new("ALICE1").unwrap()));
20637    }
20638
20639    #[tokio::test]
20640    async fn duplicate_registration_token_leaves_the_live_session_addressable() {
20641        let config = ServerConfig {
20642            bind: "127.0.0.1:0".parse().unwrap(),
20643            advertised_address: Ipv4Addr::LOCALHOST,
20644            registration_tokens: RegistrationTokenPolicy {
20645                fallback: RegistrationFallback::ReturnToPrimary,
20646                backoff: Duration::from_secs(75),
20647                server_priority: 1,
20648            },
20649            ..ServerConfig::default()
20650        };
20651        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
20652        let address = server.local_addr().unwrap();
20653        let task = tokio::spawn(server.run());
20654        let protocol = ProtocolVersion::V22;
20655        let device_id = DeviceId::new("SEP001122334455").unwrap();
20656        let call_id = CallId(7001);
20657
20658        let mut phone = TcpStream::connect(address).await.unwrap();
20659        let mut decoder = FrameDecoder::new();
20660        phone.write_all(&register_bytes(protocol)).await.unwrap();
20661        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
20662        assert!(matches!(
20663            events.recv().await,
20664            Some(Event::Device(DeviceEvent {
20665                session_generation: _,
20666                event: DeviceEventKind::Registered(_),
20667                ..
20668            }))
20669        ));
20670
20671        handle
20672            .send_confirmed(Command::new(
20673                device_id.clone(),
20674                CommandAction::BeginCall {
20675                    line_instance: LineInstance::new(1),
20676                    call_id,
20677                    codec: Codec::Pcmu,
20678                },
20679            ))
20680            .await
20681            .unwrap();
20682        let wire_call_reference = read_until_message(&mut phone, &mut decoder, id::CALL_STATE)
20683            .await
20684            .into_iter()
20685            .find_map(|frame| match ServerMessage::decode(frame, protocol) {
20686                Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
20687                _ => None,
20688            })
20689            .expect("begin call omitted its wire reference");
20690        handle
20691            .send_confirmed(Command::new(
20692                device_id.clone(),
20693                CommandAction::OpenReceiveChannel {
20694                    call_id,
20695                    source: None,
20696                    codec: Codec::Pcmu,
20697                    packet_ms: 20,
20698                    max_frames_per_packet: 1,
20699                    dtmf_mode: DtmfMode::Skinny,
20700                    audio_processing: AudioProcessingPolicy::default(),
20701                },
20702            ))
20703            .await
20704            .unwrap();
20705        let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
20706        let media_party = open_receive_request_party(&frames, protocol);
20707        phone
20708            .write_all(
20709                &ClientMessage::OpenReceiveChannelAck {
20710                    status: MediaStatus::Ok,
20711                    address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20712                    port: 4000,
20713                    call_reference: wire_call_reference,
20714                    passthrough_party_id: media_party,
20715                }
20716                .encode(protocol)
20717                .unwrap(),
20718            )
20719            .await
20720            .unwrap();
20721        assert!(matches!(
20722            events.recv().await,
20723            Some(Event::Device(DeviceEvent { session_generation: _,
20724                event: DeviceEventKind::ReceiveChannelOpened {
20725                    call_id: actual_call_id,
20726                    ..
20727                },
20728                ..
20729            })) if actual_call_id == call_id
20730        ));
20731
20732        let mut contender = TcpStream::connect(address).await.unwrap();
20733        let mut contender_decoder = FrameDecoder::new();
20734        contender
20735            .write_all(
20736                &ClientMessage::RegisterToken(crate::message::RegisterTokenMessage {
20737                    device_id: device_id.clone(),
20738                    device_instance: 1,
20739                    address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20740                    device_type: DeviceType::from(115),
20741                    flags: 0,
20742                })
20743                .encode(ProtocolVersion::V17)
20744                .unwrap(),
20745            )
20746            .await
20747            .unwrap();
20748        let response = read_until_message(
20749            &mut contender,
20750            &mut contender_decoder,
20751            id::REGISTER_TOKEN_REJECT,
20752        )
20753        .await
20754        .into_iter()
20755        .find(|frame| frame.message_id == id::REGISTER_TOKEN_REJECT)
20756        .unwrap();
20757        assert_eq!(
20758            ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20759            ServerMessage::RegisterTokenReject {
20760                backoff_seconds: 75,
20761            }
20762        );
20763
20764        handle
20765            .send_confirmed(Command::new(
20766                device_id.clone(),
20767                CommandAction::SetCallState {
20768                    call_id,
20769                    state: CallState::Connected,
20770                },
20771            ))
20772            .await
20773            .unwrap();
20774        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
20775        assert!(frames.into_iter().any(|frame| matches!(
20776            ServerMessage::decode(frame, protocol),
20777            Ok(ServerMessage::CallState {
20778                state: CallState::Connected,
20779                ..
20780            })
20781        )));
20782        handle
20783            .send_confirmed(Command::new(
20784                device_id,
20785                CommandAction::CloseReceiveChannel { call_id },
20786            ))
20787            .await
20788            .unwrap();
20789        read_until_message(&mut phone, &mut decoder, id::CLOSE_RECEIVE_CHANNEL).await;
20790        phone
20791            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
20792            .await
20793            .unwrap();
20794        read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
20795
20796        handle.shutdown().await.unwrap();
20797        task.await.unwrap().unwrap();
20798    }
20799
20800    #[tokio::test]
20801    async fn server_list_selects_ordered_endpoints_for_the_active_transport() {
20802        let config = ServerConfig {
20803            signaling_servers: vec![
20804                SignalingServerRoute {
20805                    priority: 2,
20806                    name: "backup".into(),
20807                    address: "192.0.2.20".parse().unwrap(),
20808                    clear_port: NonZeroU16::new(2001),
20809                    secure_port: None,
20810                },
20811                SignalingServerRoute {
20812                    priority: 1,
20813                    name: "primary".into(),
20814                    address: "192.0.2.10".parse().unwrap(),
20815                    clear_port: NonZeroU16::new(2000),
20816                    secure_port: NonZeroU16::new(2443),
20817                },
20818            ],
20819            ..ServerConfig::default()
20820        };
20821        let (server, handle, mut events, ingress) =
20822            Server::with_ingress(config, [definition()]).unwrap();
20823        let task = tokio::spawn(server.run());
20824
20825        for (index, transport, expected) in [
20826            (
20827                0,
20828                StationTransport::Clear,
20829                vec![
20830                    SignalingServerEndpoint {
20831                        name: "primary".into(),
20832                        address: "192.0.2.10".parse().unwrap(),
20833                        port: NonZeroU16::new(2000).unwrap(),
20834                    },
20835                    SignalingServerEndpoint {
20836                        name: "backup".into(),
20837                        address: "192.0.2.20".parse().unwrap(),
20838                        port: NonZeroU16::new(2001).unwrap(),
20839                    },
20840                ],
20841            ),
20842            (
20843                1,
20844                StationTransport::Secure,
20845                vec![SignalingServerEndpoint {
20846                    name: "primary".into(),
20847                    address: "192.0.2.10".parse().unwrap(),
20848                    port: NonZeroU16::new(2443).unwrap(),
20849                }],
20850            ),
20851        ] {
20852            let (server_stream, mut phone) = tokio::io::duplex(8_192);
20853            ingress
20854                .accept(
20855                    server_stream,
20856                    SocketAddr::from(([127, 0, 0, 1], 40_000 + index)),
20857                    SocketAddr::from(([127, 0, 0, 1], if index == 0 { 2_000 } else { 2_443 })),
20858                    transport,
20859                )
20860                .await
20861                .unwrap();
20862            let protocol = ProtocolVersion::V22;
20863            phone.write_all(&register_bytes(protocol)).await.unwrap();
20864            let mut decoder = FrameDecoder::new();
20865            read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
20866            loop {
20867                if matches!(
20868                    events.recv().await,
20869                    Some(Event::Device(DeviceEvent {
20870                        session_generation: _,
20871                        event: DeviceEventKind::Registered(_),
20872                        ..
20873                    }))
20874                ) {
20875                    break;
20876                }
20877            }
20878            phone
20879                .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
20880                .await
20881                .unwrap();
20882            let response = read_until_message(&mut phone, &mut decoder, id::SERVER_RES)
20883                .await
20884                .into_iter()
20885                .find(|frame| frame.message_id == id::SERVER_RES)
20886                .unwrap();
20887            assert_eq!(
20888                ServerMessage::decode(response, protocol).unwrap(),
20889                ServerMessage::ServerResponse { servers: expected }
20890            );
20891        }
20892
20893        handle.shutdown().await.unwrap();
20894        task.await.unwrap().unwrap();
20895    }
20896
20897    #[tokio::test]
20898    async fn server_list_never_empties_when_routes_do_not_fit_the_session() {
20899        let config = ServerConfig {
20900            advertised_address: "192.0.2.99".parse().unwrap(),
20901            signaling_servers: vec![SignalingServerRoute {
20902                priority: 1,
20903                name: "secure-v6".into(),
20904                address: "2001:db8::20".parse().unwrap(),
20905                clear_port: None,
20906                secure_port: NonZeroU16::new(2443),
20907            }],
20908            ..ServerConfig::default()
20909        };
20910        let (server, handle, mut events, ingress) =
20911            Server::with_ingress(config, [definition()]).unwrap();
20912        let task = tokio::spawn(server.run());
20913
20914        for (offset, transport, local_address, expected_address) in [
20915            (
20916                0,
20917                StationTransport::Clear,
20918                "192.0.2.30".parse().unwrap(),
20919                "192.0.2.30".parse().unwrap(),
20920            ),
20921            (
20922                1,
20923                StationTransport::Secure,
20924                "192.0.2.31".parse().unwrap(),
20925                "192.0.2.31".parse().unwrap(),
20926            ),
20927            (
20928                2,
20929                StationTransport::Secure,
20930                "2001:db8::30".parse().unwrap(),
20931                "192.0.2.99".parse().unwrap(),
20932            ),
20933        ] {
20934            let local = SocketAddr::new(
20935                local_address,
20936                if transport == StationTransport::Clear {
20937                    2000
20938                } else {
20939                    2443
20940                },
20941            );
20942            let (server_stream, mut phone) = tokio::io::duplex(8_192);
20943            ingress
20944                .accept(
20945                    server_stream,
20946                    SocketAddr::from(([127, 0, 0, 1], 41_000 + offset)),
20947                    local,
20948                    transport,
20949                )
20950                .await
20951                .unwrap();
20952            let protocol = ProtocolVersion::V3;
20953            phone.write_all(&register_bytes(protocol)).await.unwrap();
20954            let mut decoder = FrameDecoder::new();
20955            read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
20956            while !matches!(
20957                events.recv().await,
20958                Some(Event::Device(DeviceEvent {
20959                    session_generation: _,
20960                    event: DeviceEventKind::Registered(_),
20961                    ..
20962                }))
20963            ) {}
20964            phone
20965                .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
20966                .await
20967                .unwrap();
20968            let response = read_until_message(&mut phone, &mut decoder, id::SERVER_RES)
20969                .await
20970                .into_iter()
20971                .find(|frame| frame.message_id == id::SERVER_RES)
20972                .unwrap();
20973            assert_eq!(
20974                ServerMessage::decode(response, protocol).unwrap(),
20975                ServerMessage::ServerResponse {
20976                    servers: vec![SignalingServerEndpoint {
20977                        name: "sccp-protocol".into(),
20978                        address: expected_address,
20979                        port: NonZeroU16::new(local.port()).unwrap(),
20980                    }]
20981                }
20982            );
20983        }
20984
20985        handle.shutdown().await.unwrap();
20986        task.await.unwrap().unwrap();
20987    }
20988
20989    #[tokio::test(start_paused = true)]
20990    async fn secondary_sessions_use_the_secondary_keepalive_deadline() {
20991        let config = ServerConfig {
20992            keepalive_seconds: 5,
20993            secondary_keepalive_seconds: 20,
20994            registration_tokens: RegistrationTokenPolicy {
20995                server_priority: 2,
20996                ..RegistrationTokenPolicy::default()
20997            },
20998            ..ServerConfig::default()
20999        };
21000        let (server, handle, mut events, ingress) =
21001            Server::with_ingress(config, [definition()]).unwrap();
21002        let task = tokio::spawn(server.run());
21003        let (server_stream, mut phone) = tokio::io::duplex(8_192);
21004        ingress
21005            .accept(
21006                server_stream,
21007                SocketAddr::from(([127, 0, 0, 1], 40_000)),
21008                SocketAddr::from(([127, 0, 0, 1], 2_000)),
21009                StationTransport::Clear,
21010            )
21011            .await
21012            .unwrap();
21013        let protocol = ProtocolVersion::V22;
21014        phone.write_all(&register_bytes(protocol)).await.unwrap();
21015        let mut decoder = FrameDecoder::new();
21016        let frames = read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21017        let acknowledgement = frames
21018            .into_iter()
21019            .find(|frame| frame.message_id == id::REGISTER_ACK)
21020            .unwrap();
21021        assert_eq!(
21022            ServerMessage::decode(acknowledgement, protocol).unwrap(),
21023            ServerMessage::RegisterAck {
21024                keepalive_seconds: 5,
21025                secondary_keepalive_seconds: 20,
21026                protocol,
21027                features: PhoneFeatures::empty(),
21028                date_template: Default::default(),
21029            }
21030        );
21031        assert!(matches!(
21032            events.recv().await,
21033            Some(Event::Device(DeviceEvent {
21034                session_generation: _,
21035                event: DeviceEventKind::Registered(_),
21036                ..
21037            }))
21038        ));
21039
21040        tokio::time::advance(Duration::from_secs(59)).await;
21041        tokio::task::yield_now().await;
21042        assert!(events.try_recv().is_err());
21043        tokio::time::advance(Duration::from_secs(2)).await;
21044        assert!(matches!(
21045            events.recv().await,
21046            Some(Event::Device(DeviceEvent {
21047                session_generation: _,
21048                event: DeviceEventKind::Disconnected {},
21049                ..
21050            }))
21051        ));
21052
21053        handle.shutdown().await.unwrap();
21054        task.await.unwrap().unwrap();
21055    }
21056
21057    #[derive(Debug)]
21058    struct RecordingSocketQos {
21059        applied: Arc<std::sync::Mutex<Vec<SignalingQos>>>,
21060        fail: bool,
21061    }
21062
21063    impl StationSocketQos for RecordingSocketQos {
21064        fn apply(&self, qos: SignalingQos) -> SocketQosReport {
21065            self.applied.lock().unwrap().push(qos);
21066            if self.fail {
21067                SocketQosReport::failed(
21068                    SocketQosMark::SocketPriority,
21069                    std::io::Error::new(std::io::ErrorKind::Unsupported, "test platform"),
21070                )
21071            } else {
21072                SocketQosReport::default()
21073            }
21074        }
21075    }
21076
21077    #[tokio::test]
21078    async fn registration_applies_device_socket_qos_without_making_failure_fatal() {
21079        let baseline = SignalingQos::new(8, 1);
21080        let device_policy = SignalingQos::new(26, 5);
21081        let mut station = definition();
21082        station.signaling_qos = Some(device_policy);
21083        let config = ServerConfig {
21084            signaling_qos: baseline,
21085            ..ServerConfig::default()
21086        };
21087        let (server, handle, mut events, ingress) =
21088            Server::with_ingress(config, [station]).unwrap();
21089        let task = tokio::spawn(server.run());
21090        let (server_stream, mut phone) = tokio::io::duplex(8_192);
21091        let applied = Arc::new(std::sync::Mutex::new(Vec::new()));
21092        ingress
21093            .accept_with_socket_qos(
21094                server_stream,
21095                SocketAddr::from(([127, 0, 0, 1], 40_000)),
21096                SocketAddr::from(([127, 0, 0, 1], 2_000)),
21097                StationTransport::Clear,
21098                RecordingSocketQos {
21099                    applied: Arc::clone(&applied),
21100                    fail: true,
21101                },
21102            )
21103            .await
21104            .unwrap();
21105        phone
21106            .write_all(&register_bytes(ProtocolVersion::V22))
21107            .await
21108            .unwrap();
21109
21110        let mut decoder = FrameDecoder::new();
21111        let frames = read_until_message(&mut phone, &mut decoder, id::REGISTER_ACK).await;
21112        assert!(
21113            frames
21114                .iter()
21115                .any(|frame| frame.message_id == id::REGISTER_ACK)
21116        );
21117        assert!(matches!(
21118            events.recv().await,
21119            Some(Event::Device(DeviceEvent {
21120                session_generation: _,
21121                event: DeviceEventKind::Registered(_),
21122                ..
21123            }))
21124        ));
21125        assert_eq!(*applied.lock().unwrap(), vec![baseline, device_policy]);
21126
21127        handle.shutdown().await.unwrap();
21128        task.await.unwrap().unwrap();
21129    }
21130
21131    #[tokio::test]
21132    async fn headset_and_accessory_changes_are_typed_and_duplicate_stable() {
21133        let config = ServerConfig {
21134            bind: "127.0.0.1:0".parse().unwrap(),
21135            advertised_address: Ipv4Addr::LOCALHOST,
21136            ..ServerConfig::default()
21137        };
21138        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21139        let address = server.local_addr().unwrap();
21140        let task = tokio::spawn(server.run());
21141        let mut phone = TcpStream::connect(address).await.unwrap();
21142        let mut decoder = FrameDecoder::new();
21143        let protocol = ProtocolVersion::V22;
21144
21145        phone.write_all(&register_bytes(protocol)).await.unwrap();
21146        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21147        assert!(matches!(
21148            events.recv().await,
21149            Some(Event::Device(DeviceEvent {
21150                session_generation: _,
21151                device_id: _,
21152                event: DeviceEventKind::Registered(_)
21153            }))
21154        ));
21155        phone
21156            .write_all(
21157                &ClientMessage::HeadsetStatus { enabled: true }
21158                    .encode(protocol)
21159                    .unwrap(),
21160            )
21161            .await
21162            .unwrap();
21163        assert!(matches!(
21164            events.recv().await,
21165            Some(Event::Device(DeviceEvent {
21166                session_generation: _,
21167                device_id: _,
21168                event: DeviceEventKind::HeadsetStatusChanged { enabled: true, .. }
21169            }))
21170        ));
21171        phone
21172            .write_all(
21173                &ClientMessage::MediaPathEvent {
21174                    path: crate::MediaPathId::Speaker,
21175                    event: crate::MediaPathEvent::On,
21176                }
21177                .encode(protocol)
21178                .unwrap(),
21179            )
21180            .await
21181            .unwrap();
21182        assert!(matches!(
21183            events.recv().await,
21184            Some(Event::Device(DeviceEvent {
21185                session_generation: _,
21186                device_id: _,
21187                event: DeviceEventKind::MediaPathChanged {
21188                    path: crate::MediaPathId::Speaker,
21189                    event: crate::MediaPathEvent::On,
21190                    ..
21191                }
21192            }))
21193        ));
21194        phone
21195            .write_all(
21196                &ClientMessage::MediaPathEvent {
21197                    path: crate::MediaPathId::Speaker,
21198                    event: crate::MediaPathEvent::On,
21199                }
21200                .encode(protocol)
21201                .unwrap(),
21202            )
21203            .await
21204            .unwrap();
21205        assert!(
21206            tokio::time::timeout(Duration::from_millis(50), events.recv())
21207                .await
21208                .is_err()
21209        );
21210
21211        handle.shutdown().await.unwrap();
21212        task.await.unwrap().unwrap();
21213    }
21214
21215    #[tokio::test]
21216    async fn unpaired_active_media_path_release_completes_on_hook_after_grace() {
21217        let config = ServerConfig {
21218            bind: "127.0.0.1:0".parse().unwrap(),
21219            advertised_address: Ipv4Addr::LOCALHOST,
21220            ..ServerConfig::default()
21221        };
21222        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21223        let address = server.local_addr().unwrap();
21224        let task = tokio::spawn(server.run());
21225        let mut phone = TcpStream::connect(address).await.unwrap();
21226        let mut decoder = FrameDecoder::new();
21227        let protocol = ProtocolVersion::V22;
21228        let device_id = DeviceId::new("SEP001122334455").unwrap();
21229        let call_id = CallId(7101);
21230
21231        phone.write_all(&register_bytes(protocol)).await.unwrap();
21232        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21233        assert!(matches!(
21234            events.recv().await,
21235            Some(Event::Device(DeviceEvent {
21236                event: DeviceEventKind::Registered(_),
21237                ..
21238            }))
21239        ));
21240        handle
21241            .send(Command::new(
21242                device_id.clone(),
21243                CommandAction::BeginCall {
21244                    line_instance: LineInstance::new(1),
21245                    call_id,
21246                    codec: Codec::Pcmu,
21247                },
21248            ))
21249            .await
21250            .unwrap();
21251        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21252        handle
21253            .send(Command::new(
21254                device_id,
21255                CommandAction::SetCallState {
21256                    call_id,
21257                    state: CallState::Connected,
21258                },
21259            ))
21260            .await
21261            .unwrap();
21262        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21263
21264        for event in [crate::MediaPathEvent::On, crate::MediaPathEvent::Off] {
21265            phone
21266                .write_all(
21267                    &ClientMessage::MediaPathEvent {
21268                        path: crate::MediaPathId::Speaker,
21269                        event,
21270                    }
21271                    .encode(protocol)
21272                    .unwrap(),
21273                )
21274                .await
21275                .unwrap();
21276            assert!(matches!(
21277                events.recv().await,
21278                Some(Event::Device(DeviceEvent {
21279                    event: DeviceEventKind::MediaPathChanged {
21280                        path: crate::MediaPathId::Speaker,
21281                        event: actual,
21282                    },
21283                    ..
21284                })) if actual == event
21285            ));
21286        }
21287        assert!(
21288            tokio::time::timeout(Duration::from_millis(100), events.recv())
21289                .await
21290                .is_err(),
21291            "media-path release bypassed its route-change grace period"
21292        );
21293        assert!(matches!(
21294            tokio::time::timeout(Duration::from_millis(300), events.recv()).await,
21295            Ok(Some(Event::Device(DeviceEvent {
21296                event: DeviceEventKind::OnHook {
21297                    call_id: ended,
21298                    line_instance: LineInstance(1),
21299                },
21300                ..
21301            }))) if ended == call_id
21302        ));
21303        let frames = read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
21304        assert!(frames.into_iter().any(|frame| matches!(
21305            ServerMessage::decode(frame, protocol),
21306            Ok(ServerMessage::CallState {
21307                state: CallState::OnHook,
21308                ..
21309            })
21310        )));
21311
21312        handle.shutdown().await.unwrap();
21313        task.await.unwrap().unwrap();
21314    }
21315
21316    #[tokio::test]
21317    async fn replacement_media_path_cancels_pending_on_hook_completion() {
21318        let config = ServerConfig {
21319            bind: "127.0.0.1:0".parse().unwrap(),
21320            advertised_address: Ipv4Addr::LOCALHOST,
21321            ..ServerConfig::default()
21322        };
21323        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21324        let address = server.local_addr().unwrap();
21325        let task = tokio::spawn(server.run());
21326        let mut phone = TcpStream::connect(address).await.unwrap();
21327        let mut decoder = FrameDecoder::new();
21328        let protocol = ProtocolVersion::V22;
21329        let device_id = DeviceId::new("SEP001122334455").unwrap();
21330        let call_id = CallId(7102);
21331
21332        phone.write_all(&register_bytes(protocol)).await.unwrap();
21333        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21334        assert!(matches!(
21335            events.recv().await,
21336            Some(Event::Device(DeviceEvent {
21337                event: DeviceEventKind::Registered(_),
21338                ..
21339            }))
21340        ));
21341        handle
21342            .send(Command::new(
21343                device_id.clone(),
21344                CommandAction::BeginCall {
21345                    line_instance: LineInstance::new(1),
21346                    call_id,
21347                    codec: Codec::Pcmu,
21348                },
21349            ))
21350            .await
21351            .unwrap();
21352        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21353        handle
21354            .send(Command::new(
21355                device_id,
21356                CommandAction::SetCallState {
21357                    call_id,
21358                    state: CallState::Connected,
21359                },
21360            ))
21361            .await
21362            .unwrap();
21363        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21364
21365        for (path, event) in [
21366            (crate::MediaPathId::Speaker, crate::MediaPathEvent::On),
21367            (crate::MediaPathId::Speaker, crate::MediaPathEvent::Off),
21368            (crate::MediaPathId::Headset, crate::MediaPathEvent::On),
21369        ] {
21370            phone
21371                .write_all(
21372                    &ClientMessage::MediaPathEvent { path, event }
21373                        .encode(protocol)
21374                        .unwrap(),
21375                )
21376                .await
21377                .unwrap();
21378            assert!(matches!(
21379                events.recv().await,
21380                Some(Event::Device(DeviceEvent {
21381                    event: DeviceEventKind::MediaPathChanged {
21382                        path: actual_path,
21383                        event: actual_event,
21384                    },
21385                    ..
21386                })) if actual_path == path && actual_event == event
21387            ));
21388        }
21389        assert!(
21390            tokio::time::timeout(Duration::from_millis(350), events.recv())
21391                .await
21392                .is_err(),
21393            "a replacement audio path was mistaken for terminal OnHook"
21394        );
21395        assert!(
21396            tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
21397                .await
21398                .is_err(),
21399            "route switching emitted terminal handset UI"
21400        );
21401
21402        handle.shutdown().await.unwrap();
21403        task.await.unwrap().unwrap();
21404    }
21405
21406    #[tokio::test]
21407    async fn ipv6_signaling_requires_extended_layouts_and_preserves_station_addresses() {
21408        let config = ServerConfig {
21409            bind: "[::1]:0".parse().unwrap(),
21410            ..ServerConfig::default()
21411        };
21412        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21413        let address = server.local_addr().unwrap();
21414        assert!(address.is_ipv6());
21415        let task = tokio::spawn(server.run());
21416
21417        let mut legacy = TcpStream::connect(address).await.unwrap();
21418        legacy
21419            .write_all(&register_bytes(ProtocolVersion::V3))
21420            .await
21421            .unwrap();
21422        let mut legacy_decoder = FrameDecoder::new();
21423        let rejection = read_until_message(&mut legacy, &mut legacy_decoder, id::REGISTER_REJECT)
21424            .await
21425            .into_iter()
21426            .find(|frame| frame.message_id == id::REGISTER_REJECT)
21427            .unwrap();
21428        assert!(matches!(
21429            ServerMessage::decode(rejection, ProtocolVersion::V3).unwrap(),
21430            ServerMessage::RegisterReject { reason } if reason == "IPv6 requires protocol v17"
21431        ));
21432        assert!(
21433            tokio::time::timeout(Duration::from_millis(25), events.recv())
21434                .await
21435                .is_err()
21436        );
21437
21438        let protocol = ProtocolVersion::V22;
21439        let reported_ipv6: Ipv6Addr = "2001:db8::42".parse().unwrap();
21440        let registration = ClientMessage::Register(RegistrationMessage {
21441            device_id: DeviceId::new("SEP001122334455").unwrap(),
21442            reported_address: None,
21443            reported_ipv6_address: Some(reported_ipv6),
21444            device_type: DeviceType::Cisco7962,
21445            advertised_protocol: protocol.wire(),
21446            features: PhoneFeatures::empty(),
21447            firmware: "test-load".into(),
21448            configuration_version_stamp: crate::message::BoundedBytes::default(),
21449            wire: None,
21450        });
21451        let mut phone = TcpStream::connect(address).await.unwrap();
21452        phone
21453            .write_all(&registration.encode(protocol).unwrap())
21454            .await
21455            .unwrap();
21456        let mut decoder = FrameDecoder::new();
21457        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21458        assert!(matches!(
21459            events.recv().await,
21460            Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Registered(DeviceRegistration {
21461                peer,
21462                transport: StationTransport::Clear,
21463                reported_address: None,
21464                reported_ipv6_address: Some(reported),
21465                ..
21466            }),
21467                ..
21468            })) if peer.is_ipv6() && reported == reported_ipv6
21469        ));
21470
21471        phone
21472            .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
21473            .await
21474            .unwrap();
21475        let response = read_until_message(&mut phone, &mut decoder, id::SERVER_RES)
21476            .await
21477            .into_iter()
21478            .find(|frame| frame.message_id == id::SERVER_RES)
21479            .unwrap();
21480        assert_eq!(
21481            ServerMessage::decode(response, protocol).unwrap(),
21482            ServerMessage::ServerResponse {
21483                servers: vec![SignalingServerEndpoint {
21484                    name: "sccp-protocol".into(),
21485                    address: address.ip(),
21486                    port: NonZeroU16::new(address.port()).unwrap(),
21487                }],
21488            }
21489        );
21490
21491        handle.shutdown().await.unwrap();
21492        task.await.unwrap().unwrap();
21493    }
21494
21495    #[test]
21496    fn status_messages_preserve_persistence_timeout_priority_and_phone_family() {
21497        let mut persistent = false;
21498        assert!(matches!(
21499            status_message_frames(
21500                HandsetStatusMessage::Display {
21501                    text: "Persistent".into(),
21502                    timeout_seconds: 0,
21503                    priority: None,
21504                },
21505                DeviceType::Cisco7960,
21506                &mut persistent,
21507            )
21508            .as_slice(),
21509            [ServerMessage::DisplayPrompt {
21510                timeout_seconds: 0,
21511                line_instance: 0,
21512                call_reference: 0,
21513                text,
21514            }] if text == "Persistent"
21515        ));
21516        assert!(persistent);
21517        assert_eq!(
21518            status_message_frames(
21519                HandsetStatusMessage::Clear { priority: None },
21520                DeviceType::Cisco7960,
21521                &mut persistent,
21522            ),
21523            [
21524                ServerMessage::ClearPrompt {
21525                    line_instance: 0,
21526                    call_reference: 0,
21527                },
21528                ServerMessage::ClearPriorityNotify {
21529                    priority: NotificationPriority::Timed,
21530                },
21531            ]
21532        );
21533        assert!(!persistent);
21534
21535        assert!(matches!(
21536            status_message_frames(
21537                HandsetStatusMessage::Display {
21538                    text: "Timed".into(),
21539                    timeout_seconds: 9,
21540                    priority: None,
21541                },
21542                DeviceType::Cisco7960,
21543                &mut persistent,
21544            )
21545            .as_slice(),
21546            [ServerMessage::DisplayPriorityNotify {
21547                timeout_seconds: 9,
21548                priority: NotificationPriority::Timed,
21549                text,
21550            }] if text == "Timed"
21551        ));
21552        assert!(matches!(
21553            status_message_frames(
21554                HandsetStatusMessage::Display {
21555                    text: "Timed".into(),
21556                    timeout_seconds: 9,
21557                    priority: None,
21558                },
21559                DeviceType::Cisco6945,
21560                &mut persistent,
21561            )
21562            .as_slice(),
21563            [ServerMessage::DisplayPrompt {
21564                timeout_seconds: 9,
21565                line_instance: 0,
21566                call_reference: 0,
21567                ..
21568            }]
21569        ));
21570    }
21571
21572    #[test]
21573    fn every_status_priority_round_trips_through_typed_frames() {
21574        for priority in NotificationPriority::ALL_KNOWN {
21575            let mut persistent = false;
21576            assert_eq!(
21577                status_message_frames(
21578                    HandsetStatusMessage::Display {
21579                        text: "Priority".into(),
21580                        timeout_seconds: 5,
21581                        priority: Some(*priority),
21582                    },
21583                    DeviceType::Cisco7960,
21584                    &mut persistent,
21585                ),
21586                [ServerMessage::DisplayPriorityNotify {
21587                    timeout_seconds: 5,
21588                    priority: *priority,
21589                    text: "Priority".into(),
21590                }]
21591            );
21592            assert_eq!(
21593                status_message_frames(
21594                    HandsetStatusMessage::Clear {
21595                        priority: Some(*priority),
21596                    },
21597                    DeviceType::Cisco7960,
21598                    &mut persistent,
21599                ),
21600                [ServerMessage::ClearPriorityNotify {
21601                    priority: *priority,
21602                }]
21603            );
21604        }
21605    }
21606
21607    #[test]
21608    fn text_service_delivery_types_priority_and_segments_only_modern_documents() {
21609        let short = CiscoIpPhoneText::new("Sender", "Read", "Hello & goodbye").unwrap();
21610        let legacy = text_service_messages(
21611            LineInstance::new(3),
21612            CallReference::new(71),
21613            TransactionId::new(99),
21614            PhoneServicePriority::NORMAL,
21615            &short,
21616            ProtocolVersion::V17,
21617        )
21618        .unwrap();
21619        assert!(matches!(
21620            legacy.as_slice(),
21621            [ServerMessage::UserToDeviceDataV1(message)]
21622                if message.application_id == PHONE_TEXT_APPLICATION_ID
21623                    && message.line_instance == 3
21624                    && message.call_reference == 71
21625                    && message.transaction_id == 99
21626                    && message.sequence_flag == 2
21627                    && message.display_priority == 1
21628                    && message.conference_id == 71
21629                    && message.application_instance_id == PHONE_TEXT_APPLICATION_ID
21630                    && message.routing == 1
21631                    && CiscoIpPhoneText::from_xml(&message.data).unwrap() == short
21632        ));
21633
21634        let legacy_oversized = CiscoIpPhoneText::new(
21635            "Sender",
21636            "Read",
21637            "x".repeat(PHONE_TEXT_LEGACY_MAX_CHARS + 1),
21638        )
21639        .unwrap();
21640        assert!(matches!(
21641            text_service_messages(
21642                LineInstance::new(0),
21643                CallReference::new(1),
21644                TransactionId::new(1),
21645                PhoneServicePriority::LOW,
21646                &legacy_oversized,
21647                ProtocolVersion::V17,
21648            ),
21649            Err(ServerError::PhoneXml(PhoneXmlError::InvalidField {
21650                field: "legacy phone text body",
21651                ..
21652            }))
21653        ));
21654
21655        let modern = CiscoIpPhoneText::new("Sender", "Read", "&".repeat(3_000)).unwrap();
21656        let messages = text_service_messages(
21657            LineInstance::new(0),
21658            CallReference::new(1),
21659            TransactionId::new(100),
21660            PhoneServicePriority::HIGH,
21661            &modern,
21662            ProtocolVersion::V18,
21663        )
21664        .unwrap();
21665        assert!(messages.len() > 2);
21666        let mut reassembled = Vec::new();
21667        for (index, message) in messages.iter().enumerate() {
21668            let ServerMessage::UserToDeviceDataV1(message) = message else {
21669                panic!("expected text application-data segment");
21670            };
21671            assert!(message.data.len() <= 2_000);
21672            assert_eq!(message.display_priority, 2);
21673            assert_eq!(
21674                message.sequence_flag,
21675                if index == 0 {
21676                    0
21677                } else if index + 1 == messages.len() {
21678                    2
21679                } else {
21680                    1
21681                }
21682            );
21683            reassembled.extend_from_slice(&message.data);
21684        }
21685        assert_eq!(CiscoIpPhoneText::from_xml(&reassembled).unwrap(), modern);
21686    }
21687
21688    #[tokio::test]
21689    async fn registered_phone_receives_typed_text_service_controls_and_priority() {
21690        let config = ServerConfig {
21691            bind: "127.0.0.1:0".parse().unwrap(),
21692            advertised_address: Ipv4Addr::LOCALHOST,
21693            ..ServerConfig::default()
21694        };
21695        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21696        let address = server.local_addr().unwrap();
21697        let task = tokio::spawn(server.run());
21698        let mut phone = TcpStream::connect(address).await.unwrap();
21699        let mut decoder = FrameDecoder::new();
21700        let protocol = ProtocolVersion::V22;
21701        let device_id = DeviceId::new("SEP001122334455").unwrap();
21702
21703        phone.write_all(&register_bytes(protocol)).await.unwrap();
21704        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21705        assert!(matches!(
21706            events.recv().await,
21707            Some(Event::Device(DeviceEvent {
21708                session_generation: _,
21709                device_id: _,
21710                event: DeviceEventKind::Registered(_)
21711            }))
21712        ));
21713
21714        let mut expected =
21715            CiscoIpPhoneText::new("Dispatch", "Read", "Café <ready> & waiting").unwrap();
21716        expected.soft_keys.push(CiscoIpPhoneSoftKeyItem {
21717            name: Some("Refresh".into()),
21718            position: PhoneSoftKeyPosition::new(1).unwrap(),
21719            url: Some("https://pbx.example/text?id=7&view=full".into()),
21720            url_down: None,
21721        });
21722        handle
21723            .send(Command::new(
21724                device_id.clone(),
21725                CommandAction::ShowTextService {
21726                    line_instance: LineInstance::new(2),
21727                    call_reference: CallReference::new(42),
21728                    transaction_id: TransactionId::new(73),
21729                    priority: PhoneServicePriority::HIGH,
21730                    document: expected.clone(),
21731                },
21732            ))
21733            .await
21734            .unwrap();
21735        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
21736        let message = frames
21737            .into_iter()
21738            .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
21739            .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
21740            .unwrap();
21741        let ServerMessage::UserToDeviceDataV1(message) = message else {
21742            panic!("expected text application data");
21743        };
21744        assert_eq!(message.application_id, PHONE_TEXT_APPLICATION_ID);
21745        assert_eq!(message.line_instance, 2);
21746        assert_eq!(message.call_reference, 42);
21747        assert_eq!(message.transaction_id, 73);
21748        assert_eq!(message.sequence_flag, 2);
21749        assert_eq!(message.display_priority, 2);
21750        assert_eq!(CiscoIpPhoneText::from_xml(&message.data).unwrap(), expected);
21751
21752        handle.shutdown().await.unwrap();
21753        task.await.unwrap().unwrap();
21754    }
21755
21756    #[test]
21757    fn input_service_delivery_preserves_typed_fields_and_modern_segmentation() {
21758        let short = CiscoIpPhoneInput::new(
21759            "Invite",
21760            "Enter number",
21761            "conference/44/invite",
21762            vec![CiscoIpPhoneInputItem {
21763                display_name: Some("Number".into()),
21764                parameter: PhoneInputParameterName::new("NUMBER").unwrap(),
21765                flags: PhoneInputFlags::Telephone,
21766                default_value: Some("5550100".into()),
21767            }],
21768        )
21769        .unwrap();
21770        let legacy = input_service_messages(
21771            LineInstance::new(3),
21772            CallReference::new(71),
21773            ApplicationId::new(9_092),
21774            TransactionId::new(99),
21775            PhoneServicePriority::NORMAL,
21776            &short,
21777            ProtocolVersion::V17,
21778        )
21779        .unwrap();
21780        assert!(matches!(
21781            legacy.as_slice(),
21782            [ServerMessage::UserToDeviceDataV1(message)]
21783                if message.application_id == 9_092
21784                    && message.line_instance == 3
21785                    && message.call_reference == 71
21786                    && message.transaction_id == 99
21787                    && message.sequence_flag == 2
21788                    && message.display_priority == 1
21789                    && message.conference_id == 71
21790                    && message.application_instance_id == 9_092
21791                    && message.routing == 1
21792                    && CiscoIpPhoneInput::from_xml(&message.data).unwrap() == short
21793        ));
21794
21795        let mut large = short;
21796        large.key_items = (0..32)
21797            .map(|index| CiscoIpPhoneKeyItem {
21798                key: PhoneXmlKey::NavBack,
21799                url: Some(format!("{}-{index:02}", "x".repeat(252))),
21800                url_down: Some(format!("{}-{index:02}", "y".repeat(252))),
21801            })
21802            .collect();
21803        assert!(matches!(
21804            input_service_messages(
21805                LineInstance::new(3),
21806                CallReference::new(71),
21807                ApplicationId::new(9_092),
21808                TransactionId::new(100),
21809                PhoneServicePriority::HIGH,
21810                &large,
21811                ProtocolVersion::V17,
21812            ),
21813            Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
21814                maximum: 2_000,
21815                ..
21816            }))
21817        ));
21818        let messages = input_service_messages(
21819            LineInstance::new(3),
21820            CallReference::new(71),
21821            ApplicationId::new(9_092),
21822            TransactionId::new(100),
21823            PhoneServicePriority::HIGH,
21824            &large,
21825            ProtocolVersion::V18,
21826        )
21827        .unwrap();
21828        assert!(messages.len() > 2);
21829        let mut reassembled = Vec::new();
21830        for (index, message) in messages.iter().enumerate() {
21831            let ServerMessage::UserToDeviceDataV1(message) = message else {
21832                panic!("expected input application-data segment");
21833            };
21834            assert!(message.data.len() <= 2_000);
21835            assert_eq!(message.application_id, 9_092);
21836            assert_eq!(message.display_priority, 2);
21837            assert_eq!(
21838                message.sequence_flag,
21839                if index == 0 {
21840                    0
21841                } else if index + 1 == messages.len() {
21842                    2
21843                } else {
21844                    1
21845                }
21846            );
21847            reassembled.extend_from_slice(&message.data);
21848        }
21849        assert_eq!(CiscoIpPhoneInput::from_xml(&reassembled).unwrap(), large);
21850    }
21851
21852    #[tokio::test]
21853    async fn registered_phone_receives_typed_input_and_returns_ordered_submission() {
21854        let config = ServerConfig {
21855            bind: "127.0.0.1:0".parse().unwrap(),
21856            advertised_address: Ipv4Addr::LOCALHOST,
21857            ..ServerConfig::default()
21858        };
21859        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21860        let address = server.local_addr().unwrap();
21861        let task = tokio::spawn(server.run());
21862        let mut phone = TcpStream::connect(address).await.unwrap();
21863        let mut decoder = FrameDecoder::new();
21864        let protocol = ProtocolVersion::V22;
21865        let device_id = DeviceId::new("SEP001122334455").unwrap();
21866
21867        phone.write_all(&register_bytes(protocol)).await.unwrap();
21868        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21869        assert!(matches!(
21870            events.recv().await,
21871            Some(Event::Device(DeviceEvent {
21872                session_generation: _,
21873                device_id: _,
21874                event: DeviceEventKind::Registered(_)
21875            }))
21876        ));
21877
21878        let mut expected = CiscoIpPhoneInput::new(
21879            "Invite <guest>",
21880            "Enter details",
21881            "conference/44/invite",
21882            vec![
21883                CiscoIpPhoneInputItem {
21884                    display_name: Some("Number".into()),
21885                    parameter: PhoneInputParameterName::new("NUMBER").unwrap(),
21886                    flags: PhoneInputFlags::Telephone,
21887                    default_value: None,
21888                },
21889                CiscoIpPhoneInputItem {
21890                    display_name: Some("Name".into()),
21891                    parameter: PhoneInputParameterName::new("NAME").unwrap(),
21892                    flags: PhoneInputFlags::Alphabetic,
21893                    default_value: Some("François".into()),
21894                },
21895            ],
21896        )
21897        .unwrap();
21898        expected.soft_keys.push(CiscoIpPhoneSoftKeyItem {
21899            name: Some("Submit".into()),
21900            position: PhoneSoftKeyPosition::new(1).unwrap(),
21901            url: Some("SoftKey:Submit".into()),
21902            url_down: None,
21903        });
21904        handle
21905            .send(Command::new(
21906                device_id.clone(),
21907                CommandAction::ShowInputService {
21908                    line_instance: LineInstance::new(2),
21909                    call_reference: CallReference::new(42),
21910                    application_id: ApplicationId::new(9_092),
21911                    transaction_id: TransactionId::new(73),
21912                    priority: PhoneServicePriority::HIGH,
21913                    document: expected.clone(),
21914                },
21915            ))
21916            .await
21917            .unwrap();
21918        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
21919        let message = frames
21920            .into_iter()
21921            .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
21922            .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
21923            .unwrap();
21924        let ServerMessage::UserToDeviceDataV1(message) = message else {
21925            panic!("expected input application data");
21926        };
21927        assert_eq!(message.application_id, 9_092);
21928        assert_eq!(message.line_instance, 2);
21929        assert_eq!(message.call_reference, 42);
21930        assert_eq!(message.transaction_id, 73);
21931        assert_eq!(message.sequence_flag, 2);
21932        assert_eq!(message.display_priority, 2);
21933        assert_eq!(
21934            CiscoIpPhoneInput::from_xml(&message.data).unwrap(),
21935            expected
21936        );
21937
21938        phone
21939            .write_all(
21940                &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
21941                    application_id: 9_092,
21942                    line_instance: 2,
21943                    call_reference: 42,
21944                    transaction_id: 73,
21945                    sequence_flag: 2,
21946                    display_priority: 2,
21947                    conference_id: 42,
21948                    application_instance_id: 9_092,
21949                    routing: 1,
21950                    data: b"conference/44/invite?NUMBER=555%2A12&NAME=Fran%C3%A7ois".to_vec(),
21951                })
21952                .encode(protocol)
21953                .unwrap(),
21954            )
21955            .await
21956            .unwrap();
21957        let Some(Event::Device(DeviceEvent {
21958            session_generation: _,
21959            device_id: _,
21960            event: DeviceEventKind::PhoneServiceResponse { response, .. },
21961        })) = events.recv().await
21962        else {
21963            panic!("expected typed input submission");
21964        };
21965        assert_eq!(response.routing.application_id, ApplicationId::new(9_092));
21966        assert_eq!(response.routing.line_instance, LineInstance::new(2));
21967        assert_eq!(response.routing.call_reference, CallReference::new(42));
21968        assert_eq!(response.routing.transaction_id, TransactionId::new(73));
21969        let PhoneServicePayload::Submission(submission) = response.payload else {
21970            panic!("expected typed input submission payload");
21971        };
21972        assert_eq!(submission.route, ["conference", "44", "invite"]);
21973        assert_eq!(
21974            submission.values_named("NUMBER").collect::<Vec<_>>(),
21975            ["555*12"]
21976        );
21977        assert_eq!(
21978            submission.values_named("NAME").collect::<Vec<_>>(),
21979            ["François"]
21980        );
21981
21982        handle.shutdown().await.unwrap();
21983        task.await.unwrap().unwrap();
21984    }
21985
21986    #[test]
21987    fn execute_action_delivery_preserves_envelope_order_and_protocol_bounds() {
21988        let short = CiscoIpPhoneExecute::new(vec![
21989            CiscoIpPhoneExecuteItem::with_priority(
21990                "Key:Directories?view=all&side=west",
21991                PhoneExecutePriority::LOW,
21992            )
21993            .unwrap(),
21994            CiscoIpPhoneExecuteItem::new("Application:PlacedCalls").unwrap(),
21995        ])
21996        .unwrap();
21997        let legacy = execute_phone_action_messages(
21998            LineInstance::new(3),
21999            CallReference::new(71),
22000            ApplicationId::new(9_093),
22001            TransactionId::new(99),
22002            PhoneServicePriority::NORMAL,
22003            &short,
22004            ProtocolVersion::V17,
22005        )
22006        .unwrap();
22007        assert!(matches!(
22008            legacy.as_slice(),
22009            [ServerMessage::UserToDeviceDataV1(message)]
22010                if message.application_id == 9_093
22011                    && message.line_instance == 3
22012                    && message.call_reference == 71
22013                    && message.transaction_id == 99
22014                    && message.sequence_flag == 2
22015                    && message.display_priority == 1
22016                    && message.routing == 1
22017                    && CiscoIpPhoneExecute::from_xml(&message.data).unwrap() == short
22018        ));
22019
22020        let large = CiscoIpPhoneExecute::new(
22021            (0..PHONE_EXECUTE_MAX_ITEMS)
22022                .map(|_| {
22023                    CiscoIpPhoneExecuteItem::with_priority(
22024                        "\"".repeat(256),
22025                        PhoneExecutePriority::HIGH,
22026                    )
22027                    .unwrap()
22028                })
22029                .collect(),
22030        )
22031        .unwrap();
22032        assert!(matches!(
22033            execute_phone_action_messages(
22034                LineInstance::new(3),
22035                CallReference::new(71),
22036                ApplicationId::new(9_093),
22037                TransactionId::new(100),
22038                PhoneServicePriority::HIGH,
22039                &large,
22040                ProtocolVersion::V17,
22041            ),
22042            Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
22043                maximum: 2_000,
22044                ..
22045            }))
22046        ));
22047        let messages = execute_phone_action_messages(
22048            LineInstance::new(3),
22049            CallReference::new(71),
22050            ApplicationId::new(9_093),
22051            TransactionId::new(100),
22052            PhoneServicePriority::HIGH,
22053            &large,
22054            ProtocolVersion::V18,
22055        )
22056        .unwrap();
22057        assert!(messages.len() > 2);
22058        let mut reassembled = Vec::new();
22059        for (index, message) in messages.iter().enumerate() {
22060            let ServerMessage::UserToDeviceDataV1(message) = message else {
22061                panic!("expected execute application-data segment");
22062            };
22063            assert!(message.data.len() <= 2_000);
22064            assert_eq!(message.display_priority, 2);
22065            assert_eq!(
22066                message.sequence_flag,
22067                if index == 0 {
22068                    0
22069                } else if index + 1 == messages.len() {
22070                    2
22071                } else {
22072                    1
22073                }
22074            );
22075            reassembled.extend_from_slice(&message.data);
22076        }
22077        assert_eq!(CiscoIpPhoneExecute::from_xml(&reassembled).unwrap(), large);
22078    }
22079
22080    #[test]
22081    fn image_service_delivery_preserves_family_envelope_and_protocol_bounds() {
22082        let short = PhoneImageDocument::ImageFile(CiscoIpPhoneImageFile {
22083            keypad_target: None,
22084            application_id: Some("maps".into()),
22085            on_focus_lost: None,
22086            on_focus_gained: None,
22087            on_minimized: None,
22088            on_closed: Some("Notify:maps/closed".into()),
22089            title: Some("Floor map".into()),
22090            prompt: Some("Inspect".into()),
22091            soft_keys: Vec::new(),
22092            key_items: Vec::new(),
22093            location_x: Some(-1),
22094            location_y: Some(167),
22095            url: PhoneImageUrl::new("https://pbx.example/map.png?floor=2&site=east").unwrap(),
22096        });
22097        let legacy = image_service_messages(
22098            LineInstance::new(3),
22099            CallReference::new(71),
22100            ApplicationId::new(9_095),
22101            TransactionId::new(101),
22102            PhoneServicePriority::NORMAL,
22103            &short,
22104            ProtocolVersion::V17,
22105        )
22106        .unwrap();
22107        assert!(matches!(
22108            legacy.as_slice(),
22109            [ServerMessage::UserToDeviceDataV1(message)]
22110                if message.application_id == 9_095
22111                    && message.line_instance == 3
22112                    && message.call_reference == 71
22113                    && message.transaction_id == 101
22114                    && message.sequence_flag == 2
22115                    && message.display_priority == 1
22116                    && message.routing == 1
22117                    && PhoneImageDocument::from_xml(&message.data).unwrap() == short
22118        ));
22119
22120        let large = PhoneImageDocument::GraphicFileMenu(CiscoIpPhoneGraphicFileMenu {
22121            keypad_target: None,
22122            application_id: Some("map-regions".into()),
22123            on_focus_lost: None,
22124            on_focus_gained: None,
22125            on_minimized: None,
22126            on_closed: None,
22127            title: Some("Map regions".into()),
22128            prompt: Some("Choose".into()),
22129            soft_keys: Vec::new(),
22130            key_items: Vec::new(),
22131            location_x: Some(0),
22132            location_y: Some(0),
22133            url: PhoneImageUrl::new("https://pbx.example/map.png").unwrap(),
22134            items: (0..crate::phone::xml::PHONE_GRAPHIC_FILE_MENU_MAX_ITEMS)
22135                .map(|index| CiscoIpPhoneTouchAreaMenuItem {
22136                    name: Some(format!("Region {index}")),
22137                    url: Some("x".repeat(256)),
22138                    touch_area: Some(PhoneTouchArea {
22139                        x1: index as u16,
22140                        y1: index as u16,
22141                        x2: index as u16 + 1,
22142                        y2: index as u16 + 1,
22143                    }),
22144                })
22145                .collect(),
22146        });
22147        assert!(matches!(
22148            image_service_messages(
22149                LineInstance::new(3),
22150                CallReference::new(71),
22151                ApplicationId::new(9_095),
22152                TransactionId::new(102),
22153                PhoneServicePriority::HIGH,
22154                &large,
22155                ProtocolVersion::V17,
22156            ),
22157            Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
22158                maximum: 2_000,
22159                ..
22160            }))
22161        ));
22162        let messages = image_service_messages(
22163            LineInstance::new(3),
22164            CallReference::new(71),
22165            ApplicationId::new(9_095),
22166            TransactionId::new(102),
22167            PhoneServicePriority::HIGH,
22168            &large,
22169            ProtocolVersion::V18,
22170        )
22171        .unwrap();
22172        assert!(messages.len() > 2);
22173        let mut reassembled = Vec::new();
22174        for (index, message) in messages.iter().enumerate() {
22175            let ServerMessage::UserToDeviceDataV1(message) = message else {
22176                panic!("expected image application-data segment");
22177            };
22178            assert!(message.data.len() <= 2_000);
22179            assert_eq!(message.display_priority, 2);
22180            assert_eq!(
22181                message.sequence_flag,
22182                if index == 0 {
22183                    0
22184                } else if index + 1 == messages.len() {
22185                    2
22186                } else {
22187                    1
22188                }
22189            );
22190            reassembled.extend_from_slice(&message.data);
22191        }
22192        assert_eq!(PhoneImageDocument::from_xml(&reassembled).unwrap(), large);
22193    }
22194
22195    #[test]
22196    fn background_control_delivery_uses_reserved_application_envelope_and_typed_xml() {
22197        let set = CiscoIpPhoneSetBackground::new(
22198            PhoneBackgroundHttpUrl::new("http://pbx.example/background.png?site=east").unwrap(),
22199            PhoneBackgroundHttpUrl::new("http://pbx.example/background-thumb.png").unwrap(),
22200        );
22201        let message = background_control_message(
22202            TransactionId::new(107),
22203            &PhoneBackgroundControlDocument::Set(set.clone()),
22204        )
22205        .unwrap();
22206        assert!(matches!(
22207            message,
22208            ServerMessage::UserToDeviceDataV1(message)
22209                if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22210                    && message.line_instance == 0
22211                    && message.call_reference == 0
22212                    && message.transaction_id == 107
22213                    && message.sequence_flag == 2
22214                    && message.display_priority == 0
22215                    && message.conference_id == 0
22216                    && message.application_instance_id == PHONE_BACKGROUND_APPLICATION_ID
22217                    && message.routing == 1
22218                    && CiscoIpPhoneSetBackground::from_xml(&message.data).unwrap() == set
22219        ));
22220
22221        let preview = CiscoIpPhoneSetBackgroundPreview::new(
22222            PhoneBackgroundHttpUrl::new("http://pbx.example/background.png").unwrap(),
22223        );
22224        let message = background_control_message(
22225            TransactionId::new(108),
22226            &PhoneBackgroundControlDocument::Preview(preview.clone()),
22227        )
22228        .unwrap();
22229        assert!(matches!(
22230            message,
22231            ServerMessage::UserToDeviceDataV1(message)
22232                if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22233                    && message.transaction_id == 108
22234                    && CiscoIpPhoneSetBackgroundPreview::from_xml(&message.data).unwrap() == preview
22235        ));
22236    }
22237
22238    #[test]
22239    fn ringtone_control_delivery_uses_reserved_application_envelope_and_typed_xml() {
22240        let document = CiscoIpPhoneSetRingTone::new(
22241            PhoneRingtoneUrl::new("http://pbx.example/ringtones/Classic.raw?locale=sv").unwrap(),
22242        );
22243        let message = ringtone_control_message(TransactionId::new(111), &document).unwrap();
22244        assert!(matches!(
22245            message,
22246            ServerMessage::UserToDeviceDataV1(message)
22247                if message.application_id == PHONE_RINGTONE_APPLICATION_ID
22248                    && message.line_instance == 0
22249                    && message.call_reference == 0
22250                    && message.transaction_id == 111
22251                    && message.sequence_flag == 2
22252                    && message.display_priority == 0
22253                    && message.conference_id == 0
22254                    && message.application_instance_id == PHONE_RINGTONE_APPLICATION_ID
22255                    && message.routing == 1
22256                    && CiscoIpPhoneSetRingTone::from_xml(&message.data).unwrap() == document
22257        ));
22258    }
22259
22260    #[tokio::test]
22261    async fn registered_phone_receives_typed_background_selection_and_preview_commands() {
22262        let config = ServerConfig {
22263            bind: "127.0.0.1:0".parse().unwrap(),
22264            advertised_address: Ipv4Addr::LOCALHOST,
22265            ..ServerConfig::default()
22266        };
22267        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22268        let address = server.local_addr().unwrap();
22269        let task = tokio::spawn(server.run());
22270        let mut phone = TcpStream::connect(address).await.unwrap();
22271        let mut decoder = FrameDecoder::new();
22272        let protocol = ProtocolVersion::V22;
22273        let device_id = DeviceId::new("SEP001122334455").unwrap();
22274
22275        phone.write_all(&register_bytes(protocol)).await.unwrap();
22276        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22277        assert!(matches!(
22278            events.recv().await,
22279            Some(Event::Device(DeviceEvent {
22280                session_generation: _,
22281                device_id: _,
22282                event: DeviceEventKind::Registered(_)
22283            }))
22284        ));
22285
22286        let set = CiscoIpPhoneSetBackground::new(
22287            PhoneBackgroundHttpUrl::new("http://pbx.example/background.png").unwrap(),
22288            PhoneBackgroundHttpUrl::new("http://pbx.example/background-thumb.png").unwrap(),
22289        );
22290        handle
22291            .send(Command::new(
22292                device_id.clone(),
22293                CommandAction::SetBackgroundImage {
22294                    transaction_id: TransactionId::new(109),
22295                    document: set.clone(),
22296                },
22297            ))
22298            .await
22299            .unwrap();
22300        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22301        assert!(frames.into_iter().any(|frame| matches!(
22302            ServerMessage::decode(frame, protocol),
22303            Ok(ServerMessage::UserToDeviceDataV1(message))
22304                if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22305                    && message.transaction_id == 109
22306                    && CiscoIpPhoneSetBackground::from_xml(&message.data).unwrap() == set
22307        )));
22308
22309        let preview = CiscoIpPhoneSetBackgroundPreview::new(
22310            PhoneBackgroundHttpUrl::new("http://pbx.example/background.png?preview=1").unwrap(),
22311        );
22312        handle
22313            .send(Command::new(
22314                device_id,
22315                CommandAction::PreviewBackgroundImage {
22316                    transaction_id: TransactionId::new(110),
22317                    document: preview.clone(),
22318                },
22319            ))
22320            .await
22321            .unwrap();
22322        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22323        assert!(frames.into_iter().any(|frame| matches!(
22324            ServerMessage::decode(frame, protocol),
22325            Ok(ServerMessage::UserToDeviceDataV1(message))
22326                if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22327                    && message.transaction_id == 110
22328                    && CiscoIpPhoneSetBackgroundPreview::from_xml(&message.data).unwrap() == preview
22329        )));
22330
22331        handle.shutdown().await.unwrap();
22332        task.await.unwrap().unwrap();
22333    }
22334
22335    #[tokio::test]
22336    async fn registered_phone_receives_typed_ringtone_command() {
22337        let config = ServerConfig {
22338            bind: "127.0.0.1:0".parse().unwrap(),
22339            advertised_address: Ipv4Addr::LOCALHOST,
22340            ..ServerConfig::default()
22341        };
22342        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22343        let address = server.local_addr().unwrap();
22344        let task = tokio::spawn(server.run());
22345        let mut phone = TcpStream::connect(address).await.unwrap();
22346        let mut decoder = FrameDecoder::new();
22347        let protocol = ProtocolVersion::V22;
22348        let device_id = DeviceId::new("SEP001122334455").unwrap();
22349
22350        phone.write_all(&register_bytes(protocol)).await.unwrap();
22351        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22352        assert!(matches!(
22353            events.recv().await,
22354            Some(Event::Device(DeviceEvent {
22355                session_generation: _,
22356                device_id: _,
22357                event: DeviceEventKind::Registered(_)
22358            }))
22359        ));
22360
22361        let document = CiscoIpPhoneSetRingTone::new(
22362            PhoneRingtoneUrl::new("http://pbx.example/ringtones/Classic.raw?locale=sv").unwrap(),
22363        );
22364        handle
22365            .send(Command::new(
22366                device_id,
22367                CommandAction::SetRingtone {
22368                    transaction_id: TransactionId::new(112),
22369                    document: document.clone(),
22370                },
22371            ))
22372            .await
22373            .unwrap();
22374        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22375        assert!(frames.into_iter().any(|frame| matches!(
22376            ServerMessage::decode(frame, protocol),
22377            Ok(ServerMessage::UserToDeviceDataV1(message))
22378                if message.application_id == PHONE_RINGTONE_APPLICATION_ID
22379                    && message.line_instance == 0
22380                    && message.call_reference == 0
22381                    && message.transaction_id == 112
22382                    && message.sequence_flag == 2
22383                    && message.display_priority == 0
22384                    && message.conference_id == 0
22385                    && message.application_instance_id == PHONE_RINGTONE_APPLICATION_ID
22386                    && message.routing == 1
22387                    && CiscoIpPhoneSetRingTone::from_xml(&message.data).unwrap() == document
22388        )));
22389
22390        handle.shutdown().await.unwrap();
22391        task.await.unwrap().unwrap();
22392    }
22393
22394    #[test]
22395    fn status_service_delivery_preserves_items_icons_timers_and_envelope() {
22396        let bitmap = PhoneStatusDocument::Bitmap(CiscoIpPhoneStatus {
22397            text: Some("Calls waiting".into()),
22398            timer_seconds: Some(30),
22399            location_x: Some(-1),
22400            location_y: Some(20),
22401            width: 106,
22402            height: 21,
22403            depth: 2,
22404            data: Some(PhoneBitmapData::new(vec![0x5a; PHONE_STATUS_BITMAP_MAX_BYTES]).unwrap()),
22405        });
22406        let legacy = status_service_messages(
22407            LineInstance::new(3),
22408            CallReference::new(71),
22409            ApplicationId::new(9_096),
22410            TransactionId::new(103),
22411            PhoneServicePriority::HIGH,
22412            &bitmap,
22413            ProtocolVersion::V17,
22414        )
22415        .unwrap();
22416        assert!(matches!(
22417            legacy.as_slice(),
22418            [ServerMessage::UserToDeviceDataV1(message)]
22419                if message.application_id == 9_096
22420                    && message.line_instance == 3
22421                    && message.call_reference == 71
22422                    && message.transaction_id == 103
22423                    && message.sequence_flag == 2
22424                    && message.display_priority == 2
22425                    && message.routing == 1
22426                    && PhoneStatusDocument::from_xml(&message.data).unwrap() == bitmap
22427        ));
22428
22429        let file = PhoneStatusDocument::File(CiscoIpPhoneStatusFile {
22430            text: Some("Map status".into()),
22431            timer_seconds: Some(0),
22432            location_x: Some(261),
22433            location_y: Some(49),
22434            url: PhoneImageUrl::new("https://pbx.example/status.png?site=east").unwrap(),
22435        });
22436        let modern = status_service_messages(
22437            LineInstance::new(3),
22438            CallReference::new(71),
22439            ApplicationId::new(9_096),
22440            TransactionId::new(104),
22441            PhoneServicePriority::LOW,
22442            &file,
22443            ProtocolVersion::V22,
22444        )
22445        .unwrap();
22446        assert!(matches!(
22447            modern.as_slice(),
22448            [ServerMessage::UserToDeviceDataV1(message)]
22449                if message.sequence_flag == 2
22450                    && message.display_priority == 0
22451                    && PhoneStatusDocument::from_xml(&message.data).unwrap() == file
22452        ));
22453
22454        let invalid = PhoneStatusDocument::Bitmap(CiscoIpPhoneStatus {
22455            text: None,
22456            timer_seconds: None,
22457            location_x: None,
22458            location_y: None,
22459            width: 1,
22460            height: 1,
22461            depth: 1,
22462            data: Some(PhoneBitmapData::new(vec![0; PHONE_STATUS_BITMAP_MAX_BYTES + 1]).unwrap()),
22463        });
22464        assert!(matches!(
22465            status_service_messages(
22466                LineInstance::new(3),
22467                CallReference::new(71),
22468                ApplicationId::new(9_096),
22469                TransactionId::new(105),
22470                PhoneServicePriority::NORMAL,
22471                &invalid,
22472                ProtocolVersion::V22,
22473            ),
22474            Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
22475                kind: "phone status bitmap bytes",
22476                maximum: PHONE_STATUS_BITMAP_MAX_BYTES,
22477                ..
22478            }))
22479        ));
22480    }
22481
22482    #[tokio::test]
22483    async fn registered_phone_receives_typed_execute_image_status_tone_and_announcement_commands() {
22484        let config = ServerConfig {
22485            bind: "127.0.0.1:0".parse().unwrap(),
22486            advertised_address: Ipv4Addr::LOCALHOST,
22487            ..ServerConfig::default()
22488        };
22489        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22490        let address = server.local_addr().unwrap();
22491        let task = tokio::spawn(server.run());
22492        let mut phone = TcpStream::connect(address).await.unwrap();
22493        let mut decoder = FrameDecoder::new();
22494        let protocol = ProtocolVersion::V22;
22495        let device_id = DeviceId::new("SEP001122334455").unwrap();
22496
22497        phone.write_all(&register_bytes(protocol)).await.unwrap();
22498        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22499        assert!(matches!(
22500            events.recv().await,
22501            Some(Event::Device(DeviceEvent {
22502                session_generation: _,
22503                device_id: _,
22504                event: DeviceEventKind::Registered(_)
22505            }))
22506        ));
22507
22508        let execute = CiscoIpPhoneExecute::new(vec![
22509            CiscoIpPhoneExecuteItem::with_priority("App:Close:9093", PhoneExecutePriority::NORMAL)
22510                .unwrap(),
22511        ])
22512        .unwrap();
22513        handle
22514            .send(Command::new(
22515                device_id.clone(),
22516                CommandAction::ExecutePhoneActions {
22517                    line_instance: LineInstance::new(2),
22518                    call_reference: CallReference::new(42),
22519                    application_id: ApplicationId::new(9_093),
22520                    transaction_id: TransactionId::new(73),
22521                    priority: PhoneServicePriority::HIGH,
22522                    document: execute.clone(),
22523                },
22524            ))
22525            .await
22526            .unwrap();
22527        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22528        assert!(frames.into_iter().any(|frame| matches!(
22529            ServerMessage::decode(frame, protocol),
22530            Ok(ServerMessage::UserToDeviceDataV1(message))
22531                if message.application_id == 9_093
22532                    && message.line_instance == 2
22533                    && message.call_reference == 42
22534                    && message.transaction_id == 73
22535                    && message.display_priority == 2
22536                    && CiscoIpPhoneExecute::from_xml(&message.data).unwrap() == execute
22537        )));
22538
22539        let image = PhoneImageDocument::ImageFile(CiscoIpPhoneImageFile {
22540            keypad_target: None,
22541            application_id: Some("map".into()),
22542            on_focus_lost: None,
22543            on_focus_gained: None,
22544            on_minimized: None,
22545            on_closed: None,
22546            title: Some("Site map".into()),
22547            prompt: Some("Inspect".into()),
22548            soft_keys: Vec::new(),
22549            key_items: Vec::new(),
22550            location_x: Some(12),
22551            location_y: Some(8),
22552            url: PhoneImageUrl::new("https://pbx.example/site.png?view=all").unwrap(),
22553        });
22554        handle
22555            .send(Command::new(
22556                device_id.clone(),
22557                CommandAction::ShowImageService {
22558                    line_instance: LineInstance::new(2),
22559                    call_reference: CallReference::new(42),
22560                    application_id: ApplicationId::new(9_095),
22561                    transaction_id: TransactionId::new(74),
22562                    priority: PhoneServicePriority::LOW,
22563                    document: image.clone(),
22564                },
22565            ))
22566            .await
22567            .unwrap();
22568        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22569        assert!(frames.into_iter().any(|frame| matches!(
22570            ServerMessage::decode(frame, protocol),
22571            Ok(ServerMessage::UserToDeviceDataV1(message))
22572                if message.application_id == 9_095
22573                    && message.line_instance == 2
22574                    && message.call_reference == 42
22575                    && message.transaction_id == 74
22576                    && message.display_priority == 0
22577                    && PhoneImageDocument::from_xml(&message.data).unwrap() == image
22578        )));
22579
22580        let status = PhoneStatusDocument::File(CiscoIpPhoneStatusFile {
22581            text: Some("Queue ready".into()),
22582            timer_seconds: Some(10),
22583            location_x: Some(4),
22584            location_y: Some(8),
22585            url: PhoneImageUrl::new("https://pbx.example/status.png?queue=support").unwrap(),
22586        });
22587        handle
22588            .send(Command::new(
22589                device_id.clone(),
22590                CommandAction::ShowStatusService {
22591                    line_instance: LineInstance::new(2),
22592                    call_reference: CallReference::new(42),
22593                    application_id: ApplicationId::new(9_096),
22594                    transaction_id: TransactionId::new(75),
22595                    priority: PhoneServicePriority::NORMAL,
22596                    document: status.clone(),
22597                },
22598            ))
22599            .await
22600            .unwrap();
22601        let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22602        assert!(frames.into_iter().any(|frame| matches!(
22603            ServerMessage::decode(frame, protocol),
22604            Ok(ServerMessage::UserToDeviceDataV1(message))
22605                if message.application_id == 9_096
22606                    && message.line_instance == 2
22607                    && message.call_reference == 42
22608                    && message.transaction_id == 75
22609                    && message.display_priority == 1
22610                    && PhoneStatusDocument::from_xml(&message.data).unwrap() == status
22611        )));
22612
22613        let call_id = CallId(7001);
22614        handle
22615            .send(Command::new(
22616                device_id.clone(),
22617                CommandAction::BeginCall {
22618                    line_instance: LineInstance(1),
22619                    call_id,
22620                    codec: Codec::Pcma,
22621                },
22622            ))
22623            .await
22624            .unwrap();
22625        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
22626        handle
22627            .send(Command::new(
22628                device_id.clone(),
22629                CommandAction::StartTone {
22630                    call_id,
22631                    tone: Tone::RecorderWarning,
22632                },
22633            ))
22634            .await
22635            .unwrap();
22636        let frames = read_until_message(&mut phone, &mut decoder, id::START_TONE).await;
22637        assert!(frames.into_iter().any(|frame| matches!(
22638            ServerMessage::decode(frame, protocol),
22639            Ok(ServerMessage::StartTone {
22640                tone: Tone::RecorderWarning,
22641                direction: ToneDirection::User,
22642                line_instance: 1,
22643                call_reference: 7001,
22644            })
22645        )));
22646
22647        handle
22648            .send_confirmed(Command::new(
22649                device_id.clone(),
22650                CommandAction::SetMicrophoneMode { enabled: false },
22651            ))
22652            .await
22653            .unwrap();
22654        let frames = read_until_message(&mut phone, &mut decoder, id::SET_MICROPHONE_MODE).await;
22655        assert!(frames.into_iter().any(|frame| matches!(
22656            ServerMessage::decode(frame, protocol),
22657            Ok(ServerMessage::SetMicrophoneMode(MicrophoneMode::Off))
22658        )));
22659
22660        handle
22661            .send_confirmed(Command::new(
22662                device_id.clone(),
22663                CommandAction::SetRecordingStatus {
22664                    call_id,
22665                    active: true,
22666                },
22667            ))
22668            .await
22669            .unwrap();
22670        let frames = read_until_message(&mut phone, &mut decoder, id::RECORDING_STATUS).await;
22671        assert!(frames.into_iter().any(|frame| matches!(
22672            ServerMessage::decode(frame, protocol),
22673            Ok(ServerMessage::RecordingStatus {
22674                call_reference: 7001,
22675                active: true,
22676            })
22677        )));
22678
22679        let conference_id = ConferenceId::new(44);
22680        let rejected = handle
22681            .send_confirmed(Command::new(
22682                device_id.clone(),
22683                CommandAction::StartAnnouncement {
22684                    conference_id,
22685                    announcements: vec![AnnouncementEntry {
22686                        locale: 1,
22687                        country: 46,
22688                        tone: Tone::Zip,
22689                    }],
22690                    end_of_ack: true,
22691                    participant_ids: vec![ParticipantId::new(7), ParticipantId::new(9)],
22692                    hearing_participant_mask: 0b11,
22693                    play_mode: 2,
22694                },
22695            ))
22696            .await
22697            .unwrap_err();
22698        assert!(
22699            matches!(rejected, ServerError::CommandWrite(message) if message.contains("not a station command"))
22700        );
22701
22702        // Rejecting a service-node message must not retire the handset
22703        // session or poison subsequent station UI delivery.
22704        handle
22705            .send_confirmed(Command::new(
22706                device_id.clone(),
22707                CommandAction::SetMicrophoneMode { enabled: true },
22708            ))
22709            .await
22710            .unwrap();
22711        let frames = read_until_message(&mut phone, &mut decoder, id::SET_MICROPHONE_MODE).await;
22712        assert!(frames.into_iter().any(|frame| matches!(
22713            ServerMessage::decode(frame, protocol),
22714            Ok(ServerMessage::SetMicrophoneMode(MicrophoneMode::On))
22715        )));
22716
22717        handle.shutdown().await.unwrap();
22718        task.await.unwrap().unwrap();
22719    }
22720
22721    #[test]
22722    fn announcement_command_mapping_preserves_typed_ids_and_wire_bounds() {
22723        let message = start_announcement_message(
22724            ConferenceId::new(44),
22725            vec![AnnouncementEntry {
22726                locale: 1,
22727                country: 46,
22728                tone: Tone::Zip,
22729            }],
22730            true,
22731            vec![ParticipantId::new(7), ParticipantId::new(9)],
22732            0b11,
22733            2,
22734        );
22735        assert!(matches!(
22736            message,
22737            ServerMessage::StartAnnouncement {
22738                conference_id: 44,
22739                end_of_ack: 1,
22740                ref matrix_conference_party_ids,
22741                ..
22742            } if matrix_conference_party_ids == &[7, 9]
22743        ));
22744        assert!(matches!(
22745            message.encode(ProtocolVersion::V22),
22746            Err(CodecError::UnexpectedRoute {
22747                actual: crate::MessageRoute::IntraControl,
22748                ..
22749            })
22750        ));
22751        let control = ControlMessage::StartAnnouncement {
22752            announcements: vec![AnnouncementEntry {
22753                locale: 1,
22754                country: 46,
22755                tone: Tone::Zip,
22756            }],
22757            end_of_ack: EndOfAnnouncementAck::Required,
22758            conference_id: 44,
22759            matrix_conference_party_ids: vec![7, 9],
22760            hearing_conference_party_mask: 0b11,
22761            play_mode: AnnouncementPlayMode::Continuous,
22762        };
22763        assert!(control.encode(ProtocolVersion::V22).is_ok());
22764
22765        let too_many_announcements = start_announcement_message(
22766            ConferenceId::new(44),
22767            vec![
22768                AnnouncementEntry {
22769                    locale: 1,
22770                    country: 46,
22771                    tone: Tone::Zip,
22772                };
22773                33
22774            ],
22775            false,
22776            Vec::new(),
22777            0,
22778            0,
22779        );
22780        assert!(matches!(
22781            too_many_announcements.encode(ProtocolVersion::V22),
22782            Err(CodecError::CountTooLarge {
22783                field: "announcements",
22784                maximum: 32,
22785                ..
22786            })
22787        ));
22788
22789        let too_many_participants = start_announcement_message(
22790            ConferenceId::new(44),
22791            Vec::new(),
22792            false,
22793            (1..=17).map(ParticipantId::new).collect(),
22794            0,
22795            0,
22796        );
22797        assert!(matches!(
22798            too_many_participants.encode(ProtocolVersion::V22),
22799            Err(CodecError::CountTooLarge {
22800                field: "matrix conference party identifiers",
22801                maximum: 16,
22802                ..
22803            })
22804        ));
22805    }
22806
22807    #[tokio::test]
22808    async fn registered_xml_alarms_route_typed_or_opaque_without_leaking_payloads() {
22809        let config = ServerConfig {
22810            bind: "127.0.0.1:0".parse().unwrap(),
22811            advertised_address: Ipv4Addr::LOCALHOST,
22812            ..ServerConfig::default()
22813        };
22814        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22815        let address = server.local_addr().unwrap();
22816        let task = tokio::spawn(server.run());
22817        let mut phone = TcpStream::connect(address).await.unwrap();
22818        let mut decoder = FrameDecoder::new();
22819        let protocol = ProtocolVersion::V22;
22820
22821        phone.write_all(&register_bytes(protocol)).await.unwrap();
22822        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22823        assert!(matches!(
22824            events.recv().await,
22825            Some(Event::Device(DeviceEvent {
22826                session_generation: _,
22827                device_id: _,
22828                event: DeviceEventKind::Registered(_)
22829            }))
22830        ));
22831
22832        let known = "<x-cisco-alarm><Alarm Name=\"LastOutOfServiceInformation\"><ParameterList><String name=\"DeviceName\">private-device-name</String><Enum name=\"ReasonForOutOfService\">25</Enum></ParameterList></Alarm></x-cisco-alarm>";
22833        phone
22834            .write_all(
22835                &ClientMessage::XmlAlarm(XmlAlarmMessage::from_xml(known).unwrap())
22836                    .encode(protocol)
22837                    .unwrap(),
22838            )
22839            .await
22840            .unwrap();
22841        let Some(Event::Device(DeviceEvent {
22842            session_generation: _,
22843            device_id,
22844            event: DeviceEventKind::XmlAlarm { telemetry },
22845        })) = events.recv().await
22846        else {
22847            panic!("typed XML alarm event was not emitted");
22848        };
22849        assert_eq!(device_id, DeviceId::new("SEP001122334455").unwrap());
22850        assert_eq!(
22851            telemetry.summary(),
22852            Some(crate::phone::xml::PhoneAlarmSummary {
22853                kind: crate::phone::xml::PhoneAlarmKind::LastOutOfService,
22854                reason_for_out_of_service: Some(25),
22855            })
22856        );
22857        assert!(!format!("{telemetry:?}").contains("private-device-name"));
22858
22859        let unknown = "<vendor-alarm><Credential>private-token</Credential></vendor-alarm>";
22860        phone
22861            .write_all(
22862                &ClientMessage::XmlAlarm(XmlAlarmMessage::from_xml(unknown).unwrap())
22863                    .encode(protocol)
22864                    .unwrap(),
22865            )
22866            .await
22867            .unwrap();
22868        let Some(Event::Device(DeviceEvent {
22869            session_generation: _,
22870            device_id: _,
22871            event: DeviceEventKind::XmlAlarm { telemetry, .. },
22872        })) = events.recv().await
22873        else {
22874            panic!("opaque XML alarm event was not emitted");
22875        };
22876        assert!(telemetry.is_opaque());
22877        assert_eq!(telemetry.summary(), None);
22878        assert!(!format!("{telemetry:?}").contains("private-token"));
22879
22880        phone
22881            .write_all(
22882                &ClientMessage::XmlAlarm(
22883                    XmlAlarmMessage::from_xml(
22884                        "<x-cisco-alarm><Alarm Name=\"Unknown\">&undeclared;</Alarm></x-cisco-alarm>",
22885                    )
22886                    .unwrap(),
22887                )
22888                .encode(protocol)
22889                .unwrap(),
22890            )
22891            .await
22892            .unwrap();
22893        assert!(
22894            tokio::time::timeout(Duration::from_millis(50), events.recv())
22895                .await
22896                .is_err()
22897        );
22898
22899        phone
22900            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
22901            .await
22902            .unwrap();
22903        let frames = read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
22904        assert!(
22905            frames
22906                .iter()
22907                .any(|frame| frame.message_id == id::KEEP_ALIVE_ACK)
22908        );
22909
22910        handle.shutdown().await.unwrap();
22911        task.await.unwrap().unwrap();
22912    }
22913
22914    #[tokio::test]
22915    async fn registered_location_information_routes_typed_or_opaque_without_leaking_fields() {
22916        let config = ServerConfig {
22917            bind: "127.0.0.1:0".parse().unwrap(),
22918            advertised_address: Ipv4Addr::LOCALHOST,
22919            ..ServerConfig::default()
22920        };
22921        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22922        let address = server.local_addr().unwrap();
22923        let task = tokio::spawn(server.run());
22924        let mut phone = TcpStream::connect(address).await.unwrap();
22925        let mut decoder = FrameDecoder::new();
22926        let protocol = ProtocolVersion::V22;
22927
22928        phone.write_all(&register_bytes(protocol)).await.unwrap();
22929        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22930        assert!(matches!(
22931            events.recv().await,
22932            Some(Event::Device(DeviceEvent {
22933                session_generation: _,
22934                device_id: _,
22935                event: DeviceEventKind::Registered(_)
22936            }))
22937        ));
22938
22939        let known = "<Interface1><wifi><BSSID>E8:ED:F3:10:29:FD</BSSID><SSID>private-network</SSID><APName>private-access-point</APName></wifi><OffPrem></OffPrem></Interface1>";
22940        phone
22941            .write_all(
22942                &ClientMessage::LocationInfo { xml: known.into() }
22943                    .encode(protocol)
22944                    .unwrap(),
22945            )
22946            .await
22947            .unwrap();
22948        let Some(Event::Device(DeviceEvent {
22949            session_generation: _,
22950            device_id,
22951            event: DeviceEventKind::LocationInformation { telemetry },
22952        })) = events.recv().await
22953        else {
22954            panic!("typed location-information event was not emitted");
22955        };
22956        assert_eq!(device_id, DeviceId::new("SEP001122334455").unwrap());
22957        assert_eq!(
22958            telemetry.summary(),
22959            Some(crate::phone::xml::PhoneLocationSummary {
22960                kind: crate::phone::xml::PhoneLocationKind::WirelessInterface,
22961                off_premises: true,
22962            })
22963        );
22964        let crate::phone::xml::PhoneLocationTelemetry::WirelessInterface(location) = &telemetry
22965        else {
22966            panic!("known wireless location was not typed");
22967        };
22968        assert_eq!(
22969            location.wifi.bssid.octets(),
22970            [0xe8, 0xed, 0xf3, 0x10, 0x29, 0xfd]
22971        );
22972        assert_eq!(location.wifi.ssid, "private-network");
22973        assert_eq!(location.wifi.access_point_name, "private-access-point");
22974        let debug = format!("{telemetry:?}");
22975        assert!(!debug.contains("private-network"));
22976        assert!(!debug.contains("private-access-point"));
22977        assert!(!debug.contains("E8:ED:F3:10:29:FD"));
22978
22979        let unknown =
22980            "<DeviceLocation><CivicAddress>private-building</CivicAddress></DeviceLocation>";
22981        phone
22982            .write_all(
22983                &ClientMessage::LocationInfo {
22984                    xml: unknown.into(),
22985                }
22986                .encode(protocol)
22987                .unwrap(),
22988            )
22989            .await
22990            .unwrap();
22991        let Some(Event::Device(DeviceEvent {
22992            session_generation: _,
22993            device_id: _,
22994            event: DeviceEventKind::LocationInformation { telemetry, .. },
22995        })) = events.recv().await
22996        else {
22997            panic!("opaque location-information event was not emitted");
22998        };
22999        assert!(telemetry.is_opaque());
23000        assert_eq!(telemetry.summary(), None);
23001        assert!(!format!("{telemetry:?}").contains("private-building"));
23002
23003        phone
23004            .write_all(
23005                &ClientMessage::LocationInfo {
23006                    xml: "<Interface1>&undeclared;</Interface1>".into(),
23007                }
23008                .encode(protocol)
23009                .unwrap(),
23010            )
23011            .await
23012            .unwrap();
23013        assert!(
23014            tokio::time::timeout(Duration::from_millis(50), events.recv())
23015                .await
23016                .is_err()
23017        );
23018
23019        phone
23020            .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
23021            .await
23022            .unwrap();
23023        let frames = read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
23024        assert!(
23025            frames
23026                .iter()
23027                .any(|frame| frame.message_id == id::KEEP_ALIVE_ACK)
23028        );
23029
23030        handle.shutdown().await.unwrap();
23031        task.await.unwrap().unwrap();
23032    }
23033
23034    #[tokio::test]
23035    async fn transfer_presentation_marks_source_and_keeps_consultation_active() {
23036        let config = ServerConfig {
23037            bind: "127.0.0.1:0".parse().unwrap(),
23038            advertised_address: Ipv4Addr::LOCALHOST,
23039            ..ServerConfig::default()
23040        };
23041        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
23042        let address = server.local_addr().unwrap();
23043        let task = tokio::spawn(server.run());
23044        let mut phone = TcpStream::connect(address).await.unwrap();
23045        let mut decoder = FrameDecoder::new();
23046        let protocol = ProtocolVersion::V22;
23047        let device_id = DeviceId::new("SEP001122334455").unwrap();
23048
23049        phone.write_all(&register_bytes(protocol)).await.unwrap();
23050        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
23051        assert!(matches!(
23052            events.recv().await,
23053            Some(Event::Device(DeviceEvent {
23054                session_generation: _,
23055                device_id: _,
23056                event: DeviceEventKind::Registered(_)
23057            }))
23058        ));
23059        handle
23060            .send_confirmed(Command::new(
23061                device_id.clone(),
23062                CommandAction::BeginCall {
23063                    line_instance: LineInstance(1),
23064                    call_id: CallId(10),
23065                    codec: Codec::Pcmu,
23066                },
23067            ))
23068            .await
23069            .unwrap();
23070        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23071        for state in [CallState::Connected, CallState::Hold] {
23072            handle
23073                .send_confirmed(Command::new(
23074                    device_id.clone(),
23075                    CommandAction::SetCallState {
23076                        call_id: CallId(10),
23077                        state,
23078                    },
23079                ))
23080                .await
23081                .unwrap();
23082            read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23083        }
23084
23085        handle
23086            .send_confirmed(Command::new(
23087                device_id.clone(),
23088                CommandAction::BeginTransfer {
23089                    source_call_id: CallId(10),
23090                    consultation_line_instance: LineInstance(1),
23091                    consultation_call_id: CallId(20),
23092                    codec: Codec::Pcmu,
23093                },
23094            ))
23095            .await
23096            .unwrap();
23097        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
23098            matches!(
23099                message,
23100                ServerMessage::SetLamp {
23101                    stimulus: ButtonType::Transfer,
23102                    mode: LampMode::Flash,
23103                    ..
23104                }
23105            )
23106        })
23107        .await;
23108        let states = messages
23109            .iter()
23110            .filter_map(|message| match message {
23111                ServerMessage::CallState {
23112                    state,
23113                    call_reference,
23114                    ..
23115                } => Some((*state, *call_reference)),
23116                _ => None,
23117            })
23118            .collect::<Vec<_>>();
23119        assert_eq!(
23120            states,
23121            vec![(CallState::Transfer, 10), (CallState::OffHook, 20)]
23122        );
23123        assert!(messages.iter().any(|message| matches!(
23124            message,
23125            ServerMessage::SelectSoftKeys {
23126                call_reference: 20,
23127                set: KeyMode::OffHookFeature,
23128                ..
23129            }
23130        )));
23131        assert!(!messages.iter().any(|message| matches!(
23132            message,
23133            ServerMessage::CallState {
23134                state: CallState::Transfer,
23135                call_reference: 20,
23136                ..
23137            }
23138        )));
23139
23140        handle
23141            .send_confirmed(Command::new(
23142                device_id.clone(),
23143                CommandAction::SetCallState {
23144                    call_id: CallId(20),
23145                    state: CallState::Connected,
23146                },
23147            ))
23148            .await
23149            .unwrap();
23150        let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
23151            matches!(
23152                message,
23153                ServerMessage::SelectSoftKeys {
23154                    call_reference: 20,
23155                    set: KeyMode::ConnectedTransfer,
23156                    ..
23157                }
23158            )
23159        })
23160        .await;
23161        assert!(messages.iter().any(|message| matches!(
23162            message,
23163            ServerMessage::SelectSoftKeys {
23164                call_reference: 20,
23165                set: KeyMode::ConnectedTransfer,
23166                ..
23167            }
23168        )));
23169
23170        handle.shutdown().await.unwrap();
23171        task.await.unwrap().unwrap();
23172    }
23173
23174    #[tokio::test]
23175    async fn active_call_selection_and_hook_flash_use_exact_session_identity() {
23176        let config = ServerConfig {
23177            bind: "127.0.0.1:0".parse().unwrap(),
23178            advertised_address: Ipv4Addr::LOCALHOST,
23179            ..ServerConfig::default()
23180        };
23181        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
23182        let address = server.local_addr().unwrap();
23183        let task = tokio::spawn(server.run());
23184        let mut phone = TcpStream::connect(address).await.unwrap();
23185        let mut decoder = FrameDecoder::new();
23186        let protocol = ProtocolVersion::V22;
23187        let device_id = DeviceId::new("SEP001122334455").unwrap();
23188
23189        phone.write_all(&register_bytes(protocol)).await.unwrap();
23190        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
23191        assert!(matches!(
23192            events.recv().await,
23193            Some(Event::Device(DeviceEvent {
23194                session_generation: _,
23195                device_id: _,
23196                event: DeviceEventKind::Registered(_)
23197            }))
23198        ));
23199        for call_id in [CallId(10), CallId(20)] {
23200            handle
23201                .send(Command::new(
23202                    device_id.clone(),
23203                    CommandAction::BeginCall {
23204                        line_instance: LineInstance(1),
23205                        call_id,
23206                        codec: Codec::Pcmu,
23207                    },
23208                ))
23209                .await
23210                .unwrap();
23211            read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23212        }
23213
23214        handle
23215            .send(Command::new(
23216                device_id.clone(),
23217                CommandAction::SetCallSelected {
23218                    call_id: CallId(10),
23219                    selected: true,
23220                },
23221            ))
23222            .await
23223            .unwrap();
23224        let frames = read_until_message(&mut phone, &mut decoder, id::CALL_SELECT_STAT).await;
23225        assert!(frames.into_iter().any(|frame| matches!(
23226            ServerMessage::decode(frame, protocol),
23227            Ok(ServerMessage::CallSelectStatus {
23228                status: 1,
23229                call_reference: 10,
23230                line_instance: 1,
23231            })
23232        )));
23233
23234        phone
23235            .write_all(
23236                &ClientMessage::HookFlash {
23237                    line_instance: 1,
23238                    call_reference: 0,
23239                }
23240                .encode(protocol)
23241                .unwrap(),
23242            )
23243            .await
23244            .unwrap();
23245        assert!(matches!(
23246            events.recv().await,
23247            Some(Event::Device(DeviceEvent {
23248                session_generation: _,
23249                device_id: _,
23250                event: DeviceEventKind::HookFlash {
23251                    call_id: Some(CallId(20)),
23252                    line_instance: LineInstance(1),
23253                    ..
23254                }
23255            }))
23256        ));
23257
23258        handle.shutdown().await.unwrap();
23259        task.await.unwrap().unwrap();
23260    }
23261
23262    #[tokio::test]
23263    async fn omitted_answer_uses_live_policy_and_skips_an_offer_closed_before_input() {
23264        let config = ServerConfig {
23265            bind: "127.0.0.1:0".parse().unwrap(),
23266            advertised_address: Ipv4Addr::LOCALHOST,
23267            call_answer_order: CallSelectionOrder::LastFirst,
23268            ..ServerConfig::default()
23269        };
23270        let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
23271        let address = server.local_addr().unwrap();
23272        let task = tokio::spawn(server.run());
23273        let mut phone = TcpStream::connect(address).await.unwrap();
23274        let mut decoder = FrameDecoder::new();
23275        let protocol = ProtocolVersion::V22;
23276        let device_id = DeviceId::new("SEP001122334455").unwrap();
23277
23278        phone.write_all(&register_bytes(protocol)).await.unwrap();
23279        read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
23280        assert!(matches!(
23281            events.recv().await,
23282            Some(Event::Device(DeviceEvent {
23283                session_generation: _,
23284                device_id: _,
23285                event: DeviceEventKind::Registered(_)
23286            }))
23287        ));
23288        handle
23289            .send(Command::new(
23290                device_id.clone(),
23291                CommandAction::BeginCall {
23292                    line_instance: LineInstance(1),
23293                    call_id: CallId(1),
23294                    codec: Codec::Pcmu,
23295                },
23296            ))
23297            .await
23298            .unwrap();
23299        read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23300        handle
23301            .send(Command::new(
23302                device_id.clone(),
23303                CommandAction::SetCallState {
23304                    call_id: CallId(1),
23305                    state: CallState::Connected,
23306                },
23307            ))
23308            .await
23309            .unwrap();
23310        read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
23311
23312        let info = CallInfo {
23313            direction: crate::types::CallDirection::Inbound,
23314            calling_name: "Caller".into(),
23315            calling_number: "1002".into(),
23316            called_name: "Desk".into(),
23317            called_number: "1001".into(),
23318            ..CallInfo::default()
23319        };
23320        for call_id in [CallId(10), CallId(20)] {
23321            handle
23322                .offer_incoming_call_with_id(
23323                    device_id.clone(),
23324                    LineInstance::new(1),
23325                    call_id,
23326                    info.clone(),
23327                )
23328                .await
23329                .unwrap();
23330            read_until_message(&mut phone, &mut decoder, id::DISPLAY_DYNAMIC_PROMPT_STATUS).await;
23331        }
23332        phone
23333            .write_all(
23334                &ClientMessage::SoftKeyEvent {
23335                    event: SoftKey::Answer.wire_value(),
23336                    line_instance: 1,
23337                    call_reference: 0,
23338                }
23339                .encode(protocol)
23340                .unwrap(),
23341            )
23342            .await
23343            .unwrap();
23344        assert!(matches!(
23345            events.recv().await,
23346            Some(Event::Device(DeviceEvent {
23347                session_generation: _,
23348                device_id: _,
23349                event: DeviceEventKind::SoftKey {
23350                    call_id: Some(CallId(20)),
23351                    soft_key: SoftKey::Answer,
23352                    ..
23353                }
23354            }))
23355        ));
23356
23357        handle
23358            .try_offer_incoming_call_with_id(
23359                device_id.clone(),
23360                LineInstance::new(1),
23361                CallId(30),
23362                info,
23363            )
23364            .unwrap();
23365        handle
23366            .send_confirmed(Command::new(
23367                device_id.clone(),
23368                CommandAction::CloseCall {
23369                    call_id: CallId(30),
23370                },
23371            ))
23372            .await
23373            .unwrap();
23374        handle
23375            .send_confirmed(Command::new(
23376                device_id.clone(),
23377                CommandAction::CloseCall {
23378                    call_id: CallId(20),
23379                },
23380            ))
23381            .await
23382            .unwrap();
23383        phone
23384            .write_all(
23385                &ClientMessage::OffHook {
23386                    line_instance: 1,
23387                    call_reference: 0,
23388                }
23389                .encode(protocol)
23390                .unwrap(),
23391            )
23392            .await
23393            .unwrap();
23394        assert!(matches!(
23395            events.recv().await,
23396            Some(Event::Device(DeviceEvent {
23397                session_generation: _,
23398                device_id: _,
23399                event: DeviceEventKind::OffHook {
23400                    call_id: CallId(10),
23401                    line_instance: LineInstance(1),
23402                    ..
23403                }
23404            }))
23405        ));
23406
23407        handle.shutdown().await.unwrap();
23408        task.await.unwrap().unwrap();
23409    }
23410
23411    #[test]
23412    fn server_messages_are_decodeable_frames() {
23413        let bytes = ServerMessage::CapabilitiesRequest
23414            .encode(ProtocolVersion::V22)
23415            .unwrap();
23416        assert_eq!(
23417            FrameDecoder::new().push(&bytes).unwrap()[0].message_id,
23418            id::CAPABILITIES_REQ
23419        );
23420        assert!(matches!(
23421            ClientMessage::decode(Frame::new(0, id::KEEP_ALIVE, Vec::new())).unwrap(),
23422            ClientMessage::KeepAlive
23423        ));
23424    }
23425}