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::{BTreeMap, 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::BUTTON_TEMPLATE_ENTRIES_PER_CHUNK;
75use crate::message::capabilities::StationMediaCapabilities;
76use crate::message::values::{
77    AlarmSeverity, BusyLampFieldState, ButtonType, CallHistoryDisposition, CallState, Codec,
78    CodecKind, DeviceType, Digit, DtmfMode, EchoCancellation, EncryptionCapability, G723BitRate,
79    IpAddressType, KeyMode, LampMode, MediaStatus, MicrophoneMode, MiscCommandType,
80    NotificationPriority, PhoneFeatures, ProtocolVersion, ReceiveTransmit, ResetType, RingDuration,
81    RingerMode, SilenceSuppression, SoftKey, SpeakerMode, StationSessionContext,
82    StatisticsProcessing, Stimulus, SubscriptionCause, Tone, ToneDirection,
83};
84use crate::message::wire::{CodecError, FrameDecoder};
85use crate::message::{
86    AnnouncementEntry, AudioStreamControl, BoundedBytes, ButtonTemplateEntry, ClientMessage,
87    ConnectionStatistics, MediaEncryption, MediaEndpointAddress, MediaRequestIdentity,
88    MediaRequestToken, MiscellaneousCommand, MulticastMediaReception, MulticastMediaTransmission,
89    MultimediaPayload, MultimediaPayloadDirection, MultimediaStreamControl, OpenMultimediaChannel,
90    ServerMessage, SignalingServerEndpoint,
91    StartMultimediaTransmission as MultimediaTransmissionStart, UserDataV1Message,
92    VideoFlowControl,
93};
94#[cfg(test)]
95use crate::message::{ControlMessage, MediaCapability, XmlAlarmMessage};
96use crate::phone::service::{
97    PhoneServiceEvent, PhoneServiceExtendedRouting, PhoneServiceMessageKind, PhoneServicePayload,
98    PhoneServiceRouting, parse_phone_service_payload,
99};
100#[cfg(test)]
101use crate::phone::xml::{
102    self as phone_xml, CiscoIpPhoneGraphicFileMenu, CiscoIpPhoneImageFile, CiscoIpPhoneInputItem,
103    CiscoIpPhoneKeyItem, CiscoIpPhoneSoftKeyItem, CiscoIpPhoneStatus, CiscoIpPhoneStatusFile,
104    CiscoIpPhoneTouchAreaMenuItem, PHONE_EXECUTE_MAX_ITEMS, PHONE_STATUS_BITMAP_MAX_BYTES,
105    PhoneBackgroundHttpUrl, PhoneBitmapData, PhoneExecutePriority, PhoneImageUrl, PhoneInputFlags,
106    PhoneInputParameterName, PhoneRingtoneUrl, PhoneTouchArea, PhoneXmlKey,
107};
108use crate::phone::xml::{
109    CiscoIpPhoneExecute, CiscoIpPhoneExecuteItem, CiscoIpPhoneInput, CiscoIpPhoneMenu,
110    CiscoIpPhoneMenuItem, CiscoIpPhoneSetBackground, CiscoIpPhoneSetBackgroundPreview,
111    CiscoIpPhoneSetRingTone, CiscoIpPhoneText, ConferenceListAction, ConferenceListDocument,
112    ConferenceListEntry, ConferenceMenuFamily, ConferenceParticipantActionsDocument,
113    PHONE_BACKGROUND_APPLICATION_ID, PHONE_EXECUTE_MAX_BYTES, PHONE_IMAGE_MAX_BYTES,
114    PHONE_INPUT_MAX_BYTES, PHONE_RINGTONE_APPLICATION_ID, PHONE_STATUS_MAX_BYTES,
115    PHONE_TEXT_APPLICATION_ID, PHONE_TEXT_LEGACY_MAX_CHARS, PhoneAlarmTelemetry,
116    PhoneBackgroundControlDocument, PhoneImageDocument, PhoneLocationTelemetry,
117    PhoneServicePriority, PhoneStatusDocument, PhoneXmlError, parse_phone_alarm,
118    parse_phone_location,
119};
120use crate::types::SignalingQos;
121use crate::types::{
122    ApplicationId, AudioProcessingPolicy, BlfCallerInfo, BlfState, ButtonDefinition, CallId,
123    CallInfo, CallReference, ConferenceId, DEFAULT_AUDIO_MAX_FRAMES_PER_PACKET,
124    DEFAULT_AUDIO_PACKET_MS, DeviceDefinition, DeviceId, DeviceRegistration, LineAppearance,
125    LineDefinition, LineInstance, MediaEndpoint, MediaTrafficClass, ParticipantId,
126    PassthroughPartyId, SessionGeneration, SoftKeyProfile, StationTransport,
127    StationTransportRequirement, TransactionId,
128};
129use transport::AcceptedStation;
130
131const EVENT_CAPACITY: usize = 1024;
132const COMMAND_CAPACITY: usize = 1024;
133const SESSION_COMMAND_CAPACITY: usize = 256;
134const SESSION_ACCEPT_CAPACITY: usize = 128;
135/// Maximum time a phone may leave an ordering-sensitive media command
136/// unacknowledged before the call owner is notified and the stale correlation
137/// state is retired.
138pub const HANDSET_ACKNOWLEDGEMENT_TIMEOUT: Duration = Duration::from_secs(5);
139/// Bound for the writer acknowledgement used to serialize commands whose
140/// resources must remain owned until their complete frame reaches the socket.
141pub const ORDERING_ACKNOWLEDGEMENT_TIMEOUT: Duration = Duration::from_secs(5);
142// A 79x1 normally follows the active accessory's Off event with OnHook within
143// a few dozen milliseconds.  A route change instead reports the replacement
144// accessory On immediately.  Keep the release pending long enough to
145// distinguish those two transactions when firmware omits the final OnHook.
146const MEDIA_PATH_RELEASE_GRACE: Duration = Duration::from_millis(150);
147// A timeout only releases pending correlation state; statistics are never polled.
148const CONNECTION_STATISTICS_TIMEOUT: Duration = Duration::from_secs(10);
149const MAX_PENDING_CONNECTION_STATISTICS: usize = 32;
150// Retired references prevent a late reply from binding to a replacement call.
151const MAX_STATISTICS_REFERENCES_PER_SESSION: usize = 4096;
152const PARKING_APPLICATION_ID: u32 = 9090;
153/// Shortest retry delay accepted for a rejected registration token.
154pub const MIN_REGISTRATION_BACKOFF: Duration = Duration::from_secs(30);
155/// Longest retry delay accepted for a rejected registration token.
156pub const MAX_REGISTRATION_BACKOFF: Duration = Duration::from_secs(86_400);
157/// Maximum number of parked calls rendered in one station selection menu.
158///
159/// Higher-level parking state may contain more calls; callers select and order
160/// the bounded subset passed to [`CommandAction::ShowParkingMenu`].
161pub const PARKING_MENU_MAX_ITEMS: usize = 32;
162
163/// One selectable parked call rendered by the station parking application.
164///
165/// `slot` is the stable parking-space identifier returned by a subsequent
166/// [`DeviceEventKind::ParkingMenuSelection`]. The caller and connected-party
167/// fields are presentation text and do not participate in selection identity.
168#[derive(Clone, Debug, Eq, PartialEq)]
169pub struct ParkingMenuEntry {
170    pub slot: u32,
171    pub caller_name: String,
172    pub caller_number: String,
173    pub connected_name: String,
174    pub connected_number: String,
175}
176
177/// Audible presentation to apply to a newly offered incoming call.
178///
179/// Passing `None` to an incoming-offer method presents the call silently;
180/// [`IncomingRing::default`] selects an ordinary inside ring.
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182pub struct IncomingRing {
183    pub mode: RingerMode,
184    pub duration: RingDuration,
185}
186
187/// Device-wide do-not-disturb state rendered by configured feature buttons.
188#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
189pub enum DoNotDisturbMode {
190    #[default]
191    Off,
192    Silent,
193    Reject,
194}
195
196/// Behavior selected for one do-not-disturb feature button.
197#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
198pub enum DoNotDisturbButtonMode {
199    #[default]
200    Cycle,
201    Silent,
202    Reject,
203}
204
205impl Default for IncomingRing {
206    fn default() -> Self {
207        Self {
208            mode: RingerMode::Inside,
209            duration: RingDuration::Normal,
210        }
211    }
212}
213
214/// One fully correlated, privacy-safe station media snapshot retained independently of call
215/// teardown. The firmware-specific quality payload remains opaque and is represented only by its
216/// bounded byte count.
217#[derive(Clone, Debug, Eq, PartialEq)]
218pub struct MediaStatisticsSnapshot {
219    /// Monotonic generation assigned to the statistics request.
220    ///
221    /// A response is retained only when it matches the live request generation,
222    /// preventing a delayed response from replacing newer statistics.
223    pub request_generation: u64,
224    pub call_id: CallId,
225    pub line_instance: LineInstance,
226    pub codec: Codec,
227    pub packet_ms: u32,
228    pub max_frames_per_packet: u32,
229    pub receive_peer: Option<MediaEndpoint>,
230    pub transmit_peer: Option<MediaEndpoint>,
231    pub packets_sent: u32,
232    pub octets_sent: u32,
233    pub packets_received: u32,
234    pub octets_received: u32,
235    pub packets_lost: u32,
236    pub jitter_millis: u32,
237    pub latency_millis: u32,
238    /// Length of the bounded opaque quality report; its contents are not
239    /// retained in management state.
240    pub quality_byte_count: usize,
241}
242
243/// Device-wide status-line mutation.
244///
245/// The optional priority selects a protocol-specific notification plane. A
246/// clear with a priority removes only that plane; an unqualified clear removes
247/// the ordinary status message.
248#[derive(Clone, Debug, Eq, PartialEq)]
249pub enum HandsetStatusMessage {
250    Display {
251        text: String,
252        /// Zero keeps the message until another status mutation replaces it.
253        timeout_seconds: u8,
254        priority: Option<NotificationPriority>,
255    },
256    Clear {
257        priority: Option<NotificationPriority>,
258    },
259}
260
261/// Immutable policy shared by every session owned by one [`Server`].
262///
263/// Station definitions are supplied separately to the constructor and may be
264/// replaced at runtime through [`ServerHandle::reconfigure`].
265#[derive(Clone, Debug)]
266pub struct ServerConfig {
267    pub bind: SocketAddr,
268    /// Baseline marking applied before a station identifies itself. A device
269    /// definition may replace it for the remainder of that session.
270    pub signaling_qos: SignalingQos,
271    /// Configured fallback used only if the accepted socket does not have a
272    /// concrete local address. Normal server-list replies use the local
273    /// interface selected by the operating system for that connection.
274    pub advertised_address: Ipv4Addr,
275    /// IPv6 fallback for an unspecified accepted local socket.
276    pub advertised_ipv6_address: Option<Ipv6Addr>,
277    pub server_name: String,
278    /// Keepalive interval advertised to stations; session expiry uses a bounded
279    /// multiple of this interval.
280    pub keepalive_seconds: u32,
281    /// Keepalive interval advertised for sessions using a secondary server.
282    pub secondary_keepalive_seconds: u32,
283    /// Ordered failover endpoints. An empty list advertises only the endpoint
284    /// that accepted the current connection.
285    pub signaling_servers: Vec<SignalingServerRoute>,
286    /// Admission and retry policy for pre-registration token probes.
287    pub registration_tokens: RegistrationTokenPolicy,
288    pub firmware_version: String,
289    pub dial_terminator: Digit,
290    pub record_dial_terminator: bool,
291    pub call_answer_order: CallSelectionOrder,
292    /// Fixed station wall-clock offset from UTC. SCCP does not carry a named
293    /// timezone or daylight-saving transition table.
294    pub timezone_offset_minutes: i16,
295    pub date_template: crate::types::DateTemplate,
296    /// Optional policy-neutral station template for an otherwise unknown
297    /// guest-hotline registration. The dial destination remains owned by the
298    /// channel adapter and is never exposed in the handset definition.
299    pub anonymous_hotline: Option<AnonymousHotlineDefinition>,
300}
301
302/// One configured server and the ports available for each signaling transport.
303#[derive(Clone, Debug, Eq, PartialEq)]
304pub struct SignalingServerRoute {
305    pub priority: u8,
306    pub name: String,
307    pub address: IpAddr,
308    pub clear_port: Option<NonZeroU16>,
309    pub secure_port: Option<NonZeroU16>,
310}
311
312impl SignalingServerRoute {
313    fn endpoint(&self, transport: StationTransport) -> Option<SignalingServerEndpoint> {
314        let port = match transport {
315            StationTransport::Clear => self.clear_port,
316            StationTransport::Secure => self.secure_port,
317        }?;
318        Some(SignalingServerEndpoint {
319            name: self.name.clone(),
320            address: self.address,
321            port,
322        })
323    }
324}
325
326/// Decision applied to a pre-registration token probe after station and
327/// transport eligibility have been established.
328#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
329pub enum RegistrationFallback {
330    #[default]
331    Reject,
332    ReturnToPrimary,
333    DeviceIdOdd,
334    DeviceIdEven,
335}
336
337/// Policy applied when a station probes this server while registered elsewhere.
338#[derive(Clone, Debug, Eq, PartialEq)]
339pub struct RegistrationTokenPolicy {
340    pub fallback: RegistrationFallback,
341    pub backoff: Duration,
342    pub server_priority: u8,
343}
344
345impl Default for RegistrationTokenPolicy {
346    fn default() -> Self {
347        Self {
348            fallback: RegistrationFallback::Reject,
349            backoff: Duration::from_secs(60),
350            server_priority: 1,
351        }
352    }
353}
354
355impl RegistrationTokenPolicy {
356    fn accepts(&self, device_id: &DeviceId) -> bool {
357        let last_nibble = device_id
358            .as_str()
359            .strip_prefix("SEP")
360            .filter(|mac| mac.len() == 12 && mac.bytes().all(|byte| byte.is_ascii_hexdigit()))
361            .and_then(|mac| mac.as_bytes().last().copied())
362            .and_then(|byte| char::from(byte).to_digit(16));
363        match self.fallback {
364            RegistrationFallback::Reject => false,
365            RegistrationFallback::ReturnToPrimary => self.server_priority == 1,
366            RegistrationFallback::DeviceIdOdd => last_nibble.is_some_and(|value| value % 2 == 1),
367            RegistrationFallback::DeviceIdEven => last_nibble.is_some_and(|value| value % 2 == 0),
368        }
369    }
370}
371
372/// Restricted definition used to admit an otherwise unknown station as a
373/// single-line hotline device.
374///
375/// The server supplies only station-visible policy. Routing and authorization
376/// of the resulting off-hook event remain the adapter's responsibility.
377#[derive(Clone, Debug, Eq, PartialEq)]
378pub struct AnonymousHotlineDefinition {
379    label: String,
380}
381
382impl AnonymousHotlineDefinition {
383    /// Build a hotline template with a validated station-visible label.
384    ///
385    /// Labels must contain 1 through 79 bytes and no control characters.
386    pub fn new(label: impl Into<String>) -> Result<Self, ServerError> {
387        let label = label.into();
388        if label.is_empty() || label.len() > 79 || label.chars().any(char::is_control) {
389            return Err(ServerError::InvalidConfig(
390                "anonymous-hotline label must contain 1..=79 non-control bytes".into(),
391            ));
392        }
393        Ok(Self { label })
394    }
395
396    fn device_definition(&self, id: DeviceId) -> DeviceDefinition {
397        let soft_keys = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
398            let actions = match mode {
399                KeyMode::OnHook => vec![SoftKey::NewCall],
400                KeyMode::OffHook | KeyMode::RingOut => vec![SoftKey::EndCall],
401                _ => Vec::new(),
402            };
403            (mode, actions)
404        }))
405        .expect("minimal anonymous-hotline soft keys are valid");
406        DeviceDefinition {
407            id,
408            description: self.label.clone(),
409            transport: StationTransportRequirement::Either,
410            signaling_qos: None,
411            buttons: vec![ButtonDefinition::Line(LineAppearance::new(
412                1,
413                LineDefinition {
414                    number: "hotline".into(),
415                    display_name: self.label.clone(),
416                },
417            ))],
418            soft_keys,
419            ui: Default::default(),
420        }
421    }
422}
423
424/// Ordering used when a station asks to answer without identifying a call.
425#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
426pub enum CallSelectionOrder {
427    #[default]
428    OldestFirst,
429    LastFirst,
430}
431
432/// Network and framing policy for one station multicast audio direction.
433#[derive(Clone, Copy, Debug, Eq, PartialEq)]
434pub struct MulticastMediaRoute {
435    pub address: IpAddr,
436    pub port: u16,
437    pub codec: Codec,
438    pub packet_millis: u32,
439}
440
441/// Complete application-owned description of one station video receive flow.
442///
443/// Session-owned call, line, and request identities are deliberately absent.
444/// The opaque payload is accepted only when retained from a decoded receive
445/// message for the live session's protocol version.
446#[derive(Clone, Debug, Eq, PartialEq)]
447pub struct MultimediaReceiveDescriptor {
448    pub conference_id: ConferenceId,
449    pub payload: MultimediaPayload,
450    pub conference_creator: bool,
451    pub encryption: Option<MediaEncryption>,
452    pub stream_passthrough_id: u32,
453    pub associated_stream_id: u32,
454    pub source: MediaEndpointAddress,
455    pub requested_address_type: IpAddressType,
456}
457
458impl MultimediaReceiveDescriptor {
459    /// Rejects descriptors whose typed envelope cannot represent a video
460    /// receive flow. Session-specific protocol and station capabilities are
461    /// checked when the command is dispatched.
462    pub fn validate(self) -> Result<Self, ServerError> {
463        validate_multimedia_receive_descriptor(&self)?;
464        Ok(self)
465    }
466}
467
468/// Complete application-owned description of one station video transmit flow.
469///
470/// The opaque payload is accepted only when retained from a decoded transmit
471/// message for the live session's protocol version. Call and request identities
472/// remain session-owned.
473#[derive(Clone, Debug, Eq, PartialEq)]
474pub struct MultimediaTransmitDescriptor {
475    pub conference_id: ConferenceId,
476    pub endpoint: MediaEndpointAddress,
477    pub payload: MultimediaPayload,
478    /// Full traffic-class octet; configuration DSCP is shifted left by two.
479    pub traffic_class: MediaTrafficClass,
480    pub encryption: Option<MediaEncryption>,
481    pub stream_passthrough_id: u32,
482    pub associated_stream_id: u32,
483}
484
485impl MultimediaTransmitDescriptor {
486    /// Rejects descriptors whose typed envelope cannot represent a video
487    /// transmit flow. Live session policy is checked during dispatch.
488    pub fn validate(self) -> Result<Self, ServerError> {
489        validate_multimedia_transmit_descriptor(&self)?;
490        Ok(self)
491    }
492}
493
494/// Parameters for one command applied to an exact live station video encoder.
495///
496/// The server derives the wire command selector and fixed parameter area from
497/// these variants. Arbitrary parameter bytes are intentionally unavailable at
498/// this stateful command boundary.
499#[derive(Clone, Debug, Eq, PartialEq)]
500pub enum MultimediaTransmitControl {
501    FreezePicture,
502    FastPictureUpdate {
503        first_gob: u32,
504        gob_count: u32,
505    },
506    FastGobUpdate {
507        first_gob: u32,
508        gob_count: u32,
509    },
510    FastMacroblockUpdate {
511        first_gob: u32,
512        first_macroblock: u32,
513        macroblock_count: u32,
514    },
515    LostPicture {
516        picture_number: u32,
517        long_term_picture_index: u32,
518    },
519    LostPartialPicture {
520        picture_number: u32,
521        long_term_picture_index: u32,
522        first_macroblock: u32,
523        macroblock_count: u32,
524    },
525    /// Requests recovery from at most four prior picture references.
526    RecoveryReferencePicture {
527        pictures: VideoPictureReferences,
528    },
529    TemporalSpatialTradeoff {
530        value: u32,
531    },
532}
533
534/// Picture identity carried by recovery feedback.
535#[derive(Clone, Copy, Debug, Eq, PartialEq)]
536pub struct VideoPictureReference {
537    pub picture_number: u32,
538    pub long_term_picture_index: u32,
539}
540
541/// A recovery request's bounded ordered picture-reference list.
542#[derive(Clone, Debug, Eq, PartialEq)]
543pub struct VideoPictureReferences(Box<[VideoPictureReference]>);
544
545impl VideoPictureReferences {
546    /// Collects at most five items before rejecting a list beyond the wire
547    /// capacity, so even an unbounded iterator cannot cause unbounded storage.
548    pub fn new(
549        pictures: impl IntoIterator<Item = VideoPictureReference>,
550    ) -> Result<Self, ServerError> {
551        let pictures = pictures.into_iter().take(5).collect::<Vec<_>>();
552        if pictures.len() > 4 {
553            return Err(ServerError::InvalidMultimediaTransmitControl(
554                "recovery picture count exceeds four",
555            ));
556        }
557        Ok(Self(pictures.into_boxed_slice()))
558    }
559
560    /// Returns picture identities in wire order.
561    pub fn as_slice(&self) -> &[VideoPictureReference] {
562        &self.0
563    }
564}
565
566impl TryFrom<Vec<VideoPictureReference>> for VideoPictureReferences {
567    type Error = ServerError;
568
569    fn try_from(pictures: Vec<VideoPictureReference>) -> Result<Self, Self::Error> {
570        Self::new(pictures)
571    }
572}
573
574impl Default for ServerConfig {
575    fn default() -> Self {
576        Self {
577            bind: SocketAddr::from(([0, 0, 0, 0], 2000)),
578            signaling_qos: SignalingQos::default(),
579            advertised_address: Ipv4Addr::LOCALHOST,
580            advertised_ipv6_address: None,
581            server_name: "sccp-protocol".to_string(),
582            keepalive_seconds: 30,
583            secondary_keepalive_seconds: 30,
584            signaling_servers: Vec::new(),
585            registration_tokens: RegistrationTokenPolicy::default(),
586            firmware_version: String::new(),
587            dial_terminator: Digit::Pound,
588            record_dial_terminator: false,
589            call_answer_order: CallSelectionOrder::OldestFirst,
590            timezone_offset_minutes: 0,
591            date_template: Default::default(),
592            anonymous_hotline: None,
593        }
594    }
595}
596
597#[derive(Clone, Debug, Eq, PartialEq)]
598/// Output emitted by the running server to its integration adapter.
599///
600/// The receiver returned by a server constructor is the sole event stream for
601/// every accepted connection. Consumers should drain it continuously and use
602/// the device ID carried by [`Event::Device`] rather than inferring ownership
603/// from event order. They should also retain the generation from the latest
604/// registration and reject later-delivered events from replaced sessions.
605pub enum Event {
606    SessionError {
607        peer: SocketAddr,
608        error: String,
609    },
610    /// A malformed non-registration message was discarded while the session
611    /// remained usable.
612    ProtocolWarning {
613        peer: SocketAddr,
614        /// Registered device identity, or `None` before registration.
615        device_id: Option<DeviceId>,
616        message_id: u32,
617        error: String,
618    },
619    Device(DeviceEvent),
620}
621
622impl Event {
623    pub fn device(
624        device_id: DeviceId,
625        session_generation: SessionGeneration,
626        event: DeviceEventKind,
627    ) -> Self {
628        Self::Device(DeviceEvent::new(device_id, session_generation, event))
629    }
630}
631
632/// One station-scoped item in the server event stream.
633#[derive(Clone, Debug, Eq, PartialEq)]
634pub struct DeviceEvent {
635    pub device_id: DeviceId,
636    /// Identifies the connection that produced this event across reconnects.
637    pub session_generation: SessionGeneration,
638    pub event: DeviceEventKind,
639}
640
641impl DeviceEvent {
642    pub fn new(
643        device_id: DeviceId,
644        session_generation: SessionGeneration,
645        event: DeviceEventKind,
646    ) -> Self {
647        Self {
648            device_id,
649            session_generation,
650            event,
651        }
652    }
653}
654
655/// State transitions and handset input produced by one station session.
656///
657/// Call-bearing variants use the server-owned [`CallId`] rather than the raw
658/// wire reference. Media success and failure variants are emitted only after
659/// correlation against the current request generation.
660#[derive(Clone, Debug, Eq, PartialEq)]
661pub enum DeviceEventKind {
662    Registered(DeviceRegistration),
663    Disconnected {},
664    Capabilities {
665        capabilities: StationMediaCapabilities,
666    },
667    OffHook {
668        call_id: CallId,
669        line_instance: LineInstance,
670    },
671    OnHook {
672        call_id: CallId,
673        line_instance: LineInstance,
674    },
675    Digit {
676        call_id: CallId,
677        digit: Digit,
678    },
679    EnblocCall {
680        call_id: CallId,
681        line_instance: LineInstance,
682        number: String,
683    },
684    SpeedDial {
685        call_id: CallId,
686        line_instance: LineInstance,
687        number: String,
688        await_further_digits: bool,
689    },
690    SoftKey {
691        /// Active call when the key is call-scoped.
692        call_id: Option<CallId>,
693        line_instance: LineInstance,
694        soft_key: SoftKey,
695    },
696    LineButton {
697        line_instance: LineInstance,
698        call_id: Option<CallId>,
699    },
700    HookFlash {
701        call_id: Option<CallId>,
702        line_instance: LineInstance,
703    },
704    FeatureButton {
705        instance: LineInstance,
706    },
707    DoNotDisturbButton {
708        instance: LineInstance,
709    },
710    MobilityButton {
711        instance: LineInstance,
712    },
713    /// Press of a configured voicemail button, resolved to an exact line and
714    /// addressable handset call before application dispatch.
715    VoicemailButton {
716        call_id: CallId,
717        line_instance: LineInstance,
718    },
719    ParkingLotButton {
720        /// Feature-button instance, distinct from the call line instance.
721        instance: LineInstance,
722        /// Active call associated with the press, when any.
723        call_id: Option<CallId>,
724        line_instance: LineInstance,
725    },
726    ParkingMenuSelection {
727        lot: String,
728        slot: u32,
729    },
730    PhoneServiceResponse {
731        response: PhoneServiceEvent,
732    },
733    ConferenceListAction {
734        action: ConferenceListAction,
735    },
736    /// The receive-channel acknowledgement matched the current request.
737    ReceiveChannelOpened {
738        call_id: CallId,
739        status: MediaStatus,
740        endpoint: MediaEndpoint,
741    },
742    MultimediaReceiveChannelOpened {
743        call_id: CallId,
744        codec: Codec,
745        endpoint: MediaEndpointAddress,
746        passthrough_party_id: PassthroughPartyId,
747    },
748    MultimediaReceiveChannelFailed {
749        call_id: CallId,
750        codec: Codec,
751        status: MediaStatus,
752        endpoint: MediaEndpointAddress,
753        passthrough_party_id: PassthroughPartyId,
754    },
755    MultimediaReceiveChannelTimedOut {
756        call_id: CallId,
757        codec: Codec,
758        passthrough_party_id: PassthroughPartyId,
759    },
760    MultimediaTransmitStarted {
761        call_id: CallId,
762        codec: Codec,
763        endpoint: MediaEndpointAddress,
764        passthrough_party_id: PassthroughPartyId,
765    },
766    MultimediaTransmitFailed {
767        call_id: CallId,
768        codec: Codec,
769        status: MediaStatus,
770        endpoint: MediaEndpointAddress,
771        passthrough_party_id: PassthroughPartyId,
772    },
773    MultimediaTransmitTimedOut {
774        call_id: CallId,
775        codec: Codec,
776        passthrough_party_id: PassthroughPartyId,
777    },
778    /// The transmit half of a coupled outbound media transaction became open
779    /// when the station acknowledged the matching receive request.
780    ///
781    /// The adjacent StartMediaTransmission request may omit its separate
782    /// acknowledgement. This event is
783    /// deliberately distinct from [`DeviceEventKind::TransmitChannelStarted`]: callers
784    /// can preserve the acknowledgement relationship while settling
785    /// both halves of the one coupled transaction exactly once.
786    TransmitChannelImplied {
787        call_id: CallId,
788        endpoint: MediaEndpoint,
789    },
790    /// The transmit-channel acknowledgement matched the current request.
791    TransmitChannelStarted {
792        call_id: CallId,
793        status: MediaStatus,
794        endpoint: MediaEndpoint,
795    },
796    HandsetAcknowledgementTimedOut {
797        call_id: CallId,
798        acknowledgement: HandsetAcknowledgement,
799    },
800    MediaTransmissionFailed {
801        call_id: CallId,
802        status: MediaStatus,
803        endpoint: MediaEndpoint,
804    },
805    MulticastReceptionStarted {
806        conference_id: ConferenceId,
807        call_id: CallId,
808        route: MulticastMediaRoute,
809    },
810    MulticastReceptionFailed {
811        conference_id: ConferenceId,
812        call_id: CallId,
813        status: MediaStatus,
814    },
815    MulticastReceptionTimedOut {
816        conference_id: ConferenceId,
817        call_id: CallId,
818    },
819    MulticastTransmissionStarted {
820        conference_id: ConferenceId,
821        call_id: CallId,
822        route: MulticastMediaRoute,
823    },
824    MulticastTransmissionFailed {
825        conference_id: ConferenceId,
826        call_id: CallId,
827        status: MediaStatus,
828        address: IpAddr,
829        port: u16,
830    },
831    /// A requested statistics response was correlated and retained.
832    ConnectionStatisticsCollected {
833        snapshot: MediaStatisticsSnapshot,
834    },
835    Alarm {
836        severity: AlarmSeverity,
837        text: String,
838        parameters: Option<[u32; 2]>,
839    },
840    XmlAlarm {
841        telemetry: PhoneAlarmTelemetry,
842    },
843    LocationInformation {
844        telemetry: PhoneLocationTelemetry,
845    },
846    HeadsetStatusChanged {
847        enabled: bool,
848    },
849    MediaPathChanged {
850        path: crate::message::values::MediaPathId,
851        event: crate::message::values::MediaPathEvent,
852    },
853    /// A well-framed client message has no server-side behavior yet.
854    UnhandledMessage {
855        message: ClientMessage,
856    },
857}
858
859/// Station acknowledgement whose correlation deadline expired.
860#[derive(Clone, Copy, Debug, Eq, PartialEq)]
861pub enum HandsetAcknowledgement {
862    OpenReceiveChannel,
863    StartMediaTransmission,
864}
865
866/// One station-targeted operation submitted through [`ServerHandle`].
867///
868/// Construction does not consult live session state; target and call
869/// availability are checked when the running server dispatches the action.
870#[derive(Clone, Debug)]
871pub struct Command {
872    pub device_id: DeviceId,
873    pub action: CommandAction,
874}
875
876impl Command {
877    pub fn new(device_id: DeviceId, action: CommandAction) -> Self {
878        Self { device_id, action }
879    }
880}
881
882/// Operation applied to the target station session in command-queue order.
883///
884/// Call-scoped actions resolve the server-owned [`CallId`] to the current wire
885/// reference inside the session. Actions for stale calls are ignored, except
886/// that [`Self::CloseCall`] also records an early cancellation so it can retire
887/// an incoming offer that has not reached the session yet.
888///
889/// Outbound presentation normally progresses from [`Self::BeginCall`] through
890/// proceeding or ringing to [`Self::CommitOutboundCall`]. Media actions allocate
891/// a fresh request identity for each generation; close and stop actions retire
892/// that identity so a late acknowledgement cannot settle replacement media.
893#[derive(Clone, Debug)]
894pub enum CommandAction {
895    /// Create local station state and off-hook presentation for an outbound
896    /// call whose adapter-side identity is already allocated.
897    BeginCall {
898        line_instance: LineInstance,
899        call_id: CallId,
900        codec: Codec,
901    },
902    /// Put a held source call into transfer mode and create its consultation
903    /// call as the active off-hook presentation.
904    BeginTransfer {
905        source_call_id: CallId,
906        consultation_line_instance: LineInstance,
907        consultation_call_id: CallId,
908        codec: Codec,
909    },
910    SetCallInfo {
911        call_id: CallId,
912        info: CallInfo,
913    },
914    CommitOutboundCall {
915        call_id: CallId,
916        info: CallInfo,
917    },
918    PresentOutboundProceeding {
919        call_id: CallId,
920        info: CallInfo,
921    },
922    PresentOutboundRinging {
923        call_id: CallId,
924        info: CallInfo,
925    },
926    SetCallState {
927        call_id: CallId,
928        state: CallState,
929    },
930    SetCallSelected {
931        call_id: CallId,
932        selected: bool,
933    },
934    DisplayPrompt {
935        call_id: CallId,
936        timeout_seconds: u32,
937        text: String,
938    },
939    ClearPrompt {
940        call_id: CallId,
941    },
942    SetStatusMessage {
943        message: HandsetStatusMessage,
944        beep: bool,
945    },
946    SetMicrophoneMode {
947        enabled: bool,
948    },
949    SetRecordingStatus {
950        call_id: CallId,
951        active: bool,
952    },
953    ResetDevice {
954        reset_type: ResetType,
955    },
956    SetMwi {
957        line_instance: LineInstance,
958        enabled: bool,
959    },
960    /// Replace all three forwarding destinations displayed for a line.
961    ///
962    /// `None` clears that forwarding kind; the server retains the complete
963    /// triple for subsequent station queries.
964    SetForwardStatus {
965        line_instance: LineInstance,
966        forward_all: Option<String>,
967        forward_busy: Option<String>,
968        forward_no_answer: Option<String>,
969    },
970    SetFeatureStatus {
971        instance: LineInstance,
972        enabled: bool,
973    },
974    SetDoNotDisturbStatus {
975        instance: LineInstance,
976        mode: DoNotDisturbMode,
977        button_mode: DoNotDisturbButtonMode,
978    },
979    /// Install or remove the temporary line appearance owned by a mobility
980    /// button, rebuilding the station button template atomically.
981    SetMobilityAppearance {
982        mobility_instance: LineInstance,
983        appearance: Option<LineAppearance>,
984    },
985    SetBlfStatus {
986        instance: LineInstance,
987        state: BlfState,
988        caller: Option<BlfCallerInfo>,
989    },
990    ShowParkingMenu {
991        instance: LineInstance,
992        transaction_id: TransactionId,
993        lot: String,
994        calls: Vec<ParkingMenuEntry>,
995    },
996    ShowConferenceList {
997        call_id: CallId,
998        conference_id: ConferenceId,
999        participants: Vec<ConferenceListEntry>,
1000    },
1001    ShowConferenceParticipantActions {
1002        call_id: CallId,
1003        conference_id: ConferenceId,
1004        participant: ConferenceListEntry,
1005        removable: bool,
1006        demotable: bool,
1007    },
1008    ShowTextService {
1009        line_instance: LineInstance,
1010        call_reference: CallReference,
1011        transaction_id: TransactionId,
1012        priority: PhoneServicePriority,
1013        document: CiscoIpPhoneText,
1014    },
1015    /// Send an input-service form whose response is emitted as
1016    /// [`DeviceEventKind::PhoneServiceResponse`].
1017    ShowInputService {
1018        line_instance: LineInstance,
1019        call_reference: CallReference,
1020        application_id: ApplicationId,
1021        transaction_id: TransactionId,
1022        priority: PhoneServicePriority,
1023        document: CiscoIpPhoneInput,
1024    },
1025    ExecutePhoneActions {
1026        line_instance: LineInstance,
1027        call_reference: CallReference,
1028        application_id: ApplicationId,
1029        transaction_id: TransactionId,
1030        priority: PhoneServicePriority,
1031        document: CiscoIpPhoneExecute,
1032    },
1033    ShowImageService {
1034        line_instance: LineInstance,
1035        call_reference: CallReference,
1036        application_id: ApplicationId,
1037        transaction_id: TransactionId,
1038        priority: PhoneServicePriority,
1039        document: PhoneImageDocument,
1040    },
1041    ShowStatusService {
1042        line_instance: LineInstance,
1043        call_reference: CallReference,
1044        application_id: ApplicationId,
1045        transaction_id: TransactionId,
1046        priority: PhoneServicePriority,
1047        document: PhoneStatusDocument,
1048    },
1049    SetBackgroundImage {
1050        transaction_id: TransactionId,
1051        document: CiscoIpPhoneSetBackground,
1052    },
1053    PreviewBackgroundImage {
1054        transaction_id: TransactionId,
1055        document: CiscoIpPhoneSetBackgroundPreview,
1056    },
1057    SetRingtone {
1058        transaction_id: TransactionId,
1059        document: CiscoIpPhoneSetRingTone,
1060    },
1061    StartTone {
1062        call_id: CallId,
1063        tone: Tone,
1064    },
1065    /// Start one or more conference announcements with an explicit participant
1066    /// hearing mask and playback mode.
1067    StartAnnouncement {
1068        conference_id: ConferenceId,
1069        announcements: Vec<AnnouncementEntry>,
1070        /// Marks the final request in an acknowledgement-delimited sequence.
1071        end_of_ack: bool,
1072        participant_ids: Vec<ParticipantId>,
1073        /// Bit mask selecting which listed participants hear the announcement.
1074        hearing_participant_mask: u32,
1075        /// Protocol playback-mode value retained for station interpretation.
1076        play_mode: u32,
1077    },
1078    StopAnnouncement {
1079        conference_id: ConferenceId,
1080    },
1081    AnnouncementFinish {
1082        conference_id: ConferenceId,
1083        play_status: u32,
1084    },
1085    StartRinging {
1086        call_id: CallId,
1087    },
1088    StopRinging {
1089        call_id: CallId,
1090    },
1091    /// Ask the station to allocate its receive channel and begin a correlated
1092    /// media transaction.
1093    OpenReceiveChannel {
1094        call_id: CallId,
1095        /// Optional RTP source restriction. `None` accepts media from any
1096        /// source and is encoded as the SCCP wildcard endpoint `0.0.0.0:0`.
1097        source: Option<MediaEndpoint>,
1098        codec: Codec,
1099        packet_ms: u32,
1100        max_frames_per_packet: u32,
1101        dtmf_mode: DtmfMode,
1102        audio_processing: AudioProcessingPolicy,
1103    },
1104    /// Requires a connected call and an exact advertised receive capability;
1105    /// replacing a live generation writes its close first.
1106    OpenMultimediaReceiveChannel {
1107        call_id: CallId,
1108        descriptor: MultimediaReceiveDescriptor,
1109    },
1110    CloseMultimediaReceiveChannel {
1111        call_id: CallId,
1112    },
1113    /// Requires a connected call and an exact advertised transmit capability;
1114    /// replacing a live generation writes its stop first.
1115    StartMultimediaTransmission {
1116        call_id: CallId,
1117        descriptor: MultimediaTransmitDescriptor,
1118    },
1119    StopMultimediaTransmission {
1120        call_id: CallId,
1121    },
1122    /// Limits the exact live station video encoder identified by its current
1123    /// passthrough token.
1124    SetMultimediaTransmitBitRate {
1125        call_id: CallId,
1126        passthrough_party_id: PassthroughPartyId,
1127        maximum_bit_rate: u32,
1128    },
1129    /// Reports a bit-rate change for the exact live station video encoder.
1130    NotifyMultimediaTransmitBitRate {
1131        call_id: CallId,
1132        passthrough_party_id: PassthroughPartyId,
1133        maximum_bit_rate: u32,
1134    },
1135    /// Applies typed feedback to the exact live station video encoder.
1136    ControlMultimediaTransmission {
1137        call_id: CallId,
1138        passthrough_party_id: PassthroughPartyId,
1139        control: MultimediaTransmitControl,
1140    },
1141    /// Open both directions of an outbound media path in one session-writer
1142    /// transaction. The two SCCP frames are written ORC then SMT without a
1143    /// command-queue or acknowledgement boundary between them.
1144    OpenOutboundMedia {
1145        call_id: CallId,
1146        source: Option<MediaEndpoint>,
1147        endpoint: MediaEndpoint,
1148        codec: Codec,
1149        packet_ms: u32,
1150        max_frames_per_packet: u32,
1151        dtmf_mode: DtmfMode,
1152        audio_processing: AudioProcessingPolicy,
1153        traffic_class: MediaTrafficClass,
1154    },
1155    /// Close the station receive leg and retire its pending acknowledgement.
1156    CloseReceiveChannel {
1157        call_id: CallId,
1158    },
1159    StartMedia {
1160        call_id: CallId,
1161        endpoint: MediaEndpoint,
1162        dtmf_mode: DtmfMode,
1163        audio_processing: AudioProcessingPolicy,
1164        traffic_class: MediaTrafficClass,
1165    },
1166    StartMulticastReception {
1167        conference_id: ConferenceId,
1168        call_id: CallId,
1169        route: MulticastMediaRoute,
1170        echo_cancellation: EchoCancellation,
1171        g723_bitrate: G723BitRate,
1172    },
1173    StopMulticastReception {
1174        conference_id: ConferenceId,
1175        call_id: CallId,
1176    },
1177    StartMulticastTransmission {
1178        conference_id: ConferenceId,
1179        call_id: CallId,
1180        route: MulticastMediaRoute,
1181        precedence: u32,
1182        silence_suppression: SilenceSuppression,
1183        max_frames_per_packet: u32,
1184        g723_bitrate: G723BitRate,
1185    },
1186    StopMulticastTransmission {
1187        conference_id: ConferenceId,
1188        call_id: CallId,
1189    },
1190    /// Stop the station transmit leg and retire its pending acknowledgement.
1191    StopMedia {
1192        call_id: CallId,
1193    },
1194    /// Tear down station media and presentation, request final statistics when
1195    /// applicable, and retire the call identity.
1196    CloseCall {
1197        call_id: CallId,
1198    },
1199    DisconnectDevice {},
1200}
1201
1202/// Failure returned by server construction, command submission, session I/O,
1203/// or stateful command validation.
1204///
1205/// Queue-admission failures do not imply that an earlier command failed.
1206/// [`Self::CommandWrite`] and [`Self::CommandAcknowledgementTimeout`] are
1207/// specific to [`ServerHandle::send_confirmed`]; protocol-level media outcomes
1208/// instead arrive through [`Event`].
1209#[derive(Debug, Error)]
1210pub enum ServerError {
1211    #[error("failed to bind SCCP server: {0}")]
1212    Bind(#[source] std::io::Error),
1213    #[error("SCCP server I/O failed: {0}")]
1214    Io(#[from] std::io::Error),
1215    #[error("SCCP protocol error: {0}")]
1216    Protocol(#[from] CodecError),
1217    #[error("invalid SCCP server configuration: {0}")]
1218    InvalidConfig(String),
1219    #[error("phone XML error: {0}")]
1220    PhoneXml(#[from] PhoneXmlError),
1221    #[error("device {0} is not connected")]
1222    DeviceNotConnected(DeviceId),
1223    #[error("call {0:?} does not exist")]
1224    UnknownCall(CallId),
1225    #[error("device {device} has no BLF feature button instance {instance}")]
1226    UnknownBlfButton { device: DeviceId, instance: u32 },
1227    #[error("call {call_id:?} cannot {operation} while in state {state:?}")]
1228    InvalidCallTransaction {
1229        call_id: CallId,
1230        operation: &'static str,
1231        state: CallState,
1232    },
1233    #[error("SCCP server has stopped")]
1234    Stopped,
1235    #[error("SCCP server command queue is full")]
1236    CommandQueueFull,
1237    #[error("SCCP command could not be written to the device: {0}")]
1238    CommandWrite(String),
1239    #[error("SCCP command writer acknowledgement timed out")]
1240    CommandAcknowledgementTimeout,
1241    #[error("SCCP media request identity space is exhausted")]
1242    MediaRequestIdentityExhausted,
1243    #[error("SCCP station session generation space is exhausted")]
1244    SessionGenerationExhausted,
1245    #[error("invalid multicast media policy: {0}")]
1246    InvalidMulticastMedia(&'static str),
1247    #[error("station does not advertise the requested multicast codec")]
1248    UnsupportedMulticastCodec,
1249    #[error("invalid multimedia receive policy: {0}")]
1250    InvalidMultimediaReceive(&'static str),
1251    #[error("station does not advertise the requested video receive capability")]
1252    UnsupportedMultimediaReceive,
1253    #[error("invalid multimedia transmit policy: {0}")]
1254    InvalidMultimediaTransmit(&'static str),
1255    #[error("station does not advertise the requested video transmit capability")]
1256    UnsupportedMultimediaTransmit,
1257    #[error("invalid multimedia transmit control: {0}")]
1258    InvalidMultimediaTransmitControl(&'static str),
1259    #[error(
1260        "call {call_id:?} has no open multimedia transmit stream with passthrough token {passthrough_party_id}"
1261    )]
1262    StaleMultimediaTransmitControl {
1263        call_id: CallId,
1264        passthrough_party_id: PassthroughPartyId,
1265    },
1266    #[error("{message} is a control/service-node message, not a station command")]
1267    InvalidStationCommand { message: &'static str },
1268}
1269
1270impl ServerError {
1271    const fn is_nonfatal_command_rejection(&self) -> bool {
1272        matches!(
1273            self,
1274            Self::InvalidCallTransaction { .. }
1275                | Self::UnknownBlfButton { .. }
1276                | Self::InvalidStationCommand { .. }
1277                | Self::InvalidMulticastMedia(_)
1278                | Self::UnsupportedMulticastCodec
1279                | Self::InvalidMultimediaReceive(_)
1280                | Self::UnsupportedMultimediaReceive
1281                | Self::InvalidMultimediaTransmit(_)
1282                | Self::UnsupportedMultimediaTransmit
1283                | Self::InvalidMultimediaTransmitControl(_)
1284                | Self::StaleMultimediaTransmitControl { .. }
1285        )
1286    }
1287}
1288
1289/// Cloneable command and management endpoint for a running [`Server`].
1290///
1291/// The handle does not drive I/O itself: [`Server::run`] must remain active.
1292/// Clones share call-ID allocation, retained media statistics, and the bounded
1293/// command queue. Dropping the last handle closes that queue and lets the run
1294/// loop perform its normal session shutdown.
1295#[derive(Clone, Debug)]
1296pub struct ServerHandle {
1297    command_tx: mpsc::Sender<ServerCommand>,
1298    next_call_id: Arc<AtomicU64>,
1299    latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
1300    call_answer_order: Arc<RwLock<CallSelectionOrder>>,
1301}
1302
1303/// The station definitions changed by one atomic server reconfiguration.
1304///
1305/// Only connected devices in `changed` or `removed` are disconnected. Added
1306/// devices have no session to disrupt, while definitions absent from every
1307/// list keep their live session and calls.
1308#[derive(Clone, Debug, Default, Eq, PartialEq)]
1309pub struct ReconfigureResult {
1310    pub added: Vec<DeviceId>,
1311    pub changed: Vec<DeviceId>,
1312    pub removed: Vec<DeviceId>,
1313}
1314
1315impl ReconfigureResult {
1316    pub fn is_unchanged(&self) -> bool {
1317        self.added.is_empty() && self.changed.is_empty() && self.removed.is_empty()
1318    }
1319
1320    fn disconnected_devices(&self) -> impl Iterator<Item = &DeviceId> {
1321        self.changed.iter().chain(&self.removed)
1322    }
1323}
1324
1325impl ServerHandle {
1326    /// Applies to future answer requests that omit their call reference.
1327    /// Explicit references and existing session calls are not rewritten.
1328    pub fn set_call_answer_order(&self, order: CallSelectionOrder) {
1329        *self
1330            .call_answer_order
1331            .write()
1332            .expect("SCCP call-answer-order lock poisoned") = order;
1333    }
1334
1335    /// Return the latest fully correlated statistics response for a device.
1336    ///
1337    /// Snapshots survive call and session teardown until a newer response for
1338    /// that device replaces them or the server is dropped.
1339    pub fn latest_media_statistics(&self, device_id: &DeviceId) -> Option<MediaStatisticsSnapshot> {
1340        self.latest_media_statistics
1341            .read()
1342            .expect("SCCP media-statistics lock poisoned")
1343            .get(device_id)
1344            .cloned()
1345    }
1346
1347    /// Clone every retained per-device snapshot, releasing the internal lock before a caller
1348    /// sorts, filters, or formats management output.
1349    pub fn media_statistics(&self) -> Vec<(DeviceId, MediaStatisticsSnapshot)> {
1350        self.latest_media_statistics
1351            .read()
1352            .expect("SCCP media-statistics lock poisoned")
1353            .iter()
1354            .map(|(device_id, snapshot)| (device_id.clone(), snapshot.clone()))
1355            .collect()
1356    }
1357
1358    /// Enqueue a station command, waiting for capacity in the server queue.
1359    ///
1360    /// Success confirms queue admission only. The command may subsequently be
1361    /// discarded if the target session retired, and any station response is
1362    /// reported separately through [`Event`]. Use [`Self::send_confirmed`] when
1363    /// adapter resource lifetime depends on completion of the stream write.
1364    pub async fn send(&self, command: Command) -> Result<(), ServerError> {
1365        self.command_tx
1366            .send(ServerCommand::Public(Box::new(command)))
1367            .await
1368            .map_err(|_| ServerError::Stopped)
1369    }
1370
1371    /// Send a command and wait until its complete encoded frame has been
1372    /// written to the registered device's TCP stream.
1373    ///
1374    /// This is intentionally stronger than [`Self::send`], whose completion
1375    /// only means the command entered the server queue. Lifecycle-sensitive
1376    /// callers use this boundary before releasing resources that protect the
1377    /// command's on-device operation.
1378    pub async fn send_confirmed(&self, command: Command) -> Result<(), ServerError> {
1379        let expires_at = Instant::now() + ORDERING_ACKNOWLEDGEMENT_TIMEOUT;
1380        tokio::time::timeout_at(expires_at, async {
1381            let (written_tx, written_rx) = oneshot::channel();
1382            self.command_tx
1383                .send(ServerCommand::Confirmed {
1384                    command: Box::new(command),
1385                    written: written_tx,
1386                    expires_at,
1387                })
1388                .await
1389                .map_err(|_| ServerError::Stopped)?;
1390            written_rx
1391                .await
1392                .map_err(|_| ServerError::Stopped)?
1393                .map_err(ServerError::CommandWrite)
1394        })
1395        .await
1396        .map_err(|_| ServerError::CommandAcknowledgementTimeout)?
1397    }
1398
1399    /// Enqueue a command without yielding, preserving the ordering of
1400    /// synchronous channel-driver callbacks such as call followed by hangup.
1401    pub fn try_send(&self, command: Command) -> Result<(), ServerError> {
1402        self.command_tx
1403            .try_send(ServerCommand::Public(Box::new(command)))
1404            .map_err(|error| match error {
1405                mpsc::error::TrySendError::Full(_) => ServerError::CommandQueueFull,
1406                mpsc::error::TrySendError::Closed(_) => ServerError::Stopped,
1407            })
1408    }
1409
1410    /// Allocate a call ID and enqueue an ordinarily ringing incoming offer.
1411    ///
1412    /// The returned identity is stable across all later commands and handset
1413    /// events for the offer. A failed enqueue still consumes the reserved ID.
1414    pub async fn offer_incoming_call(
1415        &self,
1416        device_id: DeviceId,
1417        line_instance: LineInstance,
1418        info: CallInfo,
1419    ) -> Result<CallId, ServerError> {
1420        let call_id = self.reserve_call_id();
1421        self.offer_incoming_call_with_id(device_id, line_instance, call_id, info)
1422            .await?;
1423        Ok(call_id)
1424    }
1425
1426    /// Reserve a call ID before exposing a call to a protocol session.
1427    ///
1428    /// Channel-driver adapters use this to install all private channel state
1429    /// before the handset can answer the subsequent offer.
1430    pub fn reserve_call_id(&self) -> CallId {
1431        CallId(self.next_call_id.fetch_add(1, Ordering::Relaxed))
1432    }
1433
1434    /// Offer an incoming call using an ID previously returned by
1435    /// [`Self::reserve_call_id`].
1436    pub async fn offer_incoming_call_with_id(
1437        &self,
1438        device_id: DeviceId,
1439        line_instance: LineInstance,
1440        call_id: CallId,
1441        info: CallInfo,
1442    ) -> Result<(), ServerError> {
1443        self.offer_incoming_call_with_id_and_ring(device_id, line_instance, call_id, info, true)
1444            .await
1445    }
1446
1447    pub async fn offer_incoming_call_with_id_and_ring(
1448        &self,
1449        device_id: DeviceId,
1450        line_instance: LineInstance,
1451        call_id: CallId,
1452        info: CallInfo,
1453        audible_ring: bool,
1454    ) -> Result<(), ServerError> {
1455        self.offer_incoming_call_with_id_and_ringer(
1456            device_id,
1457            line_instance,
1458            call_id,
1459            info,
1460            audible_ring.then_some(IncomingRing::default()),
1461        )
1462        .await
1463    }
1464
1465    /// Enqueue an incoming offer with explicit audible presentation.
1466    ///
1467    /// `None` creates a silent offer. `Some` applies the supplied ring mode and
1468    /// duration before selecting the incoming-call soft-key state.
1469    pub async fn offer_incoming_call_with_id_and_ringer(
1470        &self,
1471        device_id: DeviceId,
1472        line_instance: LineInstance,
1473        call_id: CallId,
1474        info: CallInfo,
1475        ringer: Option<IncomingRing>,
1476    ) -> Result<(), ServerError> {
1477        self.command_tx
1478            .send(ServerCommand::OfferIncoming {
1479                device_id,
1480                line_instance,
1481                call_id,
1482                info,
1483                ringer,
1484            })
1485            .await
1486            .map_err(|_| ServerError::Stopped)?;
1487        Ok(())
1488    }
1489
1490    /// Enqueue an incoming offer without yielding. Channel drivers should use
1491    /// this from their synchronous call callback so a following hangup cannot
1492    /// overtake the offer.
1493    pub fn try_offer_incoming_call_with_id(
1494        &self,
1495        device_id: DeviceId,
1496        line_instance: LineInstance,
1497        call_id: CallId,
1498        info: CallInfo,
1499    ) -> Result<(), ServerError> {
1500        self.try_offer_incoming_call_with_id_and_ring(device_id, line_instance, call_id, info, true)
1501    }
1502
1503    /// Non-blocking form of [`Self::offer_incoming_call_with_id_and_ring`].
1504    ///
1505    /// Returns [`ServerError::CommandQueueFull`] without changing session state
1506    /// when immediate queue capacity is unavailable.
1507    pub fn try_offer_incoming_call_with_id_and_ring(
1508        &self,
1509        device_id: DeviceId,
1510        line_instance: LineInstance,
1511        call_id: CallId,
1512        info: CallInfo,
1513        audible_ring: bool,
1514    ) -> Result<(), ServerError> {
1515        self.try_offer_incoming_call_with_id_and_ringer(
1516            device_id,
1517            line_instance,
1518            call_id,
1519            info,
1520            audible_ring.then_some(IncomingRing::default()),
1521        )
1522    }
1523
1524    pub fn try_offer_incoming_call_with_id_and_ringer(
1525        &self,
1526        device_id: DeviceId,
1527        line_instance: LineInstance,
1528        call_id: CallId,
1529        info: CallInfo,
1530        ringer: Option<IncomingRing>,
1531    ) -> Result<(), ServerError> {
1532        self.command_tx
1533            .try_send(ServerCommand::OfferIncoming {
1534                device_id,
1535                line_instance,
1536                call_id,
1537                info,
1538                ringer,
1539            })
1540            .map_err(|error| match error {
1541                mpsc::error::TrySendError::Full(_) => ServerError::CommandQueueFull,
1542                mpsc::error::TrySendError::Closed(_) => ServerError::Stopped,
1543            })
1544    }
1545
1546    /// Request orderly server shutdown.
1547    ///
1548    /// Success means the request entered the queue. The owner must still await
1549    /// the [`Server::run`] future to know that it stopped accepting streams and
1550    /// issued disconnects to every registered session.
1551    pub async fn shutdown(&self) -> Result<(), ServerError> {
1552        self.command_tx
1553            .send(ServerCommand::Shutdown)
1554            .await
1555            .map_err(|_| ServerError::Stopped)
1556    }
1557
1558    /// Atomically replace the configured station definitions. Only connected
1559    /// stations whose definition changed or was removed are asked to register
1560    /// again; unchanged live sessions and calls are preserved. Success means
1561    /// the replacement was committed and disconnect requests were queued, not
1562    /// that every affected transport has already closed.
1563    pub async fn reconfigure(
1564        &self,
1565        definitions: impl IntoIterator<Item = DeviceDefinition>,
1566    ) -> Result<ReconfigureResult, ServerError> {
1567        self.reconfigure_affected(definitions, []).await
1568    }
1569
1570    /// Atomically replaces station definitions and reconnects the explicit
1571    /// set in addition to stations whose wire definition changed. This lets a
1572    /// higher-level configuration owner apply line or global policy changes
1573    /// whose effects are not represented in [`DeviceDefinition`].
1574    pub async fn reconfigure_affected(
1575        &self,
1576        definitions: impl IntoIterator<Item = DeviceDefinition>,
1577        affected: impl IntoIterator<Item = DeviceId>,
1578    ) -> Result<ReconfigureResult, ServerError> {
1579        let mut by_id = HashMap::new();
1580        for definition in definitions {
1581            definition.validate()?;
1582            by_id.insert(definition.id.clone(), definition);
1583        }
1584        let (applied_tx, applied_rx) = oneshot::channel();
1585        self.command_tx
1586            .send(ServerCommand::Reconfigure {
1587                definitions: by_id,
1588                affected: affected.into_iter().collect(),
1589                applied: applied_tx,
1590            })
1591            .await
1592            .map_err(|_| ServerError::Stopped)?;
1593        applied_rx.await.map_err(|_| ServerError::Stopped)
1594    }
1595
1596    /// Commits station definitions and unknown-device admission as one server
1597    /// transaction before any affected session is disconnected.
1598    pub async fn reconfigure_station_policy(
1599        &self,
1600        definitions: impl IntoIterator<Item = DeviceDefinition>,
1601        affected: impl IntoIterator<Item = DeviceId>,
1602        anonymous_hotline: Option<AnonymousHotlineDefinition>,
1603    ) -> Result<ReconfigureResult, ServerError> {
1604        let mut by_id = HashMap::new();
1605        for definition in definitions {
1606            definition.validate()?;
1607            by_id.insert(definition.id.clone(), definition);
1608        }
1609        let (applied_tx, applied_rx) = oneshot::channel();
1610        self.command_tx
1611            .send(ServerCommand::ReconfigureStationPolicy {
1612                definitions: by_id,
1613                affected: affected.into_iter().collect(),
1614                anonymous_hotline,
1615                applied: applied_tx,
1616            })
1617            .await
1618            .map_err(|_| ServerError::Stopped)?;
1619        applied_rx.await.map_err(|_| ServerError::Stopped)
1620    }
1621
1622    /// Replace the unknown-device guest template for future registrations.
1623    /// A changed policy disconnects only sessions that were admitted through
1624    /// the previous anonymous template; configured sessions are untouched. The
1625    /// returned count is the number of such sessions asked to disconnect.
1626    pub async fn reconfigure_anonymous_hotline(
1627        &self,
1628        definition: Option<AnonymousHotlineDefinition>,
1629    ) -> Result<usize, ServerError> {
1630        let (applied_tx, applied_rx) = oneshot::channel();
1631        self.command_tx
1632            .send(ServerCommand::ReconfigureAnonymousHotline {
1633                definition,
1634                applied: applied_tx,
1635            })
1636            .await
1637            .map_err(|_| ServerError::Stopped)?;
1638        applied_rx.await.map_err(|_| ServerError::Stopped)
1639    }
1640}
1641
1642/// Stateful owner of station admission, registration, command dispatch, and
1643/// event correlation.
1644///
1645/// Construction is inert: callers must poll [`Self::run`]. The server owns its
1646/// listener or injected-ingress receiver and all registered session routing;
1647/// integration code normally retains only the returned [`ServerHandle`] and
1648/// event receiver after spawning the run future. Dropping the `Server` future
1649/// directly is abrupt, so normal shutdown should use [`ServerHandle::shutdown`]
1650/// and then await `run`.
1651#[derive(Debug)]
1652pub struct Server {
1653    listener: Option<TcpListener>,
1654    accepted_rx: mpsc::Receiver<AcceptedStation>,
1655    config: Arc<ServerConfig>,
1656    anonymous_hotline: Arc<RwLock<Option<AnonymousHotlineDefinition>>>,
1657    definitions: Arc<RwLock<HashMap<DeviceId, DeviceDefinition>>>,
1658    sessions: Sessions,
1659    event_tx: mpsc::Sender<Event>,
1660    command_rx: mpsc::Receiver<ServerCommand>,
1661    next_generation: Arc<AtomicU64>,
1662    next_statistics_generation: Arc<AtomicU64>,
1663    next_call_id: Arc<AtomicU64>,
1664    latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
1665    call_answer_order: Arc<RwLock<CallSelectionOrder>>,
1666}
1667
1668type Sessions = Arc<Mutex<HashMap<DeviceId, SessionSender>>>;
1669type CommandWriteConfirmation = oneshot::Sender<Result<(), String>>;
1670
1671#[derive(Clone, Debug)]
1672struct SessionSender {
1673    generation: SessionGeneration,
1674    anonymous_hotline: bool,
1675    tx: mpsc::Sender<SessionCommand>,
1676}
1677
1678#[derive(Debug)]
1679enum ServerCommand {
1680    Public(Box<Command>),
1681    Confirmed {
1682        command: Box<Command>,
1683        written: CommandWriteConfirmation,
1684        expires_at: Instant,
1685    },
1686    OfferIncoming {
1687        device_id: DeviceId,
1688        line_instance: LineInstance,
1689        call_id: CallId,
1690        info: CallInfo,
1691        ringer: Option<IncomingRing>,
1692    },
1693    Reconfigure {
1694        definitions: HashMap<DeviceId, DeviceDefinition>,
1695        affected: HashSet<DeviceId>,
1696        applied: oneshot::Sender<ReconfigureResult>,
1697    },
1698    ReconfigureStationPolicy {
1699        definitions: HashMap<DeviceId, DeviceDefinition>,
1700        affected: HashSet<DeviceId>,
1701        anonymous_hotline: Option<AnonymousHotlineDefinition>,
1702        applied: oneshot::Sender<ReconfigureResult>,
1703    },
1704    ReconfigureAnonymousHotline {
1705        definition: Option<AnonymousHotlineDefinition>,
1706        applied: oneshot::Sender<usize>,
1707    },
1708    Shutdown,
1709}
1710
1711#[derive(Debug)]
1712enum AnonymousHotlineUpdate {
1713    Preserve,
1714    Replace(Option<AnonymousHotlineDefinition>),
1715}
1716
1717#[derive(Debug)]
1718enum SessionCommand {
1719    Public(Box<Command>),
1720    Confirmed {
1721        command: Box<Command>,
1722        written: CommandWriteConfirmation,
1723        expires_at: Instant,
1724    },
1725    OfferIncoming {
1726        line_instance: LineInstance,
1727        call_id: CallId,
1728        info: Box<CallInfo>,
1729        ringer: Option<IncomingRing>,
1730    },
1731    Disconnect,
1732}
1733
1734#[derive(Clone, Debug)]
1735struct SessionCall {
1736    call_id: CallId,
1737    wire_reference: u32,
1738    line_instance: u32,
1739    media: CallMedia,
1740    video_receive: VideoReceive,
1741    video_transmit: VideoTransmit,
1742    state: CallState,
1743    history_disposition: CallHistoryDisposition,
1744    dialed_number: String,
1745    statistics_directory_number: String,
1746    transfer_role: Option<SessionTransferRole>,
1747}
1748
1749#[derive(Clone, Debug, Default)]
1750struct VideoReceive {
1751    generation: u64,
1752    leg: Option<VideoReceiveLeg>,
1753}
1754
1755#[derive(Clone, Debug)]
1756struct VideoReceiveLeg {
1757    request: MediaRequestIdentity,
1758    conference_id: ConferenceId,
1759    codec: Codec,
1760    requested_address_type: IpAddressType,
1761    state: MediaChannelState,
1762    deadline: Option<Instant>,
1763}
1764
1765#[derive(Debug)]
1766struct ExpiredVideoReceive {
1767    call_id: CallId,
1768    codec: Codec,
1769    passthrough_party_id: PassthroughPartyId,
1770    close: ServerMessage,
1771}
1772
1773#[derive(Clone, Debug, Default)]
1774struct VideoTransmit {
1775    generation: u64,
1776    leg: Option<VideoTransmitLeg>,
1777}
1778
1779#[derive(Clone, Debug)]
1780struct VideoTransmitLeg {
1781    request: MediaRequestIdentity,
1782    conference_id: ConferenceId,
1783    codec: Codec,
1784    address_type: IpAddressType,
1785    state: MediaChannelState,
1786    deadline: Option<Instant>,
1787}
1788
1789#[derive(Debug)]
1790struct ExpiredVideoTransmit {
1791    call_id: CallId,
1792    codec: Codec,
1793    passthrough_party_id: PassthroughPartyId,
1794    stop: ServerMessage,
1795}
1796
1797#[derive(Clone, Debug)]
1798struct CallMedia {
1799    generation: u64,
1800    codec: Codec,
1801    packet_ms: u32,
1802    max_frames_per_packet: u32,
1803    receive: MediaLeg,
1804    transmit: MediaLeg,
1805    /// Exact StartMediaTransmission endpoint paired with an outstanding
1806    /// OpenReceiveChannel in one outbound NAT compatibility transaction.
1807    /// A successful matching receive acknowledgement settles both halves.
1808    coupled_transmit_endpoint: Option<MediaEndpoint>,
1809    requested: bool,
1810}
1811
1812impl CallMedia {
1813    fn new(codec: Codec) -> Self {
1814        Self {
1815            generation: 0,
1816            codec,
1817            packet_ms: DEFAULT_AUDIO_PACKET_MS,
1818            max_frames_per_packet: DEFAULT_AUDIO_MAX_FRAMES_PER_PACKET,
1819            receive: MediaLeg::default(),
1820            transmit: MediaLeg::default(),
1821            coupled_transmit_endpoint: None,
1822            requested: false,
1823        }
1824    }
1825}
1826
1827#[derive(Clone, Debug, Default)]
1828struct MediaLeg {
1829    request: Option<MediaRequestIdentity>,
1830    telephone_event_payload: u8,
1831    peer: Option<MediaEndpoint>,
1832    state: MediaChannelState,
1833    deadline: Option<Instant>,
1834}
1835
1836#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1837enum SessionTransferRole {
1838    Source { consultation_call_id: CallId },
1839    Consultation { source_call_id: CallId },
1840}
1841
1842#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1843enum MediaChannelState {
1844    #[default]
1845    Closed,
1846    Opening,
1847    Open,
1848}
1849
1850impl MediaChannelState {
1851    const fn is_open(self) -> bool {
1852        matches!(self, Self::Open)
1853    }
1854}
1855
1856fn validate_server_config(config: &ServerConfig) -> Result<(), ServerError> {
1857    if digit_character(config.dial_terminator).is_none() {
1858        return Err(ServerError::InvalidConfig(
1859            "dial terminator must be one DTMF character".into(),
1860        ));
1861    }
1862    if !(-840..=840).contains(&config.timezone_offset_minutes) {
1863        return Err(ServerError::InvalidConfig(
1864            "timezone offset must be between -840 and 840 minutes".into(),
1865        ));
1866    }
1867    if config.keepalive_seconds < 5 || config.secondary_keepalive_seconds < 5 {
1868        return Err(ServerError::InvalidConfig(
1869            "primary and secondary keepalive intervals must be at least 5 seconds".into(),
1870        ));
1871    }
1872    if config.advertised_address.is_unspecified()
1873        || config.advertised_address.is_multicast()
1874        || config
1875            .advertised_ipv6_address
1876            .is_some_and(|address| address.is_unspecified() || address.is_multicast())
1877    {
1878        return Err(ServerError::InvalidConfig(
1879            "advertised fallback addresses must be unicast".into(),
1880        ));
1881    }
1882    if !(MIN_REGISTRATION_BACKOFF..=MAX_REGISTRATION_BACKOFF)
1883        .contains(&config.registration_tokens.backoff)
1884    {
1885        return Err(ServerError::InvalidConfig(
1886            "registration-token backoff must be between 30 and 86400 seconds".into(),
1887        ));
1888    }
1889    if config.registration_tokens.server_priority == 0 {
1890        return Err(ServerError::InvalidConfig(
1891            "server priority must be nonzero".into(),
1892        ));
1893    }
1894    if config.signaling_servers.len() > crate::message::MAX_SIGNALING_SERVERS {
1895        return Err(ServerError::InvalidConfig(format!(
1896            "at most {} signaling servers may be advertised",
1897            crate::message::MAX_SIGNALING_SERVERS
1898        )));
1899    }
1900    let mut priorities = HashSet::new();
1901    for server in &config.signaling_servers {
1902        if server.priority == 0 || !priorities.insert(server.priority) {
1903            return Err(ServerError::InvalidConfig(
1904                "signaling server priorities must be nonzero and unique".into(),
1905            ));
1906        }
1907        if server.name.is_empty()
1908            || server.name.len() >= 48
1909            || server.name.chars().any(char::is_control)
1910            || server.address.is_unspecified()
1911            || server.address.is_multicast()
1912            || server.clear_port.is_none() && server.secure_port.is_none()
1913        {
1914            return Err(ServerError::InvalidConfig(
1915                "each signaling server requires a name, unicast address, and at least one port"
1916                    .into(),
1917            ));
1918        }
1919    }
1920    if !config.signaling_servers.is_empty()
1921        && !priorities.contains(&config.registration_tokens.server_priority)
1922    {
1923        return Err(ServerError::InvalidConfig(
1924            "the local server priority must occur in the advertised server list".into(),
1925        ));
1926    }
1927    config
1928        .signaling_qos
1929        .validate()
1930        .map_err(|error| ServerError::InvalidConfig(error.to_string()))
1931}
1932
1933impl Server {
1934    /// Bind the configured plain TCP endpoint and construct a server.
1935    ///
1936    /// The returned tuple contains the inert server, its cloneable command
1937    /// handle, and the sole event receiver. Call [`Self::local_addr`] after
1938    /// construction when `config.bind` used port zero, then spawn or await
1939    /// [`Self::run`]. This constructor classifies every accepted connection as
1940    /// [`StationTransport::Clear`]. Use [`Self::with_ingress`] when transport
1941    /// negotiation or multiple listeners are owned elsewhere.
1942    pub async fn bind(
1943        config: ServerConfig,
1944        definitions: impl IntoIterator<Item = DeviceDefinition>,
1945    ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>), ServerError> {
1946        validate_server_config(&config)?;
1947        let listener = TcpListener::bind(config.bind)
1948            .await
1949            .map_err(ServerError::Bind)?;
1950        if let Ok(local) = listener.local_addr() {
1951            match SignalingSocket::capture(&listener, local) {
1952                Ok(socket) => report_socket_qos(None, local, socket.apply(config.signaling_qos)),
1953                Err(error) => {
1954                    warn!(%local, %error, "unable to retain signaling listener QoS control")
1955                }
1956            }
1957        }
1958        let (server, handle, events, _) = Self::build(config, definitions, Some(listener))?;
1959        Ok((server, handle, events))
1960    }
1961
1962    /// Construct a server whose ready station streams are supplied externally.
1963    ///
1964    /// The additional [`ServerIngress`] value is cloned by clear and secure
1965    /// listener tasks. Each task completes its transport setup, preserves the
1966    /// accepted peer and local socket addresses, and submits the stream with an
1967    /// accurate [`StationTransport`] classification. The returned server has no
1968    /// bound listener, so [`Self::local_addr`] is unavailable.
1969    pub fn with_ingress(
1970        config: ServerConfig,
1971        definitions: impl IntoIterator<Item = DeviceDefinition>,
1972    ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>, ServerIngress), ServerError> {
1973        Self::build(config, definitions, None)
1974    }
1975
1976    fn build(
1977        config: ServerConfig,
1978        definitions: impl IntoIterator<Item = DeviceDefinition>,
1979        listener: Option<TcpListener>,
1980    ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>, ServerIngress), ServerError> {
1981        validate_server_config(&config)?;
1982        let mut by_id = HashMap::new();
1983        for definition in definitions {
1984            definition.validate()?;
1985            by_id.insert(definition.id.clone(), definition);
1986        }
1987        let (event_tx, event_rx) = mpsc::channel(EVENT_CAPACITY);
1988        let (command_tx, command_rx) = mpsc::channel(COMMAND_CAPACITY);
1989        let (ingress, accepted_rx) =
1990            ServerIngress::channel(SESSION_ACCEPT_CAPACITY, config.signaling_qos);
1991        let next_call_id = Arc::new(AtomicU64::new(1));
1992        let latest_media_statistics = Arc::new(RwLock::new(HashMap::new()));
1993        let call_answer_order = Arc::new(RwLock::new(config.call_answer_order));
1994        let anonymous_hotline = Arc::new(RwLock::new(config.anonymous_hotline.clone()));
1995        let handle = ServerHandle {
1996            command_tx,
1997            next_call_id: Arc::clone(&next_call_id),
1998            latest_media_statistics: Arc::clone(&latest_media_statistics),
1999            call_answer_order: Arc::clone(&call_answer_order),
2000        };
2001        Ok((
2002            Self {
2003                listener,
2004                accepted_rx,
2005                config: Arc::new(config),
2006                anonymous_hotline,
2007                definitions: Arc::new(RwLock::new(by_id)),
2008                sessions: Arc::new(Mutex::new(HashMap::new())),
2009                event_tx,
2010                command_rx,
2011                next_generation: Arc::new(AtomicU64::new(1)),
2012                next_statistics_generation: Arc::new(AtomicU64::new(1)),
2013                next_call_id,
2014                latest_media_statistics,
2015                call_answer_order,
2016            },
2017            handle,
2018            event_rx,
2019            ingress,
2020        ))
2021    }
2022
2023    /// Return the concrete address owned by [`Self::bind`].
2024    ///
2025    /// This exposes an operating-system-assigned port when the requested bind
2026    /// address used port zero. Servers created by [`Self::with_ingress`] return
2027    /// [`ServerError::InvalidConfig`] because listener addresses belong to the
2028    /// external transport owner.
2029    pub fn local_addr(&self) -> Result<SocketAddr, ServerError> {
2030        self.listener
2031            .as_ref()
2032            .ok_or_else(|| ServerError::InvalidConfig("server has no bound listener".into()))?
2033            .local_addr()
2034            .map_err(ServerError::Io)
2035    }
2036
2037    /// Drive admission, command dispatch, reconfiguration, and shutdown.
2038    ///
2039    /// This consuming future must be polled exactly once. It accepts plain
2040    /// sockets owned by [`Self::bind`] and streams submitted through
2041    /// [`ServerIngress`], starts an independent session task for each, and
2042    /// serializes server-wide commands. It returns normally after an explicit
2043    /// shutdown request or after every [`ServerHandle`] is dropped; before
2044    /// returning it asks each registered session to disconnect. Listener or
2045    /// server-level I/O failures are returned as [`ServerError`], while an
2046    /// individual session failure is emitted as [`Event::SessionError`].
2047    pub async fn run(mut self) -> Result<(), ServerError> {
2048        if let Some(listener) = &self.listener {
2049            info!(bind = %listener.local_addr()?, "SCCP server listening");
2050        }
2051        loop {
2052            tokio::select! {
2053                accepted = accept_clear(self.listener.as_ref(), self.config.signaling_qos) => {
2054                    self.start_session(accepted?);
2055                }
2056                accepted = self.accepted_rx.recv(), if !self.accepted_rx.is_closed() => {
2057                    if let Some(accepted) = accepted {
2058                        self.start_session(accepted);
2059                    }
2060                }
2061                command = self.command_rx.recv() => {
2062                    match command {
2063                        Some(ServerCommand::Public(command)) => {
2064                            if let Err(error) = self.dispatch_public(*command).await {
2065                                warn!(%error, "discarding SCCP command for a retired session");
2066                            }
2067                        }
2068                        Some(ServerCommand::Confirmed { command, written, expires_at }) => {
2069                            self.dispatch_confirmed(command, written, expires_at).await;
2070                        }
2071                        Some(ServerCommand::OfferIncoming { device_id, line_instance, call_id, info, ringer }) => {
2072                            if let Err(error) = self.dispatch(&device_id, SessionCommand::OfferIncoming { line_instance, call_id, info: Box::new(info), ringer }).await {
2073                                warn!(%error, "discarding incoming offer for a retired session");
2074                            }
2075                        }
2076                        Some(ServerCommand::Reconfigure { definitions, affected, applied }) => {
2077                            let result = self
2078                                .apply_station_policy(
2079                                    definitions,
2080                                    affected,
2081                                    AnonymousHotlineUpdate::Preserve,
2082                                )
2083                                .await;
2084                            let _ = applied.send(result);
2085                        }
2086                        Some(ServerCommand::ReconfigureStationPolicy {
2087                            definitions,
2088                            affected,
2089                            anonymous_hotline,
2090                            applied,
2091                        }) => {
2092                            let result = self
2093                                .apply_station_policy(
2094                                    definitions,
2095                                    affected,
2096                                    AnonymousHotlineUpdate::Replace(anonymous_hotline),
2097                                )
2098                                .await;
2099                            let _ = applied.send(result);
2100                        }
2101                        Some(ServerCommand::ReconfigureAnonymousHotline { definition, applied }) => {
2102                            let sessions = self.sessions.lock().await;
2103                            let changed = {
2104                                let mut current = self
2105                                    .anonymous_hotline
2106                                    .write()
2107                                    .expect("SCCP anonymous-hotline lock poisoned");
2108                                if *current == definition {
2109                                    false
2110                                } else {
2111                                    *current = definition;
2112                                    true
2113                                }
2114                            };
2115                            let affected = if changed {
2116                                sessions
2117                                    .values()
2118                                    .filter(|session| session.anonymous_hotline)
2119                                    .cloned()
2120                                    .collect::<Vec<_>>()
2121                            } else {
2122                                Vec::new()
2123                            };
2124                            drop(sessions);
2125                            let count = affected.len();
2126                            for session in affected {
2127                                let _ = session.tx.send(SessionCommand::Disconnect).await;
2128                            }
2129                            let _ = applied.send(count);
2130                        }
2131                        Some(ServerCommand::Shutdown) | None => {
2132                            let sessions: Vec<_> = self.sessions.lock().await.values().cloned().collect();
2133                            for session in sessions { let _ = session.tx.send(SessionCommand::Disconnect).await; }
2134                            return Ok(());
2135                        }
2136                    }
2137                }
2138            }
2139        }
2140    }
2141
2142    fn start_session(&self, accepted: AcceptedStation) {
2143        let AcceptedStation {
2144            stream,
2145            peer,
2146            local,
2147            transport,
2148            socket_qos,
2149        } = accepted;
2150        let context = SessionContext {
2151            peer,
2152            local,
2153            transport,
2154            socket_qos,
2155            config: Arc::clone(&self.config),
2156            definitions: Arc::clone(&self.definitions),
2157            anonymous_hotline: Arc::clone(&self.anonymous_hotline),
2158            sessions: Arc::clone(&self.sessions),
2159            event_tx: self.event_tx.clone(),
2160            next_generation: Arc::clone(&self.next_generation),
2161            next_statistics_generation: Arc::clone(&self.next_statistics_generation),
2162            next_call_id: Arc::clone(&self.next_call_id),
2163            latest_media_statistics: Arc::clone(&self.latest_media_statistics),
2164            call_answer_order: Arc::clone(&self.call_answer_order),
2165        };
2166        let error_tx = self.event_tx.clone();
2167        tokio::spawn(async move {
2168            match run_session(stream, context).await {
2169                Ok(()) => debug!(%peer, "SCCP session ended cleanly"),
2170                Err(error) => {
2171                    warn!(%peer, %error, "SCCP session ended with an error");
2172                    let _ = error_tx
2173                        .send(Event::SessionError {
2174                            peer,
2175                            error: error.to_string(),
2176                        })
2177                        .await;
2178                }
2179            }
2180        });
2181    }
2182
2183    async fn dispatch_public(&self, command: Command) -> Result<(), ServerError> {
2184        let device_id = command.device_id.clone();
2185        self.dispatch(&device_id, SessionCommand::Public(Box::new(command)))
2186            .await
2187    }
2188
2189    async fn dispatch_confirmed(
2190        &self,
2191        command: Box<Command>,
2192        written: CommandWriteConfirmation,
2193        expires_at: Instant,
2194    ) {
2195        let device_id = command.device_id.clone();
2196        if confirmed_command_expired(&written, expires_at) {
2197            reject_expired_confirmed_command(written);
2198            return;
2199        }
2200        let tx = self
2201            .sessions
2202            .lock()
2203            .await
2204            .get(&device_id)
2205            .map(|session| session.tx.clone());
2206        let Some(tx) = tx else {
2207            let _ = written.send(Err(ServerError::DeviceNotConnected(device_id).to_string()));
2208            return;
2209        };
2210        if let Err(error) = tx
2211            .send(SessionCommand::Confirmed {
2212                command,
2213                written,
2214                expires_at,
2215            })
2216            .await
2217        {
2218            let SessionCommand::Confirmed { written, .. } = error.0 else {
2219                unreachable!("confirmed dispatch returned a different command variant")
2220            };
2221            let _ = written.send(Err(ServerError::DeviceNotConnected(device_id).to_string()));
2222        }
2223    }
2224
2225    async fn dispatch(
2226        &self,
2227        device_id: &DeviceId,
2228        command: SessionCommand,
2229    ) -> Result<(), ServerError> {
2230        let tx = self
2231            .sessions
2232            .lock()
2233            .await
2234            .get(device_id)
2235            .map(|s| s.tx.clone())
2236            .ok_or_else(|| ServerError::DeviceNotConnected(device_id.clone()))?;
2237        tx.send(command)
2238            .await
2239            .map_err(|_| ServerError::DeviceNotConnected(device_id.clone()))
2240    }
2241
2242    async fn apply_station_policy(
2243        &self,
2244        definitions: HashMap<DeviceId, DeviceDefinition>,
2245        affected: HashSet<DeviceId>,
2246        anonymous_hotline: AnonymousHotlineUpdate,
2247    ) -> ReconfigureResult {
2248        // Registration takes the session and definition locks in the same
2249        // order, so it sees either the complete current policy or the complete
2250        // candidate policy.
2251        let sessions = self.sessions.lock().await;
2252        let result = {
2253            let mut current = self
2254                .definitions
2255                .write()
2256                .expect("SCCP definitions lock poisoned");
2257            let result = reconfigure_result(&current, &definitions, &affected);
2258            *current = definitions;
2259            result
2260        };
2261        let anonymous_changed = match anonymous_hotline {
2262            AnonymousHotlineUpdate::Preserve => false,
2263            AnonymousHotlineUpdate::Replace(next) => {
2264                let mut current = self
2265                    .anonymous_hotline
2266                    .write()
2267                    .expect("SCCP anonymous-hotline lock poisoned");
2268                if *current == next {
2269                    false
2270                } else {
2271                    *current = next;
2272                    true
2273                }
2274            }
2275        };
2276        let affected_devices = result
2277            .disconnected_devices()
2278            .chain(affected.iter())
2279            .cloned()
2280            .collect::<HashSet<_>>();
2281        let affected_sessions = sessions
2282            .iter()
2283            .filter(|(device, session)| {
2284                affected_devices.contains(*device)
2285                    || (anonymous_changed && session.anonymous_hotline)
2286            })
2287            .map(|(_, session)| session.clone())
2288            .collect::<Vec<_>>();
2289        drop(sessions);
2290        for session in affected_sessions {
2291            let _ = session.tx.send(SessionCommand::Disconnect).await;
2292        }
2293        result
2294    }
2295}
2296
2297fn confirmed_command_expired(written: &CommandWriteConfirmation, expires_at: Instant) -> bool {
2298    written.is_closed() || Instant::now() >= expires_at
2299}
2300
2301fn reject_expired_confirmed_command(written: CommandWriteConfirmation) {
2302    let _ = written.send(Err(ServerError::CommandAcknowledgementTimeout.to_string()));
2303}
2304
2305fn prepare_session_command(
2306    command: SessionCommand,
2307) -> Option<(SessionCommand, Option<CommandWriteConfirmation>)> {
2308    match command {
2309        SessionCommand::Confirmed {
2310            command,
2311            written,
2312            expires_at,
2313        } => {
2314            if confirmed_command_expired(&written, expires_at) {
2315                reject_expired_confirmed_command(written);
2316                None
2317            } else {
2318                Some((SessionCommand::Public(command), Some(written)))
2319            }
2320        }
2321        command => Some((command, None)),
2322    }
2323}
2324
2325fn reconfigure_result(
2326    current: &HashMap<DeviceId, DeviceDefinition>,
2327    next: &HashMap<DeviceId, DeviceDefinition>,
2328    affected: &HashSet<DeviceId>,
2329) -> ReconfigureResult {
2330    let mut result = ReconfigureResult::default();
2331    for (device, definition) in next {
2332        match current.get(device) {
2333            None => result.added.push(device.clone()),
2334            Some(previous) if previous != definition => result.changed.push(device.clone()),
2335            Some(_) => {}
2336        }
2337    }
2338    let explicitly_changed: Vec<_> = affected
2339        .iter()
2340        .filter(|device| {
2341            current.contains_key(*device)
2342                && next.contains_key(*device)
2343                && !result.changed.contains(*device)
2344        })
2345        .cloned()
2346        .collect();
2347    result.changed.extend(explicitly_changed);
2348    result.removed.extend(
2349        current
2350            .keys()
2351            .filter(|device| !next.contains_key(*device))
2352            .cloned(),
2353    );
2354    result.added.sort();
2355    result.changed.sort();
2356    result.removed.sort();
2357    result
2358}
2359
2360fn command_call_id(command: &Command) -> Option<CallId> {
2361    match &command.action {
2362        CommandAction::BeginCall { call_id, .. }
2363        | CommandAction::SetCallInfo { call_id, .. }
2364        | CommandAction::CommitOutboundCall { call_id, .. }
2365        | CommandAction::PresentOutboundProceeding { call_id, .. }
2366        | CommandAction::PresentOutboundRinging { call_id, .. }
2367        | CommandAction::SetCallState { call_id, .. }
2368        | CommandAction::SetCallSelected { call_id, .. }
2369        | CommandAction::DisplayPrompt { call_id, .. }
2370        | CommandAction::ClearPrompt { call_id, .. }
2371        | CommandAction::SetRecordingStatus { call_id, .. }
2372        | CommandAction::ShowConferenceParticipantActions { call_id, .. }
2373        | CommandAction::StartTone { call_id, .. }
2374        | CommandAction::StartRinging { call_id, .. }
2375        | CommandAction::StopRinging { call_id, .. }
2376        | CommandAction::OpenReceiveChannel { call_id, .. }
2377        | CommandAction::OpenMultimediaReceiveChannel { call_id, .. }
2378        | CommandAction::CloseMultimediaReceiveChannel { call_id, .. }
2379        | CommandAction::StartMultimediaTransmission { call_id, .. }
2380        | CommandAction::StopMultimediaTransmission { call_id, .. }
2381        | CommandAction::SetMultimediaTransmitBitRate { call_id, .. }
2382        | CommandAction::NotifyMultimediaTransmitBitRate { call_id, .. }
2383        | CommandAction::ControlMultimediaTransmission { call_id, .. }
2384        | CommandAction::OpenOutboundMedia { call_id, .. }
2385        | CommandAction::CloseReceiveChannel { call_id, .. }
2386        | CommandAction::StartMedia { call_id, .. }
2387        | CommandAction::StartMulticastReception { call_id, .. }
2388        | CommandAction::StopMulticastReception { call_id, .. }
2389        | CommandAction::StartMulticastTransmission { call_id, .. }
2390        | CommandAction::StopMulticastTransmission { call_id, .. }
2391        | CommandAction::StopMedia { call_id, .. }
2392        | CommandAction::CloseCall { call_id, .. } => Some(*call_id),
2393        CommandAction::BeginTransfer { source_call_id, .. } => Some(*source_call_id),
2394        CommandAction::SetMwi { .. }
2395        | CommandAction::SetStatusMessage { .. }
2396        | CommandAction::SetMicrophoneMode { .. }
2397        | CommandAction::ResetDevice { .. }
2398        | CommandAction::SetForwardStatus { .. }
2399        | CommandAction::SetFeatureStatus { .. }
2400        | CommandAction::SetDoNotDisturbStatus { .. }
2401        | CommandAction::SetMobilityAppearance { .. }
2402        | CommandAction::SetBlfStatus { .. }
2403        | CommandAction::ShowParkingMenu { .. }
2404        | CommandAction::ShowConferenceList { .. }
2405        | CommandAction::ShowTextService { .. }
2406        | CommandAction::ShowInputService { .. }
2407        | CommandAction::ExecutePhoneActions { .. }
2408        | CommandAction::ShowImageService { .. }
2409        | CommandAction::ShowStatusService { .. }
2410        | CommandAction::SetBackgroundImage { .. }
2411        | CommandAction::PreviewBackgroundImage { .. }
2412        | CommandAction::SetRingtone { .. }
2413        | CommandAction::StartAnnouncement { .. }
2414        | CommandAction::StopAnnouncement { .. }
2415        | CommandAction::AnnouncementFinish { .. }
2416        | CommandAction::DisconnectDevice { .. } => None,
2417    }
2418}
2419
2420async fn accept_clear(
2421    listener: Option<&TcpListener>,
2422    signaling_qos: SignalingQos,
2423) -> Result<AcceptedStation, ServerError> {
2424    let Some(listener) = listener else {
2425        return std::future::pending().await;
2426    };
2427    let (stream, peer) = listener.accept().await?;
2428    stream.set_nodelay(true)?;
2429    let local = stream.local_addr()?;
2430    let socket_qos = match SignalingSocket::capture(&stream, local) {
2431        Ok(socket) => {
2432            report_socket_qos(None, peer, socket.apply(signaling_qos));
2433            Some(Box::new(socket) as Box<dyn StationSocketQos>)
2434        }
2435        Err(error) => {
2436            warn!(%peer, %error, "unable to retain signaling socket QoS control");
2437            None
2438        }
2439    };
2440    Ok(AcceptedStation {
2441        stream: Box::new(stream),
2442        peer,
2443        local,
2444        transport: StationTransport::Clear,
2445        socket_qos,
2446    })
2447}
2448
2449fn report_socket_qos(device_id: Option<&DeviceId>, endpoint: SocketAddr, report: SocketQosReport) {
2450    for failure in report.failures() {
2451        match device_id {
2452            Some(device_id) => {
2453                warn!(%device_id, %endpoint, %failure, "signaling socket marking unavailable")
2454            }
2455            None => warn!(%endpoint, %failure, "signaling socket marking unavailable"),
2456        }
2457    }
2458}
2459
2460fn allocate_session_generation(
2461    next_generation: &AtomicU64,
2462) -> Result<SessionGeneration, ServerError> {
2463    let generation = next_generation
2464        .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
2465            SessionGeneration::new(current).and_then(|_| current.checked_add(1))
2466        })
2467        .map_err(|_| ServerError::SessionGenerationExhausted)?;
2468    SessionGeneration::new(generation).ok_or(ServerError::SessionGenerationExhausted)
2469}
2470
2471const fn transport_allowed(
2472    requirement: StationTransportRequirement,
2473    transport: StationTransport,
2474) -> bool {
2475    matches!(
2476        (requirement, transport),
2477        (StationTransportRequirement::Either, _)
2478            | (StationTransportRequirement::Clear, StationTransport::Clear)
2479            | (
2480                StationTransportRequirement::Secure,
2481                StationTransport::Secure
2482            )
2483    )
2484}
2485
2486#[derive(Debug)]
2487struct SessionContext {
2488    peer: SocketAddr,
2489    local: SocketAddr,
2490    transport: StationTransport,
2491    socket_qos: Option<Box<dyn StationSocketQos>>,
2492    config: Arc<ServerConfig>,
2493    definitions: Arc<RwLock<HashMap<DeviceId, DeviceDefinition>>>,
2494    anonymous_hotline: Arc<RwLock<Option<AnonymousHotlineDefinition>>>,
2495    sessions: Sessions,
2496    event_tx: mpsc::Sender<Event>,
2497    next_generation: Arc<AtomicU64>,
2498    next_statistics_generation: Arc<AtomicU64>,
2499    next_call_id: Arc<AtomicU64>,
2500    latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
2501    call_answer_order: Arc<RwLock<CallSelectionOrder>>,
2502}
2503
2504#[derive(Debug)]
2505struct SessionState {
2506    device: DeviceDefinition,
2507    registration: DeviceRegistration,
2508    features: PhoneFeatures,
2509    generation: SessionGeneration,
2510    runtime: SessionRuntimeState,
2511}
2512
2513/// Context-free mutable bookkeeping for one already-valid station session.
2514///
2515/// Unlike `SessionState`, this value has a real neutral default: it contains
2516/// no device identity, peer address, negotiated protocol, or generation.
2517#[derive(Debug)]
2518struct SessionRuntimeState {
2519    calls_by_id: HashMap<CallId, SessionCall>,
2520    calls_by_wire: HashMap<u32, CallId>,
2521    media_capabilities: StationMediaCapabilities,
2522    next_media_token: Option<MediaRequestToken>,
2523    next_multicast_generation: u64,
2524    multicast: HashMap<MulticastKey, MulticastSession>,
2525    pending_connection_statistics: HashMap<u32, PendingConnectionStatistics>,
2526    statistics_references: HashSet<u32>,
2527    cancelled_calls: HashSet<CallId>,
2528    last_number_by_line: HashMap<u32, String>,
2529    forwarding_by_line: HashMap<u32, SessionForwarding>,
2530    feature_states: HashMap<u32, SessionFeatureState>,
2531    mwi_by_line: HashMap<u32, bool>,
2532    mobility_appearances: HashMap<u32, LineAppearance>,
2533    active_key_mode: KeyMode,
2534    active_call_id: Option<CallId>,
2535    pending_parking_menu: Option<PendingParkingMenu>,
2536    active_blf_alerts: BTreeMap<u32, HandsetStatusMessage>,
2537    visible_blf_alert: Option<HandsetStatusMessage>,
2538    persistent_status_message: bool,
2539    headset_enabled: bool,
2540    media_path_states:
2541        HashMap<crate::message::values::MediaPathId, crate::message::values::MediaPathEvent>,
2542    pending_media_path_release: Option<PendingMediaPathRelease>,
2543}
2544
2545impl Default for SessionRuntimeState {
2546    fn default() -> Self {
2547        Self {
2548            calls_by_id: HashMap::new(),
2549            calls_by_wire: HashMap::new(),
2550            media_capabilities: StationMediaCapabilities::default(),
2551            next_media_token: MediaRequestToken::new(1),
2552            next_multicast_generation: 0,
2553            multicast: HashMap::new(),
2554            pending_connection_statistics: HashMap::new(),
2555            statistics_references: HashSet::new(),
2556            cancelled_calls: HashSet::new(),
2557            last_number_by_line: HashMap::new(),
2558            forwarding_by_line: HashMap::new(),
2559            feature_states: HashMap::new(),
2560            mwi_by_line: HashMap::new(),
2561            mobility_appearances: HashMap::new(),
2562            active_key_mode: KeyMode::OnHook,
2563            active_call_id: None,
2564            pending_parking_menu: None,
2565            active_blf_alerts: BTreeMap::new(),
2566            visible_blf_alert: None,
2567            persistent_status_message: false,
2568            headset_enabled: false,
2569            media_path_states: HashMap::new(),
2570            pending_media_path_release: None,
2571        }
2572    }
2573}
2574
2575impl SessionState {
2576    fn new(
2577        device: DeviceDefinition,
2578        registration: DeviceRegistration,
2579        features: PhoneFeatures,
2580        generation: SessionGeneration,
2581    ) -> Self {
2582        debug_assert_eq!(device.id, registration.id);
2583        Self {
2584            device,
2585            registration,
2586            features,
2587            generation,
2588            runtime: SessionRuntimeState::default(),
2589        }
2590    }
2591}
2592
2593impl std::ops::Deref for SessionState {
2594    type Target = SessionRuntimeState;
2595
2596    fn deref(&self) -> &Self::Target {
2597        &self.runtime
2598    }
2599}
2600
2601impl std::ops::DerefMut for SessionState {
2602    fn deref_mut(&mut self) -> &mut Self::Target {
2603        &mut self.runtime
2604    }
2605}
2606
2607#[derive(Clone, Copy, Debug)]
2608struct PendingMediaPathRelease {
2609    call_id: CallId,
2610    path: crate::message::values::MediaPathId,
2611    deadline: Instant,
2612}
2613
2614#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2615struct MulticastKey {
2616    conference_id: ConferenceId,
2617    call_id: CallId,
2618}
2619
2620#[derive(Clone, Debug)]
2621struct MulticastSession {
2622    wire_call_reference: u32,
2623    receive: Option<MulticastReceive>,
2624    transmit: Option<MulticastTransmit>,
2625}
2626
2627#[derive(Clone, Debug)]
2628struct MulticastReceive {
2629    request: MediaRequestIdentity,
2630    route: MulticastMediaRoute,
2631    state: MulticastReceiveState,
2632}
2633
2634#[derive(Clone, Debug)]
2635enum MulticastReceiveState {
2636    AwaitingAcknowledgement { deadline: Instant },
2637    Open,
2638}
2639
2640#[derive(Clone, Debug)]
2641struct MulticastTransmit {
2642    request: MediaRequestIdentity,
2643    route: MulticastMediaRoute,
2644}
2645
2646impl SessionState {
2647    fn station_context(&self) -> StationSessionContext {
2648        StationSessionContext::new(self.registration.protocol, self.features)
2649    }
2650}
2651
2652#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2653struct SessionFeatureState {
2654    button_type: ButtonType,
2655    state: u32,
2656}
2657
2658#[derive(Clone, Debug)]
2659struct PendingConnectionStatistics {
2660    session_generation: SessionGeneration,
2661    request_generation: u64,
2662    call_id: CallId,
2663    line_instance: u32,
2664    codec: Codec,
2665    packet_ms: u32,
2666    max_frames_per_packet: u32,
2667    receive_peer: Option<MediaEndpoint>,
2668    transmit_peer: Option<MediaEndpoint>,
2669    directory_number: String,
2670    processing: StatisticsProcessing,
2671    expires_at: Instant,
2672}
2673
2674#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2675struct PendingParkingMenu {
2676    instance: u32,
2677    transaction_id: u32,
2678}
2679
2680#[derive(Clone, Debug, Default)]
2681struct SessionForwarding {
2682    all: Option<String>,
2683    busy: Option<String>,
2684    no_answer: Option<String>,
2685}
2686
2687async fn run_session(
2688    mut stream: Box<dyn StationIo>,
2689    context: SessionContext,
2690) -> Result<(), ServerError> {
2691    let (session_tx, mut session_rx) = mpsc::channel(SESSION_COMMAND_CAPACITY);
2692    let mut decoder = FrameDecoder::new();
2693    let mut read_buffer = [0_u8; 4096];
2694    let mut state: Option<SessionState> = None;
2695    let mut last_keepalive = Instant::now();
2696    let mut session_deadlines = tokio::time::interval(Duration::from_millis(100));
2697    session_deadlines.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
2698    let keepalive_seconds = if context.config.registration_tokens.server_priority == 1 {
2699        context.config.keepalive_seconds
2700    } else {
2701        context.config.secondary_keepalive_seconds
2702    };
2703    let keepalive_timeout = Duration::from_secs(u64::from(keepalive_seconds) * 3);
2704
2705    loop {
2706        tokio::select! {
2707            read = stream.read(&mut read_buffer) => {
2708                let count = read?;
2709                if count == 0 { break; }
2710                for frame in decoder.push(&read_buffer[..count])? {
2711                    let decode_protocol = state
2712                        .as_ref()
2713                        .map_or(ProtocolVersion::V3, |state| state.registration.protocol);
2714                    let message_id = frame.message_id;
2715                    let message = match ClientMessage::decode_with_version(frame, decode_protocol) {
2716                        Ok(message) => message,
2717                        Err(error) if message_id != crate::message::wire_id::REGISTER => {
2718                            let device_id = state.as_ref().map(|state| state.device.id.clone());
2719                            warn!(peer = %context.peer, message_id = format_args!("0x{message_id:04x}"), %error, "ignoring malformed SCCP application message");
2720                            let _ = context.event_tx.send(Event::ProtocolWarning {
2721                                peer: context.peer,
2722                                device_id,
2723                                message_id,
2724                                error: error.to_string(),
2725                            }).await;
2726                            continue;
2727                        }
2728                        Err(error) => return Err(error.into()),
2729                    };
2730                    if let ClientMessage::Register(registration) = &message {
2731                        if state.is_some() {
2732                            return Err(ServerError::Protocol(CodecError::InvalidDefinition("duplicate REGISTER on one TCP session".into())));
2733                        }
2734                        match handle_registration(&mut stream, registration, &context, &session_tx).await? {
2735                            Some(registered) => {
2736                                state = Some(registered);
2737                                last_keepalive = Instant::now();
2738                            }
2739                            None => return Ok(()),
2740                        }
2741                    } else if let Some(state) = state.as_mut() {
2742                        if matches!(message, ClientMessage::KeepAlive) { last_keepalive = Instant::now(); }
2743                        handle_client_message(&mut stream, state, message, &context).await?;
2744                    } else {
2745                        handle_pre_registration_message(&mut stream, message, &context).await?;
2746                    }
2747                }
2748            }
2749            command = session_rx.recv() => {
2750                let Some(command) = command else { break };
2751                let Some(state) = state.as_mut() else { continue };
2752                if handle_session_command_result(&mut stream, state, command, &context).await? {
2753                    break;
2754                }
2755            }
2756            _ = session_deadlines.tick(), if state.is_some() => {
2757                if let Some(state) = state.as_mut() {
2758                    handle_session_deadlines(&mut stream, state, &context, Instant::now()).await?;
2759                }
2760            }
2761            _ = tokio::time::sleep_until(last_keepalive + keepalive_timeout), if state.is_some() => {
2762                warn!(peer = %context.peer, "SCCP keepalive timeout");
2763                break;
2764            }
2765        }
2766    }
2767
2768    if let Some(mut state) = state {
2769        drain_session_media(&mut stream, &mut state).await;
2770        let mut sessions = context.sessions.lock().await;
2771        let was_current = sessions
2772            .get(&state.device.id)
2773            .is_some_and(|entry| entry.generation == state.generation);
2774        if was_current {
2775            sessions.remove(&state.device.id);
2776        }
2777        drop(sessions);
2778        if was_current {
2779            let _ = context
2780                .event_tx
2781                .send(Event::device(
2782                    state.device.id,
2783                    state.generation,
2784                    DeviceEventKind::Disconnected {},
2785                ))
2786                .await;
2787        }
2788    }
2789    Ok(())
2790}
2791
2792async fn handle_registration(
2793    stream: &mut dyn StationIo,
2794    registration: &crate::message::RegistrationMessage,
2795    context: &SessionContext,
2796    session_tx: &mpsc::Sender<SessionCommand>,
2797) -> Result<Option<SessionState>, ServerError> {
2798    let mut sessions = context.sessions.lock().await;
2799    let configured = context
2800        .definitions
2801        .read()
2802        .expect("SCCP definitions lock poisoned")
2803        .get(&registration.device_id)
2804        .cloned();
2805    let anonymous_hotline = configured.is_none();
2806    let definition = configured.or_else(|| {
2807        context
2808            .anonymous_hotline
2809            .read()
2810            .expect("SCCP anonymous-hotline lock poisoned")
2811            .as_ref()
2812            .map(|hotline| hotline.device_definition(registration.device_id.clone()))
2813    });
2814    let Some(definition) = definition else {
2815        drop(sessions);
2816        send_message(
2817            stream,
2818            &ServerMessage::RegisterReject {
2819                reason: "Device not configured".into(),
2820            },
2821            ProtocolVersion::V17,
2822        )
2823        .await?;
2824        return Ok(None);
2825    };
2826    if !transport_allowed(definition.transport, context.transport) {
2827        drop(sessions);
2828        send_message(
2829            stream,
2830            &ServerMessage::RegisterReject {
2831                reason: "Device transport not permitted".into(),
2832            },
2833            ProtocolVersion::V17,
2834        )
2835        .await?;
2836        return Ok(None);
2837    }
2838    let protocol = ProtocolVersion::negotiate(registration.advertised_protocol)?;
2839    if canonical_ip_address(context.peer.ip()).is_ipv6() && protocol < ProtocolVersion::V17 {
2840        drop(sessions);
2841        send_message(
2842            stream,
2843            &ServerMessage::RegisterReject {
2844                reason: "IPv6 requires protocol v17".into(),
2845            },
2846            protocol,
2847        )
2848        .await?;
2849        return Ok(None);
2850    }
2851    let features = registration.features;
2852    let generation = allocate_session_generation(&context.next_generation)?;
2853    if let Some(socket_qos) = &context.socket_qos {
2854        let signaling_qos = definition
2855            .signaling_qos
2856            .unwrap_or(context.config.signaling_qos);
2857        report_socket_qos(
2858            Some(&registration.device_id),
2859            context.peer,
2860            socket_qos.apply(signaling_qos),
2861        );
2862    }
2863    let device_registration = DeviceRegistration {
2864        id: registration.device_id.clone(),
2865        peer: context.peer,
2866        transport: context.transport,
2867        reported_address: registration.reported_address,
2868        reported_ipv6_address: registration.reported_ipv6_address,
2869        device_type: registration.device_type,
2870        protocol,
2871        firmware: registration.firmware.clone(),
2872    };
2873    let previous = sessions.insert(
2874        registration.device_id.clone(),
2875        SessionSender {
2876            generation,
2877            anonymous_hotline,
2878            tx: session_tx.clone(),
2879        },
2880    );
2881    drop(sessions);
2882    if let Some(previous) = previous {
2883        let _ = previous.tx.send(SessionCommand::Disconnect).await;
2884    }
2885    send_message(
2886        stream,
2887        &ServerMessage::RegisterAck {
2888            keepalive_seconds: context.config.keepalive_seconds,
2889            secondary_keepalive_seconds: context.config.secondary_keepalive_seconds,
2890            protocol,
2891            features: PhoneFeatures::empty(),
2892            date_template: context.config.date_template.clone(),
2893        },
2894        protocol,
2895    )
2896    .await?;
2897    send_message(stream, &ServerMessage::CapabilitiesRequest, protocol).await?;
2898    context
2899        .event_tx
2900        .send(Event::device(
2901            registration.device_id.clone(),
2902            generation,
2903            DeviceEventKind::Registered(device_registration.clone()),
2904        ))
2905        .await
2906        .map_err(|_| ServerError::Stopped)?;
2907    info!(device_id = %registration.device_id, %protocol, peer = %context.peer, "SCCP device registered");
2908    Ok(Some(SessionState::new(
2909        definition,
2910        device_registration,
2911        features,
2912        generation,
2913    )))
2914}
2915
2916async fn handle_session_command_result(
2917    stream: &mut dyn StationIo,
2918    state: &mut SessionState,
2919    command: SessionCommand,
2920    context: &SessionContext,
2921) -> Result<bool, ServerError> {
2922    let Some((command, written)) = prepare_session_command(command) else {
2923        return Ok(false);
2924    };
2925    match handle_session_command(stream, state, command, context).await {
2926        Ok(disconnect) => {
2927            if let Some(written) = written {
2928                let _ = written.send(Ok(()));
2929            }
2930            Ok(disconnect)
2931        }
2932        Err(error) => {
2933            if let Some(written) = written {
2934                let _ = written.send(Err(error.to_string()));
2935            }
2936            if error.is_nonfatal_command_rejection() {
2937                warn!(
2938                    device_id = %state.device.id,
2939                    %error,
2940                    "rejected invalid SCCP station command"
2941                );
2942                Ok(false)
2943            } else {
2944                Err(error)
2945            }
2946        }
2947    }
2948}
2949
2950async fn handle_session_deadlines(
2951    stream: &mut dyn StationIo,
2952    state: &mut SessionState,
2953    context: &SessionContext,
2954    now: Instant,
2955) -> Result<(), ServerError> {
2956    for (call_id, acknowledgement) in expire_handset_acknowledgements(&mut state.calls_by_id, now) {
2957        context
2958            .event_tx
2959            .send(Event::device(
2960                state.device.id.clone(),
2961                state.generation,
2962                DeviceEventKind::HandsetAcknowledgementTimedOut {
2963                    call_id,
2964                    acknowledgement,
2965                },
2966            ))
2967            .await
2968            .map_err(|_| ServerError::Stopped)?;
2969    }
2970    for (key, stop) in expire_multicast_reception_acknowledgements(state, now) {
2971        send_message(stream, &stop, state.registration.protocol).await?;
2972        context
2973            .event_tx
2974            .send(Event::device(
2975                state.device.id.clone(),
2976                state.generation,
2977                DeviceEventKind::MulticastReceptionTimedOut {
2978                    conference_id: key.conference_id,
2979                    call_id: key.call_id,
2980                },
2981            ))
2982            .await
2983            .map_err(|_| ServerError::Stopped)?;
2984    }
2985    for expired in expire_multimedia_receive_acknowledgements(state, now) {
2986        send_message(stream, &expired.close, state.registration.protocol).await?;
2987        context
2988            .event_tx
2989            .send(Event::device(
2990                state.device.id.clone(),
2991                state.generation,
2992                DeviceEventKind::MultimediaReceiveChannelTimedOut {
2993                    call_id: expired.call_id,
2994                    codec: expired.codec,
2995                    passthrough_party_id: expired.passthrough_party_id,
2996                },
2997            ))
2998            .await
2999            .map_err(|_| ServerError::Stopped)?;
3000    }
3001    for expired in expire_multimedia_transmit_acknowledgements(state, now) {
3002        send_message(stream, &expired.stop, state.registration.protocol).await?;
3003        context
3004            .event_tx
3005            .send(Event::device(
3006                state.device.id.clone(),
3007                state.generation,
3008                DeviceEventKind::MultimediaTransmitTimedOut {
3009                    call_id: expired.call_id,
3010                    codec: expired.codec,
3011                    passthrough_party_id: expired.passthrough_party_id,
3012                },
3013            ))
3014            .await
3015            .map_err(|_| ServerError::Stopped)?;
3016    }
3017    if let Some(pending) = state
3018        .pending_media_path_release
3019        .filter(|pending| pending.deadline <= now)
3020    {
3021        state.pending_media_path_release = None;
3022        let still_released = state.media_path_states.get(&pending.path)
3023            == Some(&crate::message::values::MediaPathEvent::Off)
3024            && !has_active_media_path(state)
3025            && active_media_path_call(state) == Some(pending.call_id);
3026        if still_released && let Some(call) = state.calls_by_id.get(&pending.call_id).cloned() {
3027            debug!(
3028                device_id = %state.device.id,
3029                call_id = ?call.call_id,
3030                path = ?pending.path,
3031                "completing unpaired media-path release as OnHook"
3032            );
3033            let line_instance = call.line_instance;
3034            complete_on_hook(stream, state, context, call, line_instance).await?;
3035        }
3036    }
3037    prune_connection_statistics(&mut state.pending_connection_statistics, now);
3038    Ok(())
3039}
3040
3041fn expire_handset_acknowledgements(
3042    calls_by_id: &mut HashMap<CallId, SessionCall>,
3043    now: Instant,
3044) -> Vec<(CallId, HandsetAcknowledgement)> {
3045    let mut calls = calls_by_id.keys().copied().collect::<Vec<_>>();
3046    calls.sort_unstable_by_key(|call_id| call_id.0);
3047    let mut expired = Vec::new();
3048    for call_id in calls {
3049        let call = calls_by_id
3050            .get_mut(&call_id)
3051            .expect("call identifier came from session state");
3052        if call.media.receive.state == MediaChannelState::Opening
3053            && call
3054                .media
3055                .receive
3056                .deadline
3057                .is_some_and(|deadline| deadline <= now)
3058        {
3059            call.media.receive.state = MediaChannelState::Closed;
3060            call.media.receive.deadline = None;
3061            call.media.receive.peer = None;
3062            if call.media.coupled_transmit_endpoint.take().is_some() {
3063                call.media.transmit.state = MediaChannelState::Closed;
3064                call.media.transmit.deadline = None;
3065                call.media.transmit.peer = None;
3066            }
3067            expired.push((call_id, HandsetAcknowledgement::OpenReceiveChannel));
3068        }
3069        if call.media.transmit.state == MediaChannelState::Opening
3070            && call
3071                .media
3072                .transmit
3073                .deadline
3074                .is_some_and(|deadline| deadline <= now)
3075        {
3076            call.media.transmit.state = MediaChannelState::Closed;
3077            call.media.transmit.deadline = None;
3078            call.media.transmit.peer = None;
3079            expired.push((call_id, HandsetAcknowledgement::StartMediaTransmission));
3080        }
3081    }
3082    expired
3083}
3084
3085async fn handle_pre_registration_message(
3086    stream: &mut dyn StationIo,
3087    message: ClientMessage,
3088    context: &SessionContext,
3089) -> Result<(), ServerError> {
3090    match message {
3091        ClientMessage::KeepAlive => {
3092            send_message(stream, &ServerMessage::KeepAliveAck, ProtocolVersion::V3).await?;
3093        }
3094        ClientMessage::RegisterToken(token) => {
3095            let definition = context
3096                .definitions
3097                .read()
3098                .expect("SCCP definitions lock poisoned")
3099                .get(&token.device_id)
3100                .cloned();
3101            let configured = definition.is_some()
3102                || context
3103                    .anonymous_hotline
3104                    .read()
3105                    .expect("SCCP anonymous-hotline lock poisoned")
3106                    .is_some();
3107            let transport_permitted = definition.as_ref().is_none_or(|definition| {
3108                transport_allowed(definition.transport, context.transport)
3109            });
3110            let already_registered = context.sessions.lock().await.contains_key(&token.device_id);
3111            let accept = configured
3112                && transport_permitted
3113                && !already_registered
3114                && context.config.registration_tokens.accepts(&token.device_id);
3115            let response = if accept {
3116                ServerMessage::RegisterTokenAck
3117            } else {
3118                ServerMessage::RegisterTokenReject {
3119                    backoff_seconds: u32::try_from(
3120                        context.config.registration_tokens.backoff.as_secs(),
3121                    )
3122                    .unwrap_or(u32::MAX),
3123                }
3124            };
3125            send_message(stream, &response, ProtocolVersion::V17).await?;
3126        }
3127        ClientMessage::Alarm {
3128            severity,
3129            text,
3130            parameters,
3131        } => {
3132            debug!(peer = %context.peer, ?severity, %text, ?parameters, "pre-registration SCCP alarm");
3133        }
3134        ClientMessage::XmlAlarm(message) => match parse_phone_alarm(message.xml_bytes()) {
3135            Ok(telemetry) => {
3136                debug!(
3137                    peer = %context.peer,
3138                    payload_len = message.xml_bytes().len(),
3139                    summary = ?telemetry.summary(),
3140                    opaque = telemetry.is_opaque(),
3141                    "pre-registration SCCP XML alarm"
3142                );
3143            }
3144            Err(error) => {
3145                warn!(
3146                    peer = %context.peer,
3147                    payload_len = message.xml_bytes().len(),
3148                    %error,
3149                    "rejected pre-registration SCCP XML alarm"
3150                );
3151            }
3152        },
3153        ClientMessage::LocationInfo { xml } => match parse_phone_location(xml.as_bytes()) {
3154            Ok(telemetry) => {
3155                debug!(
3156                    peer = %context.peer,
3157                    payload_len = xml.len(),
3158                    summary = ?telemetry.summary(),
3159                    opaque = telemetry.is_opaque(),
3160                    "pre-registration SCCP location information"
3161                );
3162            }
3163            Err(error) => {
3164                warn!(
3165                    peer = %context.peer,
3166                    payload_len = xml.len(),
3167                    %error,
3168                    "rejected pre-registration SCCP location information"
3169                );
3170            }
3171        },
3172        ClientMessage::KnownOpaque(message) => {
3173            debug!(peer = %context.peer, message = ?message, "pre-registration deferred SCCP message");
3174        }
3175        ClientMessage::Unknown(message) => {
3176            warn!(peer = %context.peer, message = ?message, "pre-registration unknown SCCP message");
3177        }
3178        ClientMessage::Register(_)
3179        | ClientMessage::IpPort { .. }
3180        | ClientMessage::KeypadButton { .. }
3181        | ClientMessage::EnblocCall { .. }
3182        | ClientMessage::Stimulus { .. }
3183        | ClientMessage::OffHook { .. }
3184        | ClientMessage::OnHook { .. }
3185        | ClientMessage::OffHookWithCallingParty { .. }
3186        | ClientMessage::LineStatRequest { .. }
3187        | ClientMessage::ConfigStatRequest
3188        | ClientMessage::TimeDateRequest
3189        | ClientMessage::ButtonTemplateRequest
3190        | ClientMessage::VersionRequest
3191        | ClientMessage::CapabilitiesResponse(_)
3192        | ClientMessage::CapabilitiesUpdate(_)
3193        | ClientMessage::OpenMultimediaReceiveChannelAck(_)
3194        | ClientMessage::ServerRequest
3195        | ClientMessage::MulticastMediaReceptionAck { .. }
3196        | ClientMessage::OpenReceiveChannelAck { .. }
3197        | ClientMessage::SoftKeySetRequest
3198        | ClientMessage::SoftKeyTemplateRequest
3199        | ClientMessage::SoftKeyEvent { .. }
3200        | ClientMessage::Unregister { .. }
3201        | ClientMessage::HookFlash { .. }
3202        | ClientMessage::ForwardStatusRequest { .. }
3203        | ClientMessage::SpeedDialStatusRequest { .. }
3204        | ClientMessage::ConnectionStatisticsResponse(_)
3205        | ClientMessage::HeadsetStatus { .. }
3206        | ClientMessage::MediaResourceNotification(_)
3207        | ClientMessage::MediaPathEvent { .. }
3208        | ClientMessage::MediaPathCapability { .. }
3209        | ClientMessage::MediaTransmissionFailure { .. }
3210        | ClientMessage::RegisterAvailableLines { .. }
3211        | ClientMessage::ServiceUrlStatusRequest { .. }
3212        | ClientMessage::FeatureStatusRequest { .. }
3213        | ClientMessage::StartMediaTransmissionAck(_)
3214        | ClientMessage::StartMultimediaTransmissionAck(_)
3215        | ClientMessage::ExtensionDeviceCapabilities(_)
3216        | ClientMessage::DeviceToUserData(_)
3217        | ClientMessage::DeviceToUserDataResponse(_)
3218        | ClientMessage::DeviceToUserDataV1(_)
3219        | ClientMessage::DeviceToUserDataResponseV1(_)
3220        | ClientMessage::PortResponse(_)
3221        | ClientMessage::SubscriptionStatusRequest(_)
3222        | ClientMessage::SubscribeDtmfPayloadResponse(_)
3223        | ClientMessage::UnsubscribeDtmfPayloadResponse(_)
3224        | ClientMessage::CallCountRequest { .. }
3225        | ClientMessage::CreateConferenceResponse(_)
3226        | ClientMessage::DeleteConferenceResponse { .. }
3227        | ClientMessage::ModifyConferenceResponse(_)
3228        | ClientMessage::AuditConferenceResponse(_)
3229        | ClientMessage::AddParticipantResponse(_)
3230        | ClientMessage::AuditParticipantResponse(_) => {
3231            warn!(peer = %context.peer, message = ?message, "ignoring SCCP message before registration");
3232        }
3233    }
3234    Ok(())
3235}
3236
3237async fn handle_client_message(
3238    stream: &mut dyn StationIo,
3239    state: &mut SessionState,
3240    message: ClientMessage,
3241    context: &SessionContext,
3242) -> Result<(), ServerError> {
3243    let protocol = state.registration.protocol;
3244    match message {
3245        ClientMessage::KeepAlive => {
3246            send_message(stream, &ServerMessage::KeepAliveAck, protocol).await?
3247        }
3248        ClientMessage::CapabilitiesResponse(capabilities) => {
3249            let capabilities = StationMediaCapabilities::from(capabilities);
3250            state.media_capabilities.clone_from(&capabilities);
3251            context
3252                .event_tx
3253                .send(Event::device(
3254                    state.device.id.clone(),
3255                    state.generation,
3256                    DeviceEventKind::Capabilities { capabilities },
3257                ))
3258                .await
3259                .map_err(|_| ServerError::Stopped)?;
3260        }
3261        ClientMessage::CapabilitiesUpdate(update) => {
3262            let capabilities = update.into_media_capabilities();
3263            state.media_capabilities.clone_from(&capabilities);
3264            context
3265                .event_tx
3266                .send(Event::device(
3267                    state.device.id.clone(),
3268                    state.generation,
3269                    DeviceEventKind::Capabilities { capabilities },
3270                ))
3271                .await
3272                .map_err(|_| ServerError::Stopped)?;
3273        }
3274        ClientMessage::ConfigStatRequest => {
3275            send_station_ui_message(
3276                stream,
3277                state,
3278                &ServerMessage::ConfigStatus(crate::message::ConfigurationStatus {
3279                    device_name: state.device.id.as_str().to_owned(),
3280                    station_user_id: 0,
3281                    station_instance: 1,
3282                    user_name: state.device.description.clone(),
3283                    server_name: context.config.server_name.clone(),
3284                    line_count: state.device.line_count() as u32,
3285                    speed_dial_count: 0,
3286                }),
3287            )
3288            .await?;
3289        }
3290        ClientMessage::LineStatRequest { line_instance } => {
3291            if let Some(message) = line_status(&state.device, line_instance) {
3292                send_station_ui_message(stream, state, &message).await?;
3293            }
3294        }
3295        ClientMessage::ButtonTemplateRequest => {
3296            send_button_template(stream, &state.device, protocol).await?;
3297        }
3298        ClientMessage::VersionRequest => {
3299            send_message(
3300                stream,
3301                &ServerMessage::Version {
3302                    firmware: context.config.firmware_version.clone(),
3303                },
3304                protocol,
3305            )
3306            .await?;
3307        }
3308        ClientMessage::ServerRequest => {
3309            send_message(
3310                stream,
3311                &ServerMessage::ServerResponse {
3312                    servers: server_response_endpoints(context, protocol)?,
3313                },
3314                protocol,
3315            )
3316            .await?;
3317        }
3318        ClientMessage::TimeDateRequest => {
3319            send_message(
3320                stream,
3321                &time_date_message(context.config.timezone_offset_minutes),
3322                protocol,
3323            )
3324            .await?
3325        }
3326        ClientMessage::SoftKeyTemplateRequest => {
3327            send_message(
3328                stream,
3329                &ServerMessage::SoftKeyTemplate {
3330                    actions: state.device.soft_keys.template_actions(),
3331                },
3332                protocol,
3333            )
3334            .await?
3335        }
3336        ClientMessage::SoftKeySetRequest => {
3337            send_message(
3338                stream,
3339                &ServerMessage::SoftKeySet {
3340                    profile: state.device.soft_keys.clone(),
3341                },
3342                protocol,
3343            )
3344            .await?
3345        }
3346        ClientMessage::ForwardStatusRequest { line_instance } => {
3347            let forwarding = state
3348                .forwarding_by_line
3349                .get(&line_instance)
3350                .cloned()
3351                .unwrap_or_default();
3352            send_message(
3353                stream,
3354                &ServerMessage::ForwardStatus {
3355                    line_instance,
3356                    forward_all: forwarding.all,
3357                    forward_busy: forwarding.busy,
3358                    forward_no_answer: forwarding.no_answer,
3359                },
3360                protocol,
3361            )
3362            .await?;
3363        }
3364        ClientMessage::SpeedDialStatusRequest {
3365            speed_dial_instance,
3366        } => {
3367            send_station_ui_message(
3368                stream,
3369                state,
3370                &speed_dial_status(&state.device, speed_dial_instance),
3371            )
3372            .await?;
3373        }
3374        ClientMessage::FeatureStatusRequest {
3375            index,
3376            capabilities,
3377        } => {
3378            if let Some(mut message) = feature_status(&state.device, index, capabilities) {
3379                apply_cached_feature_projection(&state.feature_states, index, &mut message);
3380                send_station_ui_message(stream, state, &message).await?;
3381            }
3382        }
3383        ClientMessage::ServiceUrlStatusRequest { index } => {
3384            if let Some(message) = service_url_status(&state.device, index) {
3385                send_station_ui_message(stream, state, &message).await?;
3386            }
3387        }
3388        ClientMessage::SubscriptionStatusRequest(request) => {
3389            send_message(
3390                stream,
3391                &ServerMessage::SubscriptionStatus {
3392                    transaction_id: request.transaction_id,
3393                    feature_id: request.feature_id,
3394                    timer_seconds: 0,
3395                    cause: SubscriptionCause::RouteFailure,
3396                },
3397                protocol,
3398            )
3399            .await?;
3400        }
3401        ClientMessage::RegisterAvailableLines { .. } => {
3402            debug!(device_id = %state.device.id, "phone finished registering available lines");
3403        }
3404        ClientMessage::OffHook {
3405            line_instance,
3406            call_reference,
3407        } => {
3408            if let Some(active_call) = find_call(state, call_reference)
3409                && !matches!(
3410                    active_call.state,
3411                    CallState::RingIn | CallState::CallWaiting | CallState::OnHook
3412                )
3413            {
3414                debug!(
3415                    device_id = %state.device.id,
3416                    call_id = ?active_call.call_id,
3417                    call_state = ?active_call.state,
3418                    line_instance,
3419                    call_reference,
3420                    "ignoring duplicate OffHook while a call is already active"
3421                );
3422                return Ok(());
3423            }
3424            let line = normalize_line(state, line_instance);
3425            let answer = find_answer_call(
3426                state,
3427                call_reference,
3428                line_instance,
3429                *context
3430                    .call_answer_order
3431                    .read()
3432                    .expect("SCCP call-answer-order lock poisoned"),
3433            )
3434            .cloned();
3435            let answering = answer.is_some();
3436            let call = answer.unwrap_or_else(|| {
3437                ensure_phone_call(state, call_reference, line, &context.next_call_id)
3438            });
3439            if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3440                stored.state = CallState::OffHook;
3441            }
3442            state.active_call_id = Some(call.call_id);
3443            if answering {
3444                begin_answer_ui(stream, &call, protocol).await?;
3445            } else {
3446                state.active_key_mode = KeyMode::OffHook;
3447                begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3448            }
3449            context
3450                .event_tx
3451                .send(Event::device(
3452                    state.device.id.clone(),
3453                    state.generation,
3454                    DeviceEventKind::OffHook {
3455                        call_id: call.call_id,
3456                        line_instance: LineInstance::new(line),
3457                    },
3458                ))
3459                .await
3460                .map_err(|_| ServerError::Stopped)?;
3461        }
3462        ClientMessage::OnHook {
3463            line_instance,
3464            call_reference,
3465        } => {
3466            state.pending_media_path_release = None;
3467            if let Some(call) = find_call(state, call_reference).cloned() {
3468                let line = if line_instance == 0 {
3469                    call.line_instance
3470                } else {
3471                    line_instance
3472                };
3473                complete_on_hook(stream, state, context, call, line).await?;
3474            }
3475        }
3476        ClientMessage::HookFlash {
3477            line_instance,
3478            call_reference,
3479        } => {
3480            let line_instance = normalize_line(state, line_instance);
3481            let call_id = find_call(state, call_reference).map(|call| call.call_id);
3482            context
3483                .event_tx
3484                .send(Event::device(
3485                    state.device.id.clone(),
3486                    state.generation,
3487                    DeviceEventKind::HookFlash {
3488                        call_id,
3489                        line_instance: LineInstance::new(line_instance),
3490                    },
3491                ))
3492                .await
3493                .map_err(|_| ServerError::Stopped)?;
3494        }
3495        ClientMessage::KeypadButton {
3496            button,
3497            call_reference,
3498            ..
3499        } => {
3500            if let Some(call) = find_call(state, call_reference) {
3501                if matches!(button, Digit::Unknown(_)) {
3502                    return Ok(());
3503                }
3504                let call = call.clone();
3505                if matches!(
3506                    call.state,
3507                    CallState::Connected
3508                        | CallState::Hold
3509                        | CallState::HoldYellow
3510                        | CallState::HoldRed
3511                ) && call.media.transmit.state.is_open()
3512                    && call.media.transmit.telephone_event_payload != 0
3513                {
3514                    // The handset sends connected-call digits in RTP when a
3515                    // telephone-event payload was negotiated. Forwarding the
3516                    // signaling copy would produce duplicate DTMF in the PBX.
3517                    return Ok(());
3518                }
3519                let collecting = matches!(call.state, CallState::OffHook | CallState::Transfer);
3520                if collecting && state.active_key_mode != KeyMode::DigitsFollowing {
3521                    state.active_key_mode = KeyMode::DigitsFollowing;
3522                    send_message(
3523                        stream,
3524                        &ServerMessage::StopTone {
3525                            line_instance: call.line_instance,
3526                            call_reference: call.wire_reference,
3527                        },
3528                        protocol,
3529                    )
3530                    .await?;
3531                    send_message(
3532                        stream,
3533                        &ServerMessage::SelectSoftKeys {
3534                            line_instance: call.line_instance,
3535                            call_reference: call.wire_reference,
3536                            set: KeyMode::DigitsFollowing,
3537                            valid_mask: state.device.soft_keys.valid_mask(KeyMode::DigitsFollowing),
3538                        },
3539                        protocol,
3540                    )
3541                    .await?;
3542                }
3543                if collecting && let Some(character) = digit_character(button) {
3544                    let number = if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3545                        stored.dialed_number.push(character);
3546                        stored.dialed_number.clone()
3547                    } else {
3548                        String::new()
3549                    };
3550                    if button == context.config.dial_terminator {
3551                        remember_last_number(state, call.line_instance, &number, &context.config);
3552                    }
3553                }
3554                context
3555                    .event_tx
3556                    .send(Event::device(
3557                        state.device.id.clone(),
3558                        state.generation,
3559                        DeviceEventKind::Digit {
3560                            call_id: call.call_id,
3561                            digit: button,
3562                        },
3563                    ))
3564                    .await
3565                    .map_err(|_| ServerError::Stopped)?;
3566            }
3567        }
3568        ClientMessage::EnblocCall {
3569            called_party,
3570            line_instance,
3571            ..
3572        } => {
3573            let line = normalize_line(state, line_instance);
3574            let existing = state
3575                .calls_by_id
3576                .values()
3577                .find(|call| call.line_instance == line && call.state != CallState::OnHook)
3578                .cloned();
3579            let created = existing.is_none();
3580            let call = existing
3581                .unwrap_or_else(|| ensure_phone_call(state, 0, line, &context.next_call_id));
3582            if created {
3583                state.active_key_mode = KeyMode::OffHook;
3584                begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3585                context
3586                    .event_tx
3587                    .send(Event::device(
3588                        state.device.id.clone(),
3589                        state.generation,
3590                        DeviceEventKind::OffHook {
3591                            call_id: call.call_id,
3592                            line_instance: LineInstance::new(line),
3593                        },
3594                    ))
3595                    .await
3596                    .map_err(|_| ServerError::Stopped)?;
3597            }
3598            if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3599                stored.dialed_number.clone_from(&called_party);
3600            }
3601            remember_last_number(state, call.line_instance, &called_party, &context.config);
3602            context
3603                .event_tx
3604                .send(Event::device(
3605                    state.device.id.clone(),
3606                    state.generation,
3607                    DeviceEventKind::EnblocCall {
3608                        call_id: call.call_id,
3609                        line_instance: LineInstance::new(line),
3610                        number: called_party,
3611                    },
3612                ))
3613                .await
3614                .map_err(|_| ServerError::Stopped)?;
3615        }
3616        ClientMessage::SoftKeyEvent {
3617            event,
3618            line_instance,
3619            call_reference,
3620        } => {
3621            let received_soft_key = SoftKey::from(event);
3622            if !state
3623                .device
3624                .soft_keys
3625                .allows(state.active_key_mode, received_soft_key)
3626            {
3627                debug!(
3628                    device_id = %state.device.id,
3629                    mode = state.active_key_mode.wire_value(),
3630                    event,
3631                    "ignoring unavailable soft-key event"
3632                );
3633                return Ok(());
3634            }
3635            let line = normalize_line(state, line_instance);
3636            let mut soft_key = received_soft_key;
3637            let ringing_call = find_answer_call(
3638                state,
3639                call_reference,
3640                line_instance,
3641                *context
3642                    .call_answer_order
3643                    .read()
3644                    .expect("SCCP call-answer-order lock poisoned"),
3645            );
3646            let mut call_id = if matches!(soft_key, SoftKey::Answer | SoftKey::NewCall)
3647                && let Some(call) = ringing_call
3648            {
3649                soft_key = SoftKey::Answer;
3650                Some(call.call_id)
3651            } else {
3652                find_call(state, call_reference).map(|call| call.call_id)
3653            };
3654            if soft_key == SoftKey::MeetMe
3655                && call_id.is_some_and(|call_id| {
3656                    state
3657                        .calls_by_id
3658                        .get(&call_id)
3659                        .is_some_and(|call| call.state != CallState::OffHook)
3660                })
3661            {
3662                call_id = None;
3663            }
3664            if matches!(
3665                soft_key,
3666                SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3667            ) && call_id.is_some_and(|call_id| {
3668                state
3669                    .calls_by_id
3670                    .get(&call_id)
3671                    .is_some_and(|call| call.state == CallState::OnHook)
3672            }) {
3673                call_id = None;
3674            }
3675            if soft_key == SoftKey::Redial {
3676                begin_redial(stream, state, context, line, call_id).await?;
3677                return Ok(());
3678            }
3679            if call_id.is_none()
3680                && matches!(
3681                    soft_key,
3682                    SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3683                )
3684            {
3685                let call = if soft_key == SoftKey::MeetMe {
3686                    reserve_phone_call(state, line, &context.next_call_id)
3687                } else {
3688                    ensure_phone_call(state, 0, line, &context.next_call_id)
3689                };
3690                state.active_call_id = Some(call.call_id);
3691                state.active_key_mode = KeyMode::OffHook;
3692                begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3693                context
3694                    .event_tx
3695                    .send(Event::device(
3696                        state.device.id.clone(),
3697                        state.generation,
3698                        DeviceEventKind::OffHook {
3699                            call_id: call.call_id,
3700                            line_instance: LineInstance::new(line),
3701                        },
3702                    ))
3703                    .await
3704                    .map_err(|_| ServerError::Stopped)?;
3705                call_id = Some(call.call_id);
3706            }
3707            if soft_key == SoftKey::Backspace
3708                && let Some(call_id) = call_id
3709                && let Some(call) = state.calls_by_id.get_mut(&call_id)
3710            {
3711                call.dialed_number.pop();
3712                let call = call.clone();
3713                send_message(
3714                    stream,
3715                    &ServerMessage::BackspaceResponse {
3716                        line_instance: call.line_instance,
3717                        call_reference: call.wire_reference,
3718                    },
3719                    protocol,
3720                )
3721                .await?;
3722            }
3723            if soft_key == SoftKey::Dial
3724                && let Some(call) = call_id.and_then(|call_id| state.calls_by_id.get(&call_id))
3725            {
3726                let line_instance = call.line_instance;
3727                let number = call.dialed_number.clone();
3728                remember_last_number(state, line_instance, &number, &context.config);
3729            }
3730            if soft_key == SoftKey::Answer
3731                && let Some(call) = call_id.and_then(|call_id| state.calls_by_id.get_mut(&call_id))
3732            {
3733                call.state = CallState::OffHook;
3734                let call = call.clone();
3735                state.active_call_id = Some(call.call_id);
3736                begin_answer_ui(stream, &call, protocol).await?;
3737            }
3738            context
3739                .event_tx
3740                .send(Event::device(
3741                    state.device.id.clone(),
3742                    state.generation,
3743                    DeviceEventKind::SoftKey {
3744                        call_id,
3745                        line_instance: LineInstance::new(line),
3746                        soft_key,
3747                    },
3748                ))
3749                .await
3750                .map_err(|_| ServerError::Stopped)?;
3751        }
3752        ClientMessage::Stimulus {
3753            stimulus,
3754            instance,
3755            call_reference,
3756            ..
3757        } => {
3758            let mut call_id = find_call(state, call_reference).map(|call| call.call_id);
3759            if stimulus == Stimulus::MeetMeConference
3760                && call_id.is_some_and(|call_id| {
3761                    state
3762                        .calls_by_id
3763                        .get(&call_id)
3764                        .is_some_and(|call| call.state != CallState::OffHook)
3765                })
3766            {
3767                call_id = None;
3768            }
3769            if matches!(
3770                stimulus,
3771                Stimulus::Line
3772                    | Stimulus::NewCall
3773                    | Stimulus::CallPickup
3774                    | Stimulus::GroupCallPickup
3775            ) && call_id.is_some_and(|call_id| {
3776                state
3777                    .calls_by_id
3778                    .get(&call_id)
3779                    .is_some_and(|call| call.state == CallState::OnHook)
3780            }) {
3781                call_id = None;
3782            }
3783            if stimulus == Stimulus::Line {
3784                let line = normalize_line(state, instance);
3785                if call_id.is_none() {
3786                    let call = ensure_phone_call(state, 0, line, &context.next_call_id);
3787                    state.active_key_mode = KeyMode::OffHook;
3788                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3789                        .await?;
3790                    context
3791                        .event_tx
3792                        .send(Event::device(
3793                            state.device.id.clone(),
3794                            state.generation,
3795                            DeviceEventKind::OffHook {
3796                                call_id: call.call_id,
3797                                line_instance: LineInstance::new(line),
3798                            },
3799                        ))
3800                        .await
3801                        .map_err(|_| ServerError::Stopped)?;
3802                } else {
3803                    context
3804                        .event_tx
3805                        .send(Event::device(
3806                            state.device.id.clone(),
3807                            state.generation,
3808                            DeviceEventKind::LineButton {
3809                                line_instance: LineInstance::new(line),
3810                                call_id,
3811                            },
3812                        ))
3813                        .await
3814                        .map_err(|_| ServerError::Stopped)?;
3815                }
3816            } else if matches!(stimulus, Stimulus::SpeedDial | Stimulus::BlfSpeedDial) {
3817                let number = state.device.buttons.iter().find_map(|button| match button {
3818                    ButtonDefinition::SpeedDial(speed_dial) if speed_dial.instance == instance => {
3819                        Some(speed_dial.number.clone())
3820                    }
3821                    ButtonDefinition::BlfSpeedDial(speed_dial)
3822                        if speed_dial.instance == instance =>
3823                    {
3824                        Some(speed_dial.number.clone())
3825                    }
3826                    _ => None,
3827                });
3828                let Some(number) = number else {
3829                    debug!(
3830                        device_id = %state.device.id,
3831                        instance,
3832                        "ignoring unconfigured speed-dial button stimulus"
3833                    );
3834                    return Ok(());
3835                };
3836
3837                // Reuse an existing outbound digit-collection call. Creating
3838                // a second call beside any other live call is permitted only
3839                // when the station advertised feature bit 30.
3840                let collecting_call = call_id
3841                    .and_then(|call_id| state.calls_by_id.get(&call_id))
3842                    .filter(|call| matches!(call.state, CallState::OffHook | CallState::Transfer))
3843                    .cloned();
3844                let has_live_call = state
3845                    .calls_by_id
3846                    .values()
3847                    .any(|call| call.state != CallState::OnHook);
3848                if collecting_call.is_none()
3849                    && has_live_call
3850                    && !state
3851                        .features
3852                        .contains(PhoneFeatures::MULTIPLE_ACTIVE_CALLS)
3853                {
3854                    debug!(
3855                        device_id = %state.device.id,
3856                        instance,
3857                        "ignoring speed-dial button beside an active call without multiple-active-call support"
3858                    );
3859                    return Ok(());
3860                }
3861                let (call, new_call) = collecting_call.map_or_else(
3862                    || {
3863                        let line = call_id
3864                            .and_then(|call_id| state.calls_by_id.get(&call_id))
3865                            .map_or_else(|| normalize_line(state, 0), |call| call.line_instance);
3866                        (reserve_phone_call(state, line, &context.next_call_id), true)
3867                    },
3868                    |call| (call, false),
3869                );
3870
3871                if new_call {
3872                    state.active_call_id = Some(call.call_id);
3873                    state.active_key_mode = KeyMode::OffHook;
3874                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3875                        .await?;
3876                    context
3877                        .event_tx
3878                        .send(Event::device(
3879                            state.device.id.clone(),
3880                            state.generation,
3881                            DeviceEventKind::OffHook {
3882                                call_id: call.call_id,
3883                                line_instance: LineInstance::new(call.line_instance),
3884                            },
3885                        ))
3886                        .await
3887                        .map_err(|_| ServerError::Stopped)?;
3888                }
3889                if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3890                    stored.dialed_number.clone_from(&number);
3891                }
3892                let await_further_digits = state.device.ui.speed_dial_await_further_digits;
3893                if await_further_digits {
3894                    state.active_key_mode = KeyMode::DigitsFollowing;
3895                    for message in [
3896                        ServerMessage::StopTone {
3897                            line_instance: call.line_instance,
3898                            call_reference: call.wire_reference,
3899                        },
3900                        ServerMessage::DialedNumber {
3901                            number: number.clone(),
3902                            line_instance: call.line_instance,
3903                            call_reference: call.wire_reference,
3904                        },
3905                        ServerMessage::SelectSoftKeys {
3906                            line_instance: call.line_instance,
3907                            call_reference: call.wire_reference,
3908                            set: KeyMode::DigitsFollowing,
3909                            valid_mask: state.device.soft_keys.valid_mask(KeyMode::DigitsFollowing),
3910                        },
3911                    ] {
3912                        send_station_ui_message(stream, state, &message).await?;
3913                    }
3914                }
3915                context
3916                    .event_tx
3917                    .send(Event::device(
3918                        state.device.id.clone(),
3919                        state.generation,
3920                        DeviceEventKind::SpeedDial {
3921                            call_id: call.call_id,
3922                            line_instance: LineInstance::new(call.line_instance),
3923                            number,
3924                            await_further_digits,
3925                        },
3926                    ))
3927                    .await
3928                    .map_err(|_| ServerError::Stopped)?;
3929            } else if stimulus == Stimulus::ParkingLot {
3930                let configured = state.device.buttons.iter().any(|button| {
3931                    matches!(
3932                        button,
3933                        ButtonDefinition::Feature(feature)
3934                            if feature.instance == instance
3935                                && feature.feature == ButtonType::ParkingLot
3936                    )
3937                });
3938                if !configured {
3939                    debug!(
3940                        device_id = %state.device.id,
3941                        instance,
3942                        "ignoring unconfigured parking-lot button stimulus"
3943                    );
3944                    return Ok(());
3945                }
3946                let line_instance = call_id
3947                    .and_then(|call_id| state.calls_by_id.get(&call_id))
3948                    .map_or_else(|| normalize_line(state, 0), |call| call.line_instance);
3949                context
3950                    .event_tx
3951                    .send(Event::device(
3952                        state.device.id.clone(),
3953                        state.generation,
3954                        DeviceEventKind::ParkingLotButton {
3955                            instance: LineInstance::new(instance),
3956                            call_id,
3957                            line_instance: LineInstance::new(line_instance),
3958                        },
3959                    ))
3960                    .await
3961                    .map_err(|_| ServerError::Stopped)?;
3962            } else if stimulus == Stimulus::Privacy {
3963                let configured = state.device.buttons.iter().any(|button| {
3964                    matches!(
3965                        button,
3966                        ButtonDefinition::Feature(feature)
3967                            if feature.instance == instance
3968                                && feature.feature == ButtonType::Feature
3969                    )
3970                });
3971                if !configured {
3972                    debug!(
3973                        device_id = %state.device.id,
3974                        instance,
3975                        "ignoring unconfigured generic feature-button stimulus"
3976                    );
3977                    return Ok(());
3978                }
3979                context
3980                    .event_tx
3981                    .send(Event::device(
3982                        state.device.id.clone(),
3983                        state.generation,
3984                        DeviceEventKind::FeatureButton {
3985                            instance: LineInstance::new(instance),
3986                        },
3987                    ))
3988                    .await
3989                    .map_err(|_| ServerError::Stopped)?;
3990            } else if stimulus == Stimulus::DoNotDisturb {
3991                let configured = state.device.buttons.iter().any(|button| {
3992                    matches!(
3993                        button,
3994                        ButtonDefinition::Feature(feature)
3995                            if feature.instance == instance
3996                                && feature.feature == ButtonType::DoNotDisturb
3997                    )
3998                });
3999                if !configured {
4000                    debug!(
4001                        device_id = %state.device.id,
4002                        instance,
4003                        "ignoring unconfigured do-not-disturb button stimulus"
4004                    );
4005                    return Ok(());
4006                }
4007                context
4008                    .event_tx
4009                    .send(Event::device(
4010                        state.device.id.clone(),
4011                        state.generation,
4012                        DeviceEventKind::DoNotDisturbButton {
4013                            instance: LineInstance::new(instance),
4014                        },
4015                    ))
4016                    .await
4017                    .map_err(|_| ServerError::Stopped)?;
4018            } else if stimulus == Stimulus::Mobility {
4019                let configured = state.device.buttons.iter().any(|button| {
4020                    matches!(
4021                        button,
4022                        ButtonDefinition::Feature(feature)
4023                            if feature.instance == instance
4024                                && feature.feature == ButtonType::Mobility
4025                    )
4026                });
4027                if !configured {
4028                    debug!(
4029                        device_id = %state.device.id,
4030                        instance,
4031                        "ignoring unconfigured mobility button stimulus"
4032                    );
4033                    return Ok(());
4034                }
4035                context
4036                    .event_tx
4037                    .send(Event::device(
4038                        state.device.id.clone(),
4039                        state.generation,
4040                        DeviceEventKind::MobilityButton {
4041                            instance: LineInstance::new(instance),
4042                        },
4043                    ))
4044                    .await
4045                    .map_err(|_| ServerError::Stopped)?;
4046            } else if stimulus == Stimulus::Voicemail {
4047                let configured = state.device.buttons.iter().any(|button| {
4048                    matches!(
4049                        button,
4050                        ButtonDefinition::Feature(feature)
4051                            if feature.instance == instance
4052                                && feature.feature == ButtonType::Voicemail
4053                    )
4054                });
4055                if !configured {
4056                    debug!(
4057                        device_id = %state.device.id,
4058                        instance,
4059                        "ignoring unconfigured voicemail button stimulus"
4060                    );
4061                    return Ok(());
4062                }
4063                let line = call_id
4064                    .and_then(|call_id| state.calls_by_id.get(&call_id))
4065                    .map_or_else(
4066                        || normalize_line(state, instance),
4067                        |call| call.line_instance,
4068                    );
4069                let call = call_id
4070                    .and_then(|call_id| state.calls_by_id.get(&call_id).cloned())
4071                    .unwrap_or_else(|| ensure_phone_call(state, 0, line, &context.next_call_id));
4072                if call_id.is_none() {
4073                    state.active_call_id = Some(call.call_id);
4074                    state.active_key_mode = KeyMode::OffHook;
4075                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
4076                        .await?;
4077                    context
4078                        .event_tx
4079                        .send(Event::device(
4080                            state.device.id.clone(),
4081                            state.generation,
4082                            DeviceEventKind::OffHook {
4083                                call_id: call.call_id,
4084                                line_instance: LineInstance::new(line),
4085                            },
4086                        ))
4087                        .await
4088                        .map_err(|_| ServerError::Stopped)?;
4089                }
4090                context
4091                    .event_tx
4092                    .send(Event::device(
4093                        state.device.id.clone(),
4094                        state.generation,
4095                        DeviceEventKind::VoicemailButton {
4096                            call_id: call.call_id,
4097                            line_instance: LineInstance::new(line),
4098                        },
4099                    ))
4100                    .await
4101                    .map_err(|_| ServerError::Stopped)?;
4102            } else {
4103                let line = normalize_line(state, instance);
4104                let Some(soft_key) = stimulus_soft_key(stimulus) else {
4105                    debug!(
4106                        device_id = %state.device.id,
4107                        stimulus = stimulus.wire_value(),
4108                        "ignoring stimulus without a soft-key action mapping"
4109                    );
4110                    return Ok(());
4111                };
4112                if !state
4113                    .device
4114                    .soft_keys
4115                    .allows(state.active_key_mode, soft_key)
4116                {
4117                    debug!(
4118                        device_id = %state.device.id,
4119                        mode = state.active_key_mode.wire_value(),
4120                        stimulus = stimulus.wire_value(),
4121                        "ignoring unavailable soft-key stimulus"
4122                    );
4123                    return Ok(());
4124                }
4125                if soft_key == SoftKey::Redial {
4126                    begin_redial(stream, state, context, line, call_id).await?;
4127                    return Ok(());
4128                }
4129                if matches!(
4130                    soft_key,
4131                    SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
4132                ) && call_id.is_none()
4133                {
4134                    let call = if soft_key == SoftKey::MeetMe {
4135                        reserve_phone_call(state, line, &context.next_call_id)
4136                    } else {
4137                        ensure_phone_call(state, 0, line, &context.next_call_id)
4138                    };
4139                    state.active_call_id = Some(call.call_id);
4140                    state.active_key_mode = KeyMode::OffHook;
4141                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
4142                        .await?;
4143                    context
4144                        .event_tx
4145                        .send(Event::device(
4146                            state.device.id.clone(),
4147                            state.generation,
4148                            DeviceEventKind::OffHook {
4149                                call_id: call.call_id,
4150                                line_instance: LineInstance::new(line),
4151                            },
4152                        ))
4153                        .await
4154                        .map_err(|_| ServerError::Stopped)?;
4155                    call_id = Some(call.call_id);
4156                }
4157                context
4158                    .event_tx
4159                    .send(Event::device(
4160                        state.device.id.clone(),
4161                        state.generation,
4162                        DeviceEventKind::SoftKey {
4163                            call_id,
4164                            line_instance: LineInstance::new(line),
4165                            soft_key,
4166                        },
4167                    ))
4168                    .await
4169                    .map_err(|_| ServerError::Stopped)?;
4170            }
4171        }
4172        ClientMessage::MulticastMediaReceptionAck {
4173            status,
4174            passthrough_party_id,
4175            call_reference,
4176        } => {
4177            let Some(key) =
4178                find_multicast_receive_key(state, call_reference.get(), passthrough_party_id.get())
4179            else {
4180                debug!(
4181                    device_id = %state.device.id,
4182                    "ignored stale or mismatched multicast reception acknowledgement"
4183                );
4184                return Ok(());
4185            };
4186            if status == MediaStatus::Ok {
4187                let route = {
4188                    let receive = state
4189                        .multicast
4190                        .get_mut(&key)
4191                        .and_then(|session| session.receive.as_mut())
4192                        .expect("multicast key came from current receive state");
4193                    receive.state = MulticastReceiveState::Open;
4194                    receive.route
4195                };
4196                context
4197                    .event_tx
4198                    .send(Event::device(
4199                        state.device.id.clone(),
4200                        state.generation,
4201                        DeviceEventKind::MulticastReceptionStarted {
4202                            conference_id: key.conference_id,
4203                            call_id: key.call_id,
4204                            route,
4205                        },
4206                    ))
4207                    .await
4208                    .map_err(|_| ServerError::Stopped)?;
4209            } else {
4210                if let Some(stop) = take_multicast_stop(state, key, true) {
4211                    send_message(stream, &stop, protocol).await?;
4212                }
4213                context
4214                    .event_tx
4215                    .send(Event::device(
4216                        state.device.id.clone(),
4217                        state.generation,
4218                        DeviceEventKind::MulticastReceptionFailed {
4219                            conference_id: key.conference_id,
4220                            call_id: key.call_id,
4221                            status,
4222                        },
4223                    ))
4224                    .await
4225                    .map_err(|_| ServerError::Stopped)?;
4226            }
4227        }
4228        ClientMessage::OpenReceiveChannelAck {
4229            status,
4230            address,
4231            port,
4232            call_reference,
4233            passthrough_party_id,
4234        } => {
4235            if let Some(call_id) =
4236                find_receive_media_call_id(state, call_reference, passthrough_party_id)
4237            {
4238                let call = state
4239                    .calls_by_id
4240                    .get(&call_id)
4241                    .expect("media call identifier came from session state")
4242                    .clone();
4243                if call.media.receive.state != MediaChannelState::Opening {
4244                    debug!(
4245                        device_id = %state.device.id,
4246                        call_id = ?call.call_id,
4247                        state = ?call.media.receive.state,
4248                        "ignored stale receive-channel acknowledgement"
4249                    );
4250                    return Ok(());
4251                }
4252                let endpoint = MediaEndpoint {
4253                    address,
4254                    rtp_port: port,
4255                    rtcp_port: port.saturating_add(1),
4256                    codec: call.media.codec,
4257                    packet_ms: call.media.packet_ms,
4258                    max_frames_per_packet: call.media.max_frames_per_packet,
4259                    telephone_event_payload: call.media.receive.telephone_event_payload,
4260                };
4261                let stored = state
4262                    .calls_by_id
4263                    .get_mut(&call_id)
4264                    .expect("media call identifier came from session state");
4265                let implied_transmit = if status == MediaStatus::Ok {
4266                    stored.media.receive.state = MediaChannelState::Open;
4267                    stored.media.receive.peer = Some(endpoint);
4268                    if let Some(endpoint) = stored.media.coupled_transmit_endpoint.take() {
4269                        stored.media.transmit.state = MediaChannelState::Open;
4270                        stored.media.transmit.peer = Some(endpoint);
4271                        stored.media.transmit.deadline = None;
4272                        Some(endpoint)
4273                    } else {
4274                        None
4275                    }
4276                } else {
4277                    stored.media.receive.state = MediaChannelState::Closed;
4278                    stored.media.receive.peer = None;
4279                    if stored.media.coupled_transmit_endpoint.take().is_some() {
4280                        stored.media.transmit.state = MediaChannelState::Closed;
4281                        stored.media.transmit.peer = None;
4282                        stored.media.transmit.deadline = None;
4283                    }
4284                    None
4285                };
4286                stored.media.receive.deadline = None;
4287                context
4288                    .event_tx
4289                    .send(Event::device(
4290                        state.device.id.clone(),
4291                        state.generation,
4292                        DeviceEventKind::ReceiveChannelOpened {
4293                            call_id: call.call_id,
4294                            status,
4295                            endpoint,
4296                        },
4297                    ))
4298                    .await
4299                    .map_err(|_| ServerError::Stopped)?;
4300                if let Some(endpoint) = implied_transmit {
4301                    context
4302                        .event_tx
4303                        .send(Event::device(
4304                            state.device.id.clone(),
4305                            state.generation,
4306                            DeviceEventKind::TransmitChannelImplied {
4307                                call_id: call.call_id,
4308                                endpoint,
4309                            },
4310                        ))
4311                        .await
4312                        .map_err(|_| ServerError::Stopped)?;
4313                }
4314            }
4315        }
4316        ClientMessage::StartMediaTransmissionAck(ack) => {
4317            if let Some(call_id) = find_transmit_media_call_id(
4318                state,
4319                ack.conference_id,
4320                ack.call_reference,
4321                ack.passthrough_party_id,
4322            ) {
4323                let call = state
4324                    .calls_by_id
4325                    .get(&call_id)
4326                    .expect("media call identifier came from session state")
4327                    .clone();
4328                if call.media.transmit.state != MediaChannelState::Opening {
4329                    debug!(
4330                        device_id = %state.device.id,
4331                        call_id = ?call.call_id,
4332                        state = ?call.media.transmit.state,
4333                        "ignored stale transmit-channel acknowledgement"
4334                    );
4335                    return Ok(());
4336                }
4337                let endpoint = MediaEndpoint {
4338                    address: ack.address,
4339                    rtp_port: ack.port,
4340                    rtcp_port: ack.port.saturating_add(1),
4341                    codec: call.media.codec,
4342                    packet_ms: call.media.packet_ms,
4343                    max_frames_per_packet: call.media.max_frames_per_packet,
4344                    telephone_event_payload: call.media.transmit.telephone_event_payload,
4345                };
4346                let stored = state
4347                    .calls_by_id
4348                    .get_mut(&call_id)
4349                    .expect("media call identifier came from session state");
4350                let coupled = stored.media.coupled_transmit_endpoint.take().is_some();
4351                if ack.status == MediaStatus::Ok {
4352                    stored.media.transmit.state = MediaChannelState::Open;
4353                    stored.media.transmit.peer = Some(endpoint);
4354                } else {
4355                    stored.media.transmit.state = MediaChannelState::Closed;
4356                    stored.media.transmit.peer = None;
4357                    if coupled {
4358                        stored.media.receive.state = MediaChannelState::Closed;
4359                        stored.media.receive.deadline = None;
4360                        stored.media.receive.peer = None;
4361                    }
4362                }
4363                stored.media.transmit.deadline = None;
4364                context
4365                    .event_tx
4366                    .send(Event::device(
4367                        state.device.id.clone(),
4368                        state.generation,
4369                        DeviceEventKind::TransmitChannelStarted {
4370                            call_id: call.call_id,
4371                            status: ack.status,
4372                            endpoint,
4373                        },
4374                    ))
4375                    .await
4376                    .map_err(|_| ServerError::Stopped)?;
4377            }
4378        }
4379        ClientMessage::Alarm {
4380            severity,
4381            text,
4382            parameters,
4383        } => {
4384            context
4385                .event_tx
4386                .send(Event::device(
4387                    state.device.id.clone(),
4388                    state.generation,
4389                    DeviceEventKind::Alarm {
4390                        severity,
4391                        text,
4392                        parameters,
4393                    },
4394                ))
4395                .await
4396                .map_err(|_| ServerError::Stopped)?;
4397        }
4398        ClientMessage::XmlAlarm(message) => match parse_phone_alarm(message.xml_bytes()) {
4399            Ok(telemetry) => {
4400                context
4401                    .event_tx
4402                    .send(Event::device(
4403                        state.device.id.clone(),
4404                        state.generation,
4405                        DeviceEventKind::XmlAlarm { telemetry },
4406                    ))
4407                    .await
4408                    .map_err(|_| ServerError::Stopped)?;
4409            }
4410            Err(error) => {
4411                warn!(
4412                    device_id = %state.device.id,
4413                    payload_len = message.xml_bytes().len(),
4414                    %error,
4415                    "rejected SCCP XML alarm"
4416                );
4417            }
4418        },
4419        ClientMessage::LocationInfo { xml } => match parse_phone_location(xml.as_bytes()) {
4420            Ok(telemetry) => {
4421                context
4422                    .event_tx
4423                    .send(Event::device(
4424                        state.device.id.clone(),
4425                        state.generation,
4426                        DeviceEventKind::LocationInformation { telemetry },
4427                    ))
4428                    .await
4429                    .map_err(|_| ServerError::Stopped)?;
4430            }
4431            Err(error) => {
4432                warn!(
4433                    device_id = %state.device.id,
4434                    payload_len = xml.len(),
4435                    %error,
4436                    "rejected SCCP location information"
4437                );
4438            }
4439        },
4440        ClientMessage::Unregister { .. } => {
4441            send_message(stream, &ServerMessage::UnregisterAck, protocol).await?;
4442        }
4443        ClientMessage::CallCountRequest { .. } => {
4444            send_message(stream, &ServerMessage::CallCountResponse, protocol).await?;
4445        }
4446        ClientMessage::ConnectionStatisticsResponse(statistics) => {
4447            collect_connection_statistics(state, statistics, context).await?;
4448        }
4449        ClientMessage::MediaTransmissionFailure {
4450            conference_id,
4451            passthrough_party_id,
4452            address,
4453            port,
4454            call_reference,
4455            status,
4456        } => {
4457            if let Some(key) = find_multicast_transmit_key(
4458                state,
4459                conference_id,
4460                call_reference,
4461                passthrough_party_id,
4462                address,
4463                port,
4464            ) {
4465                if let Some(stop) = take_multicast_stop(state, key, false) {
4466                    send_message(stream, &stop, protocol).await?;
4467                }
4468                context
4469                    .event_tx
4470                    .send(Event::device(
4471                        state.device.id.clone(),
4472                        state.generation,
4473                        DeviceEventKind::MulticastTransmissionFailed {
4474                            conference_id: key.conference_id,
4475                            call_id: key.call_id,
4476                            status,
4477                            address,
4478                            port,
4479                        },
4480                    ))
4481                    .await
4482                    .map_err(|_| ServerError::Stopped)?;
4483                return Ok(());
4484            }
4485            let Some(call_id) = find_transmit_media_call_id(
4486                state,
4487                conference_id,
4488                call_reference,
4489                passthrough_party_id,
4490            ) else {
4491                return Ok(());
4492            };
4493            let call = state
4494                .calls_by_id
4495                .get(&call_id)
4496                .expect("media call identifier came from session state")
4497                .clone();
4498            let Some(endpoint) = call.media.transmit.peer else {
4499                return Ok(());
4500            };
4501            if call.media.transmit.state != MediaChannelState::Open
4502                || (conference_id != 0 && conference_id != call.wire_reference)
4503                || endpoint.address != address
4504                || endpoint.rtp_port != port
4505            {
4506                debug!(
4507                    device_id = %state.device.id,
4508                    call_id = ?call.call_id,
4509                    "ignored stale or mismatched media-transmission failure"
4510                );
4511                return Ok(());
4512            }
4513            let stored = state
4514                .calls_by_id
4515                .get_mut(&call_id)
4516                .expect("media call identifier came from session state");
4517            stored.media.transmit.state = MediaChannelState::Closed;
4518            stored.media.transmit.peer = None;
4519            context
4520                .event_tx
4521                .send(Event::device(
4522                    state.device.id.clone(),
4523                    state.generation,
4524                    DeviceEventKind::MediaTransmissionFailed {
4525                        call_id,
4526                        status,
4527                        endpoint,
4528                    },
4529                ))
4530                .await
4531                .map_err(|_| ServerError::Stopped)?;
4532        }
4533        ClientMessage::HeadsetStatus { enabled } => {
4534            if state.headset_enabled != enabled {
4535                state.headset_enabled = enabled;
4536                context
4537                    .event_tx
4538                    .send(Event::device(
4539                        state.device.id.clone(),
4540                        state.generation,
4541                        DeviceEventKind::HeadsetStatusChanged { enabled },
4542                    ))
4543                    .await
4544                    .map_err(|_| ServerError::Stopped)?;
4545            }
4546        }
4547        ClientMessage::MediaPathEvent {
4548            path,
4549            event: media_path_event,
4550        } => {
4551            if state.media_path_states.get(&path) != Some(&media_path_event) {
4552                state.media_path_states.insert(path, media_path_event);
4553                if media_path_event == crate::message::values::MediaPathEvent::On {
4554                    state.pending_media_path_release = None;
4555                } else if media_path_event == crate::message::values::MediaPathEvent::Off
4556                    && is_local_audio_path(path)
4557                    && !has_active_media_path(state)
4558                    && let Some(call_id) = active_media_path_call(state)
4559                {
4560                    state.pending_media_path_release = Some(PendingMediaPathRelease {
4561                        call_id,
4562                        path,
4563                        deadline: Instant::now() + MEDIA_PATH_RELEASE_GRACE,
4564                    });
4565                }
4566                context
4567                    .event_tx
4568                    .send(Event::device(
4569                        state.device.id.clone(),
4570                        state.generation,
4571                        DeviceEventKind::MediaPathChanged {
4572                            path,
4573                            event: media_path_event,
4574                        },
4575                    ))
4576                    .await
4577                    .map_err(|_| ServerError::Stopped)?;
4578            }
4579        }
4580        ClientMessage::MediaPathCapability { .. } => {}
4581        message @ (ClientMessage::IpPort { .. }
4582        | ClientMessage::OffHookWithCallingParty { .. }
4583        | ClientMessage::MediaResourceNotification(_)
4584        | ClientMessage::SubscribeDtmfPayloadResponse(_)
4585        | ClientMessage::UnsubscribeDtmfPayloadResponse(_)
4586        | ClientMessage::PortResponse(_)) => {
4587            debug!(device_id = %state.device.id, message = ?message, "consumed SCCP telemetry");
4588        }
4589        ClientMessage::DeviceToUserData(message) => {
4590            handle_phone_service_message(
4591                state,
4592                context,
4593                crate::message::wire_id::DEVICE_TO_USER_DATA,
4594                PhoneServiceMessageKind::Data,
4595                PhoneServiceRouting {
4596                    application_id: ApplicationId::new(message.application_id),
4597                    line_instance: LineInstance::new(message.line_instance),
4598                    call_reference: CallReference::new(message.call_reference),
4599                    transaction_id: TransactionId::new(message.transaction_id),
4600                },
4601                None,
4602                &message.data,
4603            )
4604            .await?;
4605        }
4606        ClientMessage::DeviceToUserDataResponse(message) => {
4607            handle_phone_service_message(
4608                state,
4609                context,
4610                crate::message::wire_id::DEVICE_TO_USER_DATA_RESPONSE,
4611                PhoneServiceMessageKind::Response,
4612                PhoneServiceRouting {
4613                    application_id: ApplicationId::new(message.application_id),
4614                    line_instance: LineInstance::new(message.line_instance),
4615                    call_reference: CallReference::new(message.call_reference),
4616                    transaction_id: TransactionId::new(message.transaction_id),
4617                },
4618                None,
4619                &message.data,
4620            )
4621            .await?;
4622        }
4623        ClientMessage::DeviceToUserDataV1(message) => {
4624            handle_phone_service_message(
4625                state,
4626                context,
4627                crate::message::wire_id::DEVICE_TO_USER_DATA_V1,
4628                PhoneServiceMessageKind::Data,
4629                PhoneServiceRouting {
4630                    application_id: ApplicationId::new(message.application_id),
4631                    line_instance: LineInstance::new(message.line_instance),
4632                    call_reference: CallReference::new(message.call_reference),
4633                    transaction_id: TransactionId::new(message.transaction_id),
4634                },
4635                Some(PhoneServiceExtendedRouting {
4636                    sequence_flag: message.sequence_flag,
4637                    display_priority: message.display_priority,
4638                    conference_id: message.conference_id,
4639                    application_instance_id: message.application_instance_id,
4640                    routing: message.routing,
4641                }),
4642                &message.data,
4643            )
4644            .await?;
4645        }
4646        ClientMessage::DeviceToUserDataResponseV1(message) => {
4647            handle_phone_service_message(
4648                state,
4649                context,
4650                crate::message::wire_id::DEVICE_TO_USER_DATA_RESPONSE_V1,
4651                PhoneServiceMessageKind::Response,
4652                PhoneServiceRouting {
4653                    application_id: ApplicationId::new(message.application_id),
4654                    line_instance: LineInstance::new(message.line_instance),
4655                    call_reference: CallReference::new(message.call_reference),
4656                    transaction_id: TransactionId::new(message.transaction_id),
4657                },
4658                Some(PhoneServiceExtendedRouting {
4659                    sequence_flag: message.sequence_flag,
4660                    display_priority: message.display_priority,
4661                    conference_id: message.conference_id,
4662                    application_instance_id: message.application_instance_id,
4663                    routing: message.routing,
4664                }),
4665                &message.data,
4666            )
4667            .await?;
4668        }
4669        ClientMessage::OpenMultimediaReceiveChannelAck(ack) => {
4670            let Some(call_id) = state.calls_by_wire.get(&ack.call_reference.get()).copied() else {
4671                debug!(device_id = %state.device.id, "ignored video receive acknowledgement for an unknown call");
4672                return Ok(());
4673            };
4674            let Some((request, codec, requested_address_type)) =
4675                state.calls_by_id.get(&call_id).and_then(|call| {
4676                    call.video_receive.leg.as_ref().and_then(|leg| {
4677                        (leg.state == MediaChannelState::Opening
4678                            && leg.request.token().get() == ack.passthrough_party_id.get())
4679                        .then_some((leg.request, leg.codec, leg.requested_address_type))
4680                    })
4681                })
4682            else {
4683                debug!(device_id = %state.device.id, ?call_id, "ignored stale video receive acknowledgement");
4684                return Ok(());
4685            };
4686
4687            let event = if ack.status == MediaStatus::Ok {
4688                if !endpoint_is_usable(ack.endpoint)
4689                    || !address_matches_type(ack.endpoint.address, requested_address_type)
4690                {
4691                    debug!(device_id = %state.device.id, ?call_id, "ignored unusable video receive endpoint");
4692                    return Ok(());
4693                }
4694                let leg = state
4695                    .calls_by_id
4696                    .get_mut(&call_id)
4697                    .and_then(|call| call.video_receive.leg.as_mut())
4698                    .expect("correlated video receive leg remains present");
4699                debug_assert_eq!(leg.request, request);
4700                leg.state = MediaChannelState::Open;
4701                leg.deadline = None;
4702                DeviceEventKind::MultimediaReceiveChannelOpened {
4703                    call_id,
4704                    codec,
4705                    endpoint: ack.endpoint,
4706                    passthrough_party_id: ack.passthrough_party_id,
4707                }
4708            } else {
4709                let close = take_multimedia_receive_close(state, call_id)
4710                    .expect("correlated video receive leg remains present");
4711                send_message(stream, &close, protocol).await?;
4712                DeviceEventKind::MultimediaReceiveChannelFailed {
4713                    call_id,
4714                    codec,
4715                    status: ack.status,
4716                    endpoint: ack.endpoint,
4717                    passthrough_party_id: ack.passthrough_party_id,
4718                }
4719            };
4720            context
4721                .event_tx
4722                .send(Event::device(
4723                    state.device.id.clone(),
4724                    state.generation,
4725                    event,
4726                ))
4727                .await
4728                .map_err(|_| ServerError::Stopped)?;
4729        }
4730        ClientMessage::StartMultimediaTransmissionAck(ack) => {
4731            let Some(call_id) = state.calls_by_wire.get(&ack.call_reference.get()).copied() else {
4732                debug!(device_id = %state.device.id, "ignored video transmit acknowledgement for an unknown call");
4733                return Ok(());
4734            };
4735            let Some((request, codec, address_type)) =
4736                state.calls_by_id.get(&call_id).and_then(|call| {
4737                    call.video_transmit.leg.as_ref().and_then(|leg| {
4738                        (leg.state == MediaChannelState::Opening
4739                            && leg.request.token().get() == ack.passthrough_party_id.get()
4740                            && leg.conference_id == ack.conference_id)
4741                            .then_some((leg.request, leg.codec, leg.address_type))
4742                    })
4743                })
4744            else {
4745                debug!(device_id = %state.device.id, ?call_id, "ignored stale video transmit acknowledgement");
4746                return Ok(());
4747            };
4748
4749            let event = if ack.status == MediaStatus::Ok {
4750                if !endpoint_is_usable(ack.endpoint)
4751                    || !address_matches_type(ack.endpoint.address, address_type)
4752                {
4753                    debug!(device_id = %state.device.id, ?call_id, "ignored unusable video transmit endpoint");
4754                    return Ok(());
4755                }
4756                let leg = state
4757                    .calls_by_id
4758                    .get_mut(&call_id)
4759                    .and_then(|call| call.video_transmit.leg.as_mut())
4760                    .expect("correlated video transmit leg remains present");
4761                debug_assert_eq!(leg.request, request);
4762                leg.state = MediaChannelState::Open;
4763                leg.deadline = None;
4764                DeviceEventKind::MultimediaTransmitStarted {
4765                    call_id,
4766                    codec,
4767                    endpoint: ack.endpoint,
4768                    passthrough_party_id: ack.passthrough_party_id,
4769                }
4770            } else {
4771                let stop = take_multimedia_transmit_stop(state, call_id)
4772                    .expect("correlated video transmit leg remains present");
4773                send_message(stream, &stop, protocol).await?;
4774                DeviceEventKind::MultimediaTransmitFailed {
4775                    call_id,
4776                    codec,
4777                    status: ack.status,
4778                    endpoint: ack.endpoint,
4779                    passthrough_party_id: ack.passthrough_party_id,
4780                }
4781            };
4782            context
4783                .event_tx
4784                .send(Event::device(
4785                    state.device.id.clone(),
4786                    state.generation,
4787                    event,
4788                ))
4789                .await
4790                .map_err(|_| ServerError::Stopped)?;
4791        }
4792        message @ (ClientMessage::ExtensionDeviceCapabilities(_)
4793        | ClientMessage::CreateConferenceResponse(_)
4794        | ClientMessage::DeleteConferenceResponse { .. }
4795        | ClientMessage::ModifyConferenceResponse(_)
4796        | ClientMessage::AuditConferenceResponse(_)
4797        | ClientMessage::AddParticipantResponse(_)
4798        | ClientMessage::AuditParticipantResponse(_)) => {
4799            debug!(device_id = %state.device.id, message = ?message, "deferred SCCP application message");
4800            context
4801                .event_tx
4802                .send(Event::device(
4803                    state.device.id.clone(),
4804                    state.generation,
4805                    DeviceEventKind::UnhandledMessage { message },
4806                ))
4807                .await
4808                .map_err(|_| ServerError::Stopped)?;
4809        }
4810        ClientMessage::KnownOpaque(message) => {
4811            let message = ClientMessage::KnownOpaque(message);
4812            debug!(device_id = %state.device.id, message = ?message, "unhandled SCCP message");
4813            context
4814                .event_tx
4815                .send(Event::device(
4816                    state.device.id.clone(),
4817                    state.generation,
4818                    DeviceEventKind::UnhandledMessage { message },
4819                ))
4820                .await
4821                .map_err(|_| ServerError::Stopped)?;
4822        }
4823        ClientMessage::Unknown(message) => {
4824            let message = ClientMessage::Unknown(message);
4825            warn!(device_id = %state.device.id, message = ?message, "unknown SCCP message");
4826            context
4827                .event_tx
4828                .send(Event::device(
4829                    state.device.id.clone(),
4830                    state.generation,
4831                    DeviceEventKind::UnhandledMessage { message },
4832                ))
4833                .await
4834                .map_err(|_| ServerError::Stopped)?;
4835        }
4836        ClientMessage::Register(_) | ClientMessage::RegisterToken(_) => {
4837            warn!(device_id = %state.device.id, "ignoring registration message on registered session");
4838        }
4839    }
4840    Ok(())
4841}
4842
4843const fn is_local_audio_path(path: crate::message::values::MediaPathId) -> bool {
4844    matches!(
4845        path,
4846        crate::message::values::MediaPathId::Headset
4847            | crate::message::values::MediaPathId::Handset
4848            | crate::message::values::MediaPathId::Speaker
4849    )
4850}
4851
4852fn has_active_media_path(state: &SessionState) -> bool {
4853    state.media_path_states.iter().any(|(path, event)| {
4854        is_local_audio_path(*path) && *event == crate::message::values::MediaPathEvent::On
4855    })
4856}
4857
4858fn active_media_path_call(state: &SessionState) -> Option<CallId> {
4859    state.active_call_id.filter(|call_id| {
4860        state.calls_by_id.get(call_id).is_some_and(|call| {
4861            !matches!(
4862                call.state,
4863                CallState::OnHook
4864                    | CallState::RingIn
4865                    | CallState::CallWaiting
4866                    | CallState::Hold
4867                    | CallState::HoldYellow
4868                    | CallState::HoldRed
4869            )
4870        })
4871    })
4872}
4873
4874async fn complete_on_hook(
4875    stream: &mut dyn StationIo,
4876    state: &mut SessionState,
4877    context: &SessionContext,
4878    call: SessionCall,
4879    line_instance: u32,
4880) -> Result<(), ServerError> {
4881    state.pending_media_path_release = None;
4882    context
4883        .event_tx
4884        .send(Event::device(
4885            state.device.id.clone(),
4886            state.generation,
4887            DeviceEventKind::OnHook {
4888                call_id: call.call_id,
4889                line_instance: LineInstance::new(line_instance),
4890            },
4891        ))
4892        .await
4893        .map_err(|_| ServerError::Stopped)?;
4894    state.active_key_mode = KeyMode::OnHook;
4895    stop_call_multicast(stream, state, call.call_id, state.registration.protocol).await?;
4896    close_call_media_messages(stream, &call, state.registration.protocol).await?;
4897    close_call_messages(
4898        stream,
4899        &call,
4900        &state.device.soft_keys,
4901        state.registration.protocol,
4902        context.config.timezone_offset_minutes,
4903    )
4904    .await?;
4905    request_connection_statistics(stream, state, &call, context).await?;
4906    if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
4907        stored.state = CallState::OnHook;
4908        stored.media.receive.state = MediaChannelState::Closed;
4909        stored.media.receive.deadline = None;
4910        stored.media.transmit.state = MediaChannelState::Closed;
4911        stored.media.transmit.deadline = None;
4912        stored.media.coupled_transmit_endpoint = None;
4913        stored.video_receive.leg = None;
4914        stored.video_transmit.leg = None;
4915    }
4916    if state.active_call_id == Some(call.call_id) {
4917        state.active_call_id = None;
4918    }
4919    Ok(())
4920}
4921
4922fn button_template(device: &DeviceDefinition) -> Vec<ButtonTemplateEntry> {
4923    let mut buttons = Vec::with_capacity(56);
4924    let mut addon_buttons_remaining = None;
4925    for button in &device.buttons {
4926        if let ButtonDefinition::AddonModule(addon) = button {
4927            buttons.extend(std::iter::repeat_n(
4928                ButtonTemplateEntry {
4929                    instance: 0,
4930                    button_type: ButtonType::Unused,
4931                },
4932                addon_buttons_remaining.take().unwrap_or_default(),
4933            ));
4934            addon_buttons_remaining = addon.button_capacity();
4935            continue;
4936        }
4937        buttons.push(match button {
4938            ButtonDefinition::Line(appearance) => ButtonTemplateEntry {
4939                instance: appearance.instance,
4940                button_type: ButtonType::Line,
4941            },
4942            ButtonDefinition::SpeedDial(speed_dial) => ButtonTemplateEntry {
4943                instance: speed_dial.instance,
4944                button_type: ButtonType::SpeedDial,
4945            },
4946            ButtonDefinition::BlfSpeedDial(speed_dial) => ButtonTemplateEntry {
4947                instance: speed_dial.instance,
4948                button_type: ButtonType::BlfSpeedDial,
4949            },
4950            ButtonDefinition::Feature(feature) => ButtonTemplateEntry {
4951                instance: feature.instance,
4952                button_type: ButtonType::from(feature.feature.wire_value()),
4953            },
4954            ButtonDefinition::Service(service) => ButtonTemplateEntry {
4955                instance: service.instance,
4956                button_type: ButtonType::ServiceUrl,
4957            },
4958            ButtonDefinition::Unused => ButtonTemplateEntry {
4959                instance: 0,
4960                button_type: ButtonType::Unused,
4961            },
4962            ButtonDefinition::AddonModule(_) => unreachable!("addon marker handled above"),
4963        });
4964        if let Some(remaining) = &mut addon_buttons_remaining {
4965            *remaining = remaining.saturating_sub(1);
4966        }
4967    }
4968    buttons.extend(std::iter::repeat_n(
4969        ButtonTemplateEntry {
4970            instance: 0,
4971            button_type: ButtonType::Unused,
4972        },
4973        addon_buttons_remaining.unwrap_or_default(),
4974    ));
4975    buttons
4976}
4977
4978async fn send_button_template(
4979    stream: &mut dyn StationIo,
4980    device: &DeviceDefinition,
4981    session: impl Into<StationSessionContext>,
4982) -> Result<(), ServerError> {
4983    let session = session.into();
4984    for message in button_template_messages(device)? {
4985        send_message(stream, &message, session).await?;
4986    }
4987    Ok(())
4988}
4989
4990fn button_template_messages(device: &DeviceDefinition) -> Result<Vec<ServerMessage>, CodecError> {
4991    let buttons = button_template(device);
4992    let total = u32::try_from(buttons.len()).map_err(|_| {
4993        CodecError::InvalidDefinition(format!(
4994            "device {} button template is too large for SCCP",
4995            device.id
4996        ))
4997    })?;
4998    if buttons.is_empty() {
4999        return Ok(vec![ServerMessage::ButtonTemplate {
5000            offset: 0,
5001            total: 0,
5002            buttons: Vec::new(),
5003        }]);
5004    }
5005    Ok(buttons
5006        .chunks(BUTTON_TEMPLATE_ENTRIES_PER_CHUNK)
5007        .enumerate()
5008        .map(|(chunk_index, chunk)| ServerMessage::ButtonTemplate {
5009            offset: u32::try_from(chunk_index * BUTTON_TEMPLATE_ENTRIES_PER_CHUNK)
5010                .expect("validated button template offset"),
5011            total,
5012            buttons: chunk.to_vec(),
5013        })
5014        .collect())
5015}
5016
5017fn line_status(device: &DeviceDefinition, instance: u32) -> Option<ServerMessage> {
5018    device
5019        .line(instance)
5020        .map(|appearance| ServerMessage::LineStatus {
5021            instance: appearance.instance,
5022            number: appearance.line.number.clone(),
5023            display_name: appearance.display_label().to_owned(),
5024        })
5025}
5026
5027fn mobility_device_candidate(
5028    current: &DeviceDefinition,
5029    current_appearances: &HashMap<u32, LineAppearance>,
5030    next_appearances: &HashMap<u32, LineAppearance>,
5031) -> Result<DeviceDefinition, CodecError> {
5032    let mut candidate = current.clone();
5033    candidate.buttons.retain(|button| {
5034        !matches!(
5035            button,
5036            ButtonDefinition::Line(line)
5037                if current_appearances.values().any(|appearance| appearance == line)
5038        )
5039    });
5040    let mut index = 0;
5041    while index < candidate.buttons.len() {
5042        let mobility_instance = match &candidate.buttons[index] {
5043            ButtonDefinition::Feature(feature) if feature.feature == ButtonType::Mobility => {
5044                Some(feature.instance)
5045            }
5046            _ => None,
5047        };
5048        if let Some(appearance) = mobility_instance
5049            .and_then(|instance| next_appearances.get(&instance))
5050            .cloned()
5051        {
5052            candidate
5053                .buttons
5054                .insert(index + 1, ButtonDefinition::Line(appearance));
5055            index += 2;
5056        } else {
5057            index += 1;
5058        }
5059    }
5060    candidate.validate()?;
5061    Ok(candidate)
5062}
5063
5064fn speed_dial_status(device: &DeviceDefinition, instance: u32) -> ServerMessage {
5065    let speed_dial = device.buttons.iter().find_map(|button| match button {
5066        ButtonDefinition::SpeedDial(speed_dial) if speed_dial.instance == instance => {
5067            Some((&speed_dial.number, &speed_dial.display_name))
5068        }
5069        _ => None,
5070    });
5071    ServerMessage::SpeedDialStatus {
5072        instance,
5073        number: speed_dial.map_or_else(String::new, |(number, _)| number.clone()),
5074        display_name: speed_dial.map_or_else(String::new, |(_, display_name)| display_name.clone()),
5075    }
5076}
5077
5078fn feature_status(
5079    device: &DeviceDefinition,
5080    instance: u32,
5081    _capabilities: u32,
5082) -> Option<ServerMessage> {
5083    if let Some(speed_dial) = device.blf_button(instance) {
5084        return Some(ServerMessage::FeatureStatus {
5085            instance,
5086            button_type: ButtonType::BlfSpeedDial,
5087            label: speed_dial.display_name.clone(),
5088            state: BusyLampFieldState::UnknownState.wire_value(),
5089        });
5090    }
5091    device
5092        .feature_button(instance)
5093        .map(|feature| ServerMessage::FeatureStatus {
5094            instance,
5095            button_type: ButtonType::from(feature.feature.wire_value()),
5096            label: feature.label.clone(),
5097            state: 0,
5098        })
5099}
5100
5101fn feature_state_messages(
5102    device: &DeviceDefinition,
5103    instance: u32,
5104    enabled: bool,
5105) -> Option<[ServerMessage; 2]> {
5106    let feature = device.feature_button(instance)?;
5107    Some([
5108        ServerMessage::FeatureStatus {
5109            instance,
5110            button_type: ButtonType::from(feature.feature.wire_value()),
5111            label: feature.label.clone(),
5112            state: u32::from(enabled),
5113        },
5114        ServerMessage::SetLamp {
5115            stimulus: feature.feature,
5116            instance,
5117            mode: if enabled { LampMode::On } else { LampMode::Off },
5118        },
5119    ])
5120}
5121
5122fn cache_feature_projection(
5123    cache: &mut HashMap<u32, SessionFeatureState>,
5124    instance: u32,
5125    message: &ServerMessage,
5126) {
5127    let ServerMessage::FeatureStatus {
5128        button_type, state, ..
5129    } = message
5130    else {
5131        debug_assert!(false, "feature projection cache requires FeatureStatus");
5132        return;
5133    };
5134    cache.insert(
5135        instance,
5136        SessionFeatureState {
5137            button_type: *button_type,
5138            state: *state,
5139        },
5140    );
5141}
5142
5143fn apply_cached_feature_projection(
5144    cache: &HashMap<u32, SessionFeatureState>,
5145    instance: u32,
5146    message: &mut ServerMessage,
5147) {
5148    let Some(cached) = cache.get(&instance) else {
5149        return;
5150    };
5151    if let ServerMessage::FeatureStatus {
5152        button_type, state, ..
5153    } = message
5154    {
5155        *button_type = cached.button_type;
5156        *state = cached.state;
5157    }
5158}
5159
5160/// Pack the three DND multiblink selectors used by v16+ stations.
5161///
5162/// The low byte selects the primary icon state, the middle byte the lamp
5163/// cadence, and the high byte the alternate state. Cisco's canonical words
5164/// are intentionally preserved rather than recomputed from boolean state.
5165const fn multiblink_dnd_state(mode: DoNotDisturbMode) -> u32 {
5166    const OFF: u32 = 0x01_00_00;
5167    const REJECT: u32 = 0x02_02_02;
5168    const SILENT: u32 = 0x03_03_02;
5169
5170    match mode {
5171        DoNotDisturbMode::Off => OFF,
5172        DoNotDisturbMode::Reject => REJECT,
5173        DoNotDisturbMode::Silent => SILENT,
5174    }
5175}
5176
5177fn do_not_disturb_state_messages(
5178    device: &DeviceDefinition,
5179    instance: u32,
5180    mode: DoNotDisturbMode,
5181    button_mode: DoNotDisturbButtonMode,
5182    protocol: ProtocolVersion,
5183) -> Option<[ServerMessage; 2]> {
5184    let feature = device
5185        .feature_button(instance)
5186        .filter(|feature| feature.feature == ButtonType::DoNotDisturb)?;
5187    let exact_enabled = match button_mode {
5188        DoNotDisturbButtonMode::Cycle => mode != DoNotDisturbMode::Off,
5189        DoNotDisturbButtonMode::Silent => mode == DoNotDisturbMode::Silent,
5190        DoNotDisturbButtonMode::Reject => mode == DoNotDisturbMode::Reject,
5191    };
5192    let multi_state =
5193        button_mode == DoNotDisturbButtonMode::Cycle && protocol > ProtocolVersion::V15;
5194    let (button_type, state) = if multi_state {
5195        (ButtonType::MultiblinkFeature, multiblink_dnd_state(mode))
5196    } else {
5197        (ButtonType::DoNotDisturb, u32::from(exact_enabled))
5198    };
5199    let lamp = match (exact_enabled, mode) {
5200        (false, _) | (_, DoNotDisturbMode::Off) => LampMode::Off,
5201        (true, DoNotDisturbMode::Silent) => LampMode::Blink,
5202        (true, DoNotDisturbMode::Reject) => LampMode::On,
5203    };
5204    Some([
5205        ServerMessage::FeatureStatus {
5206            instance,
5207            button_type,
5208            label: feature.label.clone(),
5209            state,
5210        },
5211        ServerMessage::SetLamp {
5212            stimulus: feature.feature,
5213            instance,
5214            mode: lamp,
5215        },
5216    ])
5217}
5218
5219fn blf_status_message(
5220    device: &DeviceDefinition,
5221    instance: u32,
5222    state: BlfState,
5223) -> Option<ServerMessage> {
5224    let definition = device.blf_button(instance)?;
5225    let icon = match state {
5226        BlfState::Idle => BusyLampFieldState::Idle,
5227        BlfState::Ringing => BusyLampFieldState::Alerting,
5228        BlfState::Busy | BlfState::Held => BusyLampFieldState::InUse,
5229        BlfState::DoNotDisturb => BusyLampFieldState::DoNotDisturb,
5230        BlfState::Unavailable | BlfState::Unknown => BusyLampFieldState::UnknownState,
5231    };
5232    Some(ServerMessage::FeatureStatus {
5233        instance,
5234        button_type: ButtonType::BlfSpeedDial,
5235        label: definition.display_name.clone(),
5236        state: icon.wire_value(),
5237    })
5238}
5239
5240fn hinted_ringing_notification(
5241    device: &DeviceDefinition,
5242    label: &str,
5243    caller: Option<&BlfCallerInfo>,
5244    state: BlfState,
5245) -> Option<HandsetStatusMessage> {
5246    if !device.ui.hinted_ringing_notification || state != BlfState::Ringing {
5247        return None;
5248    }
5249    let caller = caller.map(BlfCallerInfo::display).unwrap_or_default();
5250    let text = if caller.is_empty() {
5251        format!("{label} is ringing")
5252    } else {
5253        format!("{label} is ringing: {caller}")
5254    };
5255    Some(HandsetStatusMessage::Display {
5256        text: truncate_utf8(&text, 79),
5257        timeout_seconds: 5,
5258        priority: None,
5259    })
5260}
5261
5262fn reconcile_blf_alert(
5263    instance: u32,
5264    notification: Option<HandsetStatusMessage>,
5265    active: &mut BTreeMap<u32, HandsetStatusMessage>,
5266    visible: &mut Option<HandsetStatusMessage>,
5267) -> Option<HandsetStatusMessage> {
5268    match notification {
5269        Some(notification) => {
5270            active.insert(instance, notification);
5271        }
5272        None => {
5273            active.remove(&instance);
5274        }
5275    }
5276    let next = active.first_key_value().map(|(_, message)| message.clone());
5277    if *visible == next {
5278        return None;
5279    }
5280    *visible = next.clone();
5281    Some(next.unwrap_or(HandsetStatusMessage::Clear { priority: None }))
5282}
5283
5284fn truncate_utf8(value: &str, maximum_bytes: usize) -> String {
5285    if value.len() <= maximum_bytes {
5286        return value.to_owned();
5287    }
5288    let end = value
5289        .char_indices()
5290        .map(|(index, _)| index)
5291        .take_while(|index| *index <= maximum_bytes)
5292        .last()
5293        .unwrap_or(0);
5294    value[..end].to_owned()
5295}
5296
5297fn service_url_status(device: &DeviceDefinition, index: u32) -> Option<ServerMessage> {
5298    device.buttons.iter().find_map(|button| match button {
5299        ButtonDefinition::Service(service) if service.instance == index => {
5300            Some(ServerMessage::ServiceUrlStatus {
5301                index,
5302                url: service.url.clone(),
5303                label: service.label.clone(),
5304                extension_text: String::new(),
5305            })
5306        }
5307        _ => None,
5308    })
5309}
5310
5311const fn key_mode_for_call_state(state: CallState) -> KeyMode {
5312    match state {
5313        CallState::Connected => KeyMode::Connected,
5314        CallState::Hold | CallState::HoldYellow | CallState::HoldRed => KeyMode::OnHold,
5315        CallState::RingIn | CallState::CallWaiting => KeyMode::RingIn,
5316        CallState::OffHook
5317        | CallState::Busy
5318        | CallState::Congestion
5319        | CallState::InvalidNumber
5320        | CallState::IntercomOneWay => KeyMode::OffHook,
5321        CallState::Transfer => KeyMode::ConnectedTransfer,
5322        CallState::RingOut | CallState::Proceed => KeyMode::RingOut,
5323        CallState::RemoteMultiline => KeyMode::OnHookStealable,
5324        CallState::OnHook | CallState::Park | CallState::Unknown(_) => KeyMode::OnHook,
5325    }
5326}
5327
5328fn transfer_key_mode(call: &SessionCall, state: CallState) -> KeyMode {
5329    if matches!(
5330        call.transfer_role,
5331        Some(SessionTransferRole::Consultation { .. })
5332    ) && matches!(state, CallState::RingOut | CallState::Connected)
5333    {
5334        KeyMode::ConnectedTransfer
5335    } else {
5336        key_mode_for_call_state(state)
5337    }
5338}
5339
5340fn stimulus_soft_key(stimulus: Stimulus) -> Option<SoftKey> {
5341    Some(match stimulus {
5342        Stimulus::LastNumberRedial => SoftKey::Redial,
5343        Stimulus::Hold => SoftKey::Hold,
5344        Stimulus::Transfer => SoftKey::Transfer,
5345        Stimulus::ForwardAll => SoftKey::ForwardAll,
5346        Stimulus::ForwardBusy => SoftKey::ForwardBusy,
5347        Stimulus::ForwardNoAnswer => SoftKey::ForwardNoAnswer,
5348        Stimulus::Conference => SoftKey::Conference,
5349        Stimulus::MeetMeConference => SoftKey::MeetMe,
5350        Stimulus::CallPark => SoftKey::Park,
5351        Stimulus::CallPickup => SoftKey::Pickup,
5352        Stimulus::GroupCallPickup => SoftKey::GroupPickup,
5353        Stimulus::DoNotDisturb => SoftKey::DoNotDisturb,
5354        Stimulus::ConferenceList => SoftKey::ConferenceList,
5355        Stimulus::NewCall => SoftKey::NewCall,
5356        Stimulus::EndCall => SoftKey::EndCall,
5357        _ => return None,
5358    })
5359}
5360
5361fn parking_menu_xml(
5362    instance: u32,
5363    transaction_id: u32,
5364    lot: &str,
5365    calls: &[ParkingMenuEntry],
5366) -> Result<String, ServerError> {
5367    if calls.len() > PARKING_MENU_MAX_ITEMS {
5368        return Err(PhoneXmlError::LimitExceeded {
5369            kind: "parking menu",
5370            actual: calls.len(),
5371            maximum: PARKING_MENU_MAX_ITEMS,
5372        }
5373        .into());
5374    }
5375    let items = calls
5376        .iter()
5377        .map(|call| {
5378            let party = if !call.caller_name.trim().is_empty() {
5379                call.caller_name.trim()
5380            } else if !call.caller_number.trim().is_empty() {
5381                call.caller_number.trim()
5382            } else {
5383                "Unknown caller"
5384            };
5385            let connected = if !call.connected_name.trim().is_empty() {
5386                format!(" to {}", call.connected_name.trim())
5387            } else if !call.connected_number.trim().is_empty() {
5388                format!(" to {}", call.connected_number.trim())
5389            } else {
5390                String::new()
5391            };
5392            CiscoIpPhoneMenuItem {
5393                name: Some(format!("{}: {}{}", call.slot, party, connected)),
5394                url: Some(format!(
5395                    "UserCallData:{}:{instance}:0:{transaction_id}:retrieve/{}/{}",
5396                    PARKING_APPLICATION_ID,
5397                    utf8_percent_encode(lot, NON_ALPHANUMERIC),
5398                    call.slot,
5399                )),
5400            }
5401        })
5402        .collect();
5403    CiscoIpPhoneMenu::new(
5404        format!("Parked calls - {lot}"),
5405        if calls.is_empty() {
5406            "No parked calls"
5407        } else {
5408            "Select a call"
5409        },
5410        items,
5411    )?
5412    .to_xml_with_limit(2_000)
5413    .map_err(ServerError::from)
5414}
5415
5416fn text_service_messages(
5417    line_instance: LineInstance,
5418    call_reference: CallReference,
5419    transaction_id: TransactionId,
5420    priority: PhoneServicePriority,
5421    document: &CiscoIpPhoneText,
5422    protocol: ProtocolVersion,
5423) -> Result<Vec<ServerMessage>, ServerError> {
5424    if protocol <= ProtocolVersion::V17
5425        && document
5426            .text
5427            .as_deref()
5428            .is_some_and(|text| text.chars().count() > PHONE_TEXT_LEGACY_MAX_CHARS)
5429    {
5430        return Err(PhoneXmlError::InvalidField {
5431            field: "legacy phone text body",
5432            expected: "at most 1024 characters",
5433        }
5434        .into());
5435    }
5436    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5437        2_000
5438    } else {
5439        crate::phone::xml::PHONE_TEXT_MAX_BYTES
5440    };
5441    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5442    Ok(phone_service_document_messages(
5443        line_instance,
5444        call_reference,
5445        ApplicationId::new(PHONE_TEXT_APPLICATION_ID),
5446        transaction_id,
5447        priority,
5448        &xml,
5449    ))
5450}
5451
5452fn input_service_messages(
5453    line_instance: LineInstance,
5454    call_reference: CallReference,
5455    application_id: ApplicationId,
5456    transaction_id: TransactionId,
5457    priority: PhoneServicePriority,
5458    document: &CiscoIpPhoneInput,
5459    protocol: ProtocolVersion,
5460) -> Result<Vec<ServerMessage>, ServerError> {
5461    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5462        2_000
5463    } else {
5464        PHONE_INPUT_MAX_BYTES
5465    };
5466    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5467    Ok(phone_service_document_messages(
5468        line_instance,
5469        call_reference,
5470        application_id,
5471        transaction_id,
5472        priority,
5473        &xml,
5474    ))
5475}
5476
5477fn execute_phone_action_messages(
5478    line_instance: LineInstance,
5479    call_reference: CallReference,
5480    application_id: ApplicationId,
5481    transaction_id: TransactionId,
5482    priority: PhoneServicePriority,
5483    document: &CiscoIpPhoneExecute,
5484    protocol: ProtocolVersion,
5485) -> Result<Vec<ServerMessage>, ServerError> {
5486    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5487        2_000
5488    } else {
5489        PHONE_EXECUTE_MAX_BYTES
5490    };
5491    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5492    Ok(phone_service_document_messages(
5493        line_instance,
5494        call_reference,
5495        application_id,
5496        transaction_id,
5497        priority,
5498        &xml,
5499    ))
5500}
5501
5502fn image_service_messages(
5503    line_instance: LineInstance,
5504    call_reference: CallReference,
5505    application_id: ApplicationId,
5506    transaction_id: TransactionId,
5507    priority: PhoneServicePriority,
5508    document: &PhoneImageDocument,
5509    protocol: ProtocolVersion,
5510) -> Result<Vec<ServerMessage>, ServerError> {
5511    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5512        2_000
5513    } else {
5514        PHONE_IMAGE_MAX_BYTES
5515    };
5516    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5517    Ok(phone_service_document_messages(
5518        line_instance,
5519        call_reference,
5520        application_id,
5521        transaction_id,
5522        priority,
5523        &xml,
5524    ))
5525}
5526
5527fn status_service_messages(
5528    line_instance: LineInstance,
5529    call_reference: CallReference,
5530    application_id: ApplicationId,
5531    transaction_id: TransactionId,
5532    priority: PhoneServicePriority,
5533    document: &PhoneStatusDocument,
5534    protocol: ProtocolVersion,
5535) -> Result<Vec<ServerMessage>, ServerError> {
5536    let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5537        2_000
5538    } else {
5539        PHONE_STATUS_MAX_BYTES
5540    };
5541    let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5542    Ok(phone_service_document_messages(
5543        line_instance,
5544        call_reference,
5545        application_id,
5546        transaction_id,
5547        priority,
5548        &xml,
5549    ))
5550}
5551
5552fn background_control_message(
5553    transaction_id: TransactionId,
5554    document: &PhoneBackgroundControlDocument,
5555) -> Result<ServerMessage, ServerError> {
5556    let xml = document.to_xml()?.into_bytes();
5557    let [message] = phone_service_document_messages(
5558        LineInstance::new(0),
5559        CallReference::new(0),
5560        ApplicationId::new(PHONE_BACKGROUND_APPLICATION_ID),
5561        transaction_id,
5562        PhoneServicePriority::LOW,
5563        &xml,
5564    )
5565    .try_into()
5566    .map_err(|_| PhoneXmlError::InvalidField {
5567        field: "background control document",
5568        expected: "a single application-data frame",
5569    })?;
5570    Ok(message)
5571}
5572
5573fn ringtone_control_message(
5574    transaction_id: TransactionId,
5575    document: &CiscoIpPhoneSetRingTone,
5576) -> Result<ServerMessage, ServerError> {
5577    let xml = document.to_xml()?.into_bytes();
5578    let [message] = phone_service_document_messages(
5579        LineInstance::new(0),
5580        CallReference::new(0),
5581        ApplicationId::new(PHONE_RINGTONE_APPLICATION_ID),
5582        transaction_id,
5583        PhoneServicePriority::LOW,
5584        &xml,
5585    )
5586    .try_into()
5587    .map_err(|_| PhoneXmlError::InvalidField {
5588        field: "ringtone control document",
5589        expected: "a single application-data frame",
5590    })?;
5591    Ok(message)
5592}
5593
5594#[cfg(test)]
5595fn start_announcement_message(
5596    conference_id: ConferenceId,
5597    announcements: Vec<AnnouncementEntry>,
5598    end_of_ack: bool,
5599    participant_ids: Vec<ParticipantId>,
5600    hearing_participant_mask: u32,
5601    play_mode: u32,
5602) -> ServerMessage {
5603    ServerMessage::StartAnnouncement {
5604        announcements,
5605        end_of_ack: u32::from(end_of_ack),
5606        conference_id: conference_id.get(),
5607        matrix_conference_party_ids: participant_ids
5608            .into_iter()
5609            .map(ParticipantId::get)
5610            .collect(),
5611        hearing_conference_party_mask: hearing_participant_mask,
5612        play_mode,
5613    }
5614}
5615
5616fn phone_service_document_messages(
5617    line_instance: LineInstance,
5618    call_reference: CallReference,
5619    application_id: ApplicationId,
5620    transaction_id: TransactionId,
5621    priority: PhoneServicePriority,
5622    xml: &[u8],
5623) -> Vec<ServerMessage> {
5624    let chunks = xml.chunks(2_000);
5625    let chunk_count = chunks.len();
5626    chunks
5627        .enumerate()
5628        .map(|(index, data)| {
5629            let sequence_flag = if chunk_count == 1 || index + 1 == chunk_count {
5630                2
5631            } else if index == 0 {
5632                0
5633            } else {
5634                1
5635            };
5636            ServerMessage::UserToDeviceDataV1(UserDataV1Message {
5637                application_id: application_id.get(),
5638                line_instance: line_instance.get(),
5639                call_reference: call_reference.get(),
5640                transaction_id: transaction_id.get(),
5641                sequence_flag,
5642                display_priority: priority.wire(),
5643                conference_id: call_reference.get(),
5644                application_instance_id: application_id.get(),
5645                routing: 1,
5646                data: data.to_vec(),
5647            })
5648        })
5649        .collect()
5650}
5651
5652async fn handle_phone_service_message(
5653    state: &mut SessionState,
5654    context: &SessionContext,
5655    message_id: u32,
5656    kind: PhoneServiceMessageKind,
5657    routing: PhoneServiceRouting,
5658    extended: Option<PhoneServiceExtendedRouting>,
5659    data: &[u8],
5660) -> Result<(), ServerError> {
5661    let payload = match parse_phone_service_payload(data, kind) {
5662        Ok(payload) => payload,
5663        Err(error) => {
5664            warn!(
5665                device_id = %state.device.id,
5666                message_id = format_args!("0x{message_id:04x}"),
5667                %error,
5668                "ignoring malformed phone-service response"
5669            );
5670            context
5671                .event_tx
5672                .send(Event::ProtocolWarning {
5673                    peer: context.peer,
5674                    device_id: Some(state.device.id.clone()),
5675                    message_id,
5676                    error: error.to_string(),
5677                })
5678                .await
5679                .map_err(|_| ServerError::Stopped)?;
5680            return Ok(());
5681        }
5682    };
5683    let response = PhoneServiceEvent {
5684        kind,
5685        routing,
5686        extended,
5687        payload,
5688    };
5689
5690    if let Some((lot, slot)) = parking_menu_selection(state.pending_parking_menu, &response) {
5691        state.pending_parking_menu = None;
5692        context
5693            .event_tx
5694            .send(Event::device(
5695                state.device.id.clone(),
5696                state.generation,
5697                DeviceEventKind::ParkingMenuSelection { lot, slot },
5698            ))
5699            .await
5700            .map_err(|_| ServerError::Stopped)?;
5701    }
5702    if response.kind == PhoneServiceMessageKind::Data
5703        && response.routing.application_id.get() == ConferenceListAction::APPLICATION_ID
5704        && let PhoneServicePayload::Submission(submission) = &response.payload
5705        && let Some(action) = ConferenceListAction::from_route(&submission.route)
5706    {
5707        context
5708            .event_tx
5709            .send(Event::device(
5710                state.device.id.clone(),
5711                state.generation,
5712                DeviceEventKind::ConferenceListAction { action },
5713            ))
5714            .await
5715            .map_err(|_| ServerError::Stopped)?;
5716    }
5717    context
5718        .event_tx
5719        .send(Event::device(
5720            state.device.id.clone(),
5721            state.generation,
5722            DeviceEventKind::PhoneServiceResponse { response },
5723        ))
5724        .await
5725        .map_err(|_| ServerError::Stopped)
5726}
5727
5728fn parking_menu_selection(
5729    pending: Option<PendingParkingMenu>,
5730    response: &PhoneServiceEvent,
5731) -> Option<(String, u32)> {
5732    let pending = pending?;
5733    if response.kind != PhoneServiceMessageKind::Data
5734        || response.routing.application_id.get() != PARKING_APPLICATION_ID
5735        || response.routing.line_instance.get() != pending.instance
5736        || response.routing.call_reference.get() != 0
5737        || response.routing.transaction_id.get() != pending.transaction_id
5738        || response
5739            .extended
5740            .is_some_and(|extended| extended.application_instance_id != pending.instance)
5741    {
5742        return None;
5743    }
5744    let PhoneServicePayload::Submission(submission) = &response.payload else {
5745        return None;
5746    };
5747    let [action, lot, slot] = submission.route.as_slice() else {
5748        return None;
5749    };
5750    if action != "retrieve" || lot.is_empty() || !submission.values.is_empty() {
5751        return None;
5752    }
5753    let slot = slot.parse().ok()?;
5754    (slot != 0).then(|| (lot.clone(), slot))
5755}
5756
5757fn digit_character(digit: Digit) -> Option<char> {
5758    match digit {
5759        Digit::Number(number @ 0..=9) => Some(char::from(b'0' + number)),
5760        Digit::Star => Some('*'),
5761        Digit::Pound => Some('#'),
5762        Digit::A => Some('A'),
5763        Digit::B => Some('B'),
5764        Digit::C => Some('C'),
5765        Digit::D => Some('D'),
5766        Digit::Number(_) | Digit::Unknown(_) => None,
5767    }
5768}
5769
5770fn normalized_last_number(number: &str, config: &ServerConfig) -> Option<String> {
5771    let number = number.trim();
5772    let number = if config.record_dial_terminator {
5773        number
5774    } else {
5775        digit_character(config.dial_terminator)
5776            .map_or(number, |terminator| number.trim_end_matches(terminator))
5777    };
5778    (!number.is_empty()).then(|| number.to_owned())
5779}
5780
5781fn remember_last_number(
5782    state: &mut SessionState,
5783    line_instance: u32,
5784    number: &str,
5785    config: &ServerConfig,
5786) {
5787    if let Some(number) = normalized_last_number(number, config) {
5788        state.last_number_by_line.insert(line_instance, number);
5789    }
5790}
5791
5792async fn begin_redial(
5793    stream: &mut dyn StationIo,
5794    state: &mut SessionState,
5795    context: &SessionContext,
5796    line_instance: u32,
5797    existing_call_id: Option<CallId>,
5798) -> Result<(), ServerError> {
5799    if state.device.ui.placed_calls_redial_menu
5800        && placed_calls_menu_supported(state.registration.protocol)
5801    {
5802        let document = CiscoIpPhoneExecute::new(vec![CiscoIpPhoneExecuteItem::new(
5803            "Application:PlacedCalls",
5804        )?])?;
5805        for message in execute_phone_action_messages(
5806            LineInstance::new(line_instance),
5807            CallReference::new(0),
5808            ApplicationId::new(0),
5809            TransactionId::new(0),
5810            PhoneServicePriority::NORMAL,
5811            &document,
5812            state.registration.protocol,
5813        )? {
5814            send_message(stream, &message, state.registration.protocol).await?;
5815        }
5816        return Ok(());
5817    }
5818
5819    let Some(number) = state.last_number_by_line.get(&line_instance).cloned() else {
5820        return Ok(());
5821    };
5822    let existing = existing_call_id.and_then(|call_id| {
5823        state
5824            .calls_by_id
5825            .get(&call_id)
5826            .filter(|call| call.line_instance == line_instance && call.state != CallState::OnHook)
5827            .cloned()
5828    });
5829    let (call, created) = existing.map_or_else(
5830        || {
5831            (
5832                ensure_phone_call(state, 0, line_instance, &context.next_call_id),
5833                true,
5834            )
5835        },
5836        |call| (call, false),
5837    );
5838
5839    if created {
5840        state.active_key_mode = KeyMode::OffHook;
5841        begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
5842        context
5843            .event_tx
5844            .send(Event::device(
5845                state.device.id.clone(),
5846                state.generation,
5847                DeviceEventKind::OffHook {
5848                    call_id: call.call_id,
5849                    line_instance: LineInstance::new(line_instance),
5850                },
5851            ))
5852            .await
5853            .map_err(|_| ServerError::Stopped)?;
5854    }
5855    if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
5856        stored.dialed_number.clone_from(&number);
5857    }
5858    send_message(
5859        stream,
5860        &ServerMessage::DialedNumber {
5861            number: number.clone(),
5862            line_instance,
5863            call_reference: call.wire_reference,
5864        },
5865        state.registration.protocol,
5866    )
5867    .await?;
5868    context
5869        .event_tx
5870        .send(Event::device(
5871            state.device.id.clone(),
5872            state.generation,
5873            DeviceEventKind::EnblocCall {
5874                call_id: call.call_id,
5875                line_instance: LineInstance::new(line_instance),
5876                number,
5877            },
5878        ))
5879        .await
5880        .map_err(|_| ServerError::Stopped)?;
5881    Ok(())
5882}
5883
5884fn placed_calls_menu_supported(protocol: ProtocolVersion) -> bool {
5885    protocol >= ProtocolVersion::V8
5886}
5887
5888async fn handle_session_command(
5889    stream: &mut dyn StationIo,
5890    state: &mut SessionState,
5891    command: SessionCommand,
5892    context: &SessionContext,
5893) -> Result<bool, ServerError> {
5894    let config = &context.config;
5895    let protocol = state.registration.protocol;
5896    match command {
5897        SessionCommand::Confirmed { .. } => {
5898            unreachable!("confirmed commands are unwrapped by the session loop")
5899        }
5900        SessionCommand::Disconnect => {
5901            drain_session_media(stream, state).await;
5902            return Ok(true);
5903        }
5904        SessionCommand::OfferIncoming {
5905            line_instance,
5906            call_id,
5907            info,
5908            ringer,
5909        } => {
5910            if state.cancelled_calls.remove(&call_id) {
5911                debug!(device_id = %state.device.id, ?call_id, "discarding incoming call cancelled before it was offered");
5912                return Ok(false);
5913            }
5914            let line_instance = normalize_line(state, line_instance.get());
5915            let statistics_directory_number = statistics_directory_for_call_info(&info).to_owned();
5916            let caller = match (
5917                info.calling_name.trim().is_empty(),
5918                info.calling_number.trim().is_empty(),
5919            ) {
5920                (false, false) => format!("{} ({})", info.calling_name, info.calling_number),
5921                (false, true) => info.calling_name.clone(),
5922                (true, false) => info.calling_number.clone(),
5923                (true, true) => "Unknown number".to_owned(),
5924            };
5925            let incoming_state = if state.calls_by_id.values().any(|call| {
5926                matches!(
5927                    call.state,
5928                    CallState::Connected
5929                        | CallState::Hold
5930                        | CallState::HoldYellow
5931                        | CallState::HoldRed
5932                )
5933            }) {
5934                CallState::CallWaiting
5935            } else {
5936                CallState::RingIn
5937            };
5938            let call = insert_call(state, call_id, line_instance, Codec::Pcmu, incoming_state);
5939            if incoming_state == CallState::RingIn && state.active_call_id.is_none() {
5940                state.active_call_id = Some(call.call_id);
5941            }
5942            if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
5943                stored.statistics_directory_number = statistics_directory_number;
5944            }
5945            send_message(
5946                stream,
5947                &ServerMessage::ClearPrompt {
5948                    line_instance,
5949                    call_reference: call.wire_reference,
5950                },
5951                protocol,
5952            )
5953            .await?;
5954            send_message(
5955                stream,
5956                &ServerMessage::CallState {
5957                    state: incoming_state,
5958                    line_instance,
5959                    call_reference: call.wire_reference,
5960                },
5961                protocol,
5962            )
5963            .await?;
5964            send_station_ui_message(
5965                stream,
5966                state,
5967                &ServerMessage::CallInfo {
5968                    info: *info,
5969                    line_instance,
5970                    call_reference: call.wire_reference,
5971                },
5972            )
5973            .await?;
5974            send_message(
5975                stream,
5976                &ServerMessage::SetLamp {
5977                    stimulus: ButtonType::Line,
5978                    instance: line_instance,
5979                    mode: LampMode::Blink,
5980                },
5981                protocol,
5982            )
5983            .await?;
5984            if let Some(ringer) = incoming_ringer(ringer, incoming_state) {
5985                send_message(
5986                    stream,
5987                    &ServerMessage::SetRinger {
5988                        mode: ringer.mode,
5989                        duration: ringer.duration,
5990                        line_instance,
5991                        call_reference: call.wire_reference,
5992                    },
5993                    protocol,
5994                )
5995                .await?;
5996            }
5997            state.active_key_mode = KeyMode::RingIn;
5998            send_message(
5999                stream,
6000                &ServerMessage::SelectSoftKeys {
6001                    line_instance,
6002                    call_reference: call.wire_reference,
6003                    set: KeyMode::RingIn,
6004                    valid_mask: state.device.soft_keys.valid_mask(KeyMode::RingIn),
6005                },
6006                protocol,
6007            )
6008            .await?;
6009            send_station_ui_message(
6010                stream,
6011                state,
6012                &ServerMessage::DisplayPrompt {
6013                    timeout_seconds: 0,
6014                    text: format!("From {caller}"),
6015                    line_instance,
6016                    call_reference: call.wire_reference,
6017                },
6018            )
6019            .await?;
6020        }
6021        SessionCommand::Public(command) => {
6022            let command = *command;
6023            if let Some(call_id) = command_call_id(&command)
6024                && !matches!(
6025                    &command.action,
6026                    CommandAction::BeginCall { .. } | CommandAction::CloseCall { .. }
6027                )
6028                && !state.calls_by_id.contains_key(&call_id)
6029            {
6030                debug!(device_id = %state.device.id, ?call_id, command = ?command, "ignoring stale SCCP call command");
6031                return Ok(false);
6032            }
6033            let action = command.action;
6034            match action {
6035                CommandAction::DisconnectDevice { .. } => {
6036                    drain_session_media(stream, state).await;
6037                    return Ok(true);
6038                }
6039                CommandAction::BeginCall {
6040                    line_instance,
6041                    call_id,
6042                    codec,
6043                } => {
6044                    if state.calls_by_id.contains_key(&call_id) {
6045                        return Ok(false);
6046                    }
6047                    let line_instance = normalize_line(state, line_instance.get());
6048                    let call =
6049                        insert_call(state, call_id, line_instance, codec, CallState::OffHook);
6050                    state.active_call_id = Some(call.call_id);
6051                    state.active_key_mode = KeyMode::OffHook;
6052                    begin_phone_call_ui(stream, &call, &state.device, state.station_context())
6053                        .await?;
6054                }
6055                CommandAction::BeginTransfer {
6056                    source_call_id,
6057                    consultation_line_instance,
6058                    consultation_call_id,
6059                    codec,
6060                } => {
6061                    let consultation_line_instance = consultation_line_instance.get();
6062                    if state.calls_by_id.contains_key(&consultation_call_id) {
6063                        return Ok(false);
6064                    }
6065                    let source = require_call_mut(state, source_call_id)?;
6066                    if !matches!(
6067                        source.state,
6068                        CallState::Hold | CallState::HoldYellow | CallState::HoldRed
6069                    ) {
6070                        return Err(ServerError::InvalidCallTransaction {
6071                            call_id: source_call_id,
6072                            operation: "begin transfer",
6073                            state: source.state,
6074                        });
6075                    }
6076                    source.state = CallState::Transfer;
6077                    source.transfer_role = Some(SessionTransferRole::Source {
6078                        consultation_call_id,
6079                    });
6080                    let source = source.clone();
6081                    send_message(
6082                        stream,
6083                        &ServerMessage::CallState {
6084                            state: CallState::Transfer,
6085                            line_instance: source.line_instance,
6086                            call_reference: source.wire_reference,
6087                        },
6088                        protocol,
6089                    )
6090                    .await?;
6091                    send_station_ui_message(
6092                        stream,
6093                        state,
6094                        &ServerMessage::DisplayPrompt {
6095                            timeout_seconds: 0,
6096                            text: "Call Transfer".into(),
6097                            line_instance: source.line_instance,
6098                            call_reference: source.wire_reference,
6099                        },
6100                    )
6101                    .await?;
6102
6103                    let line_instance = normalize_line(state, consultation_line_instance);
6104                    let mut consultation = insert_call(
6105                        state,
6106                        consultation_call_id,
6107                        line_instance,
6108                        codec,
6109                        CallState::OffHook,
6110                    );
6111                    consultation.transfer_role =
6112                        Some(SessionTransferRole::Consultation { source_call_id });
6113                    state
6114                        .calls_by_id
6115                        .insert(consultation_call_id, consultation.clone());
6116                    state.active_call_id = Some(consultation.call_id);
6117                    state.active_key_mode = KeyMode::OffHookFeature;
6118                    begin_phone_call_ui_with_key_mode(
6119                        stream,
6120                        &consultation,
6121                        &state.device,
6122                        KeyMode::OffHookFeature,
6123                        state.station_context(),
6124                    )
6125                    .await?;
6126                    send_message(
6127                        stream,
6128                        &ServerMessage::SetLamp {
6129                            stimulus: ButtonType::Transfer,
6130                            instance: source.line_instance,
6131                            mode: LampMode::Flash,
6132                        },
6133                        protocol,
6134                    )
6135                    .await?;
6136                }
6137                CommandAction::SetCallSelected {
6138                    call_id, selected, ..
6139                } => {
6140                    let call = require_call(state, call_id)?.clone();
6141                    send_message(
6142                        stream,
6143                        &ServerMessage::CallSelectStatus {
6144                            status: u32::from(selected),
6145                            call_reference: call.wire_reference,
6146                            line_instance: call.line_instance,
6147                        },
6148                        protocol,
6149                    )
6150                    .await?;
6151                }
6152                CommandAction::SetMwi {
6153                    line_instance,
6154                    enabled,
6155                    ..
6156                } => {
6157                    let line_instance = line_instance.get();
6158                    state.mwi_by_line.insert(line_instance, enabled);
6159                    send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
6160                }
6161                CommandAction::SetForwardStatus {
6162                    line_instance,
6163                    forward_all,
6164                    forward_busy,
6165                    forward_no_answer,
6166                    ..
6167                } => {
6168                    let line_instance = line_instance.get();
6169                    state.forwarding_by_line.insert(
6170                        line_instance,
6171                        SessionForwarding {
6172                            all: forward_all.clone(),
6173                            busy: forward_busy.clone(),
6174                            no_answer: forward_no_answer.clone(),
6175                        },
6176                    );
6177                    send_message(
6178                        stream,
6179                        &ServerMessage::ForwardStatus {
6180                            line_instance,
6181                            forward_all,
6182                            forward_busy,
6183                            forward_no_answer,
6184                        },
6185                        protocol,
6186                    )
6187                    .await?;
6188                }
6189                CommandAction::SetFeatureStatus {
6190                    instance, enabled, ..
6191                } => {
6192                    let instance = instance.get();
6193                    if let Some(messages) = feature_state_messages(&state.device, instance, enabled)
6194                    {
6195                        cache_feature_projection(&mut state.feature_states, instance, &messages[0]);
6196                        for message in messages {
6197                            send_station_ui_message(stream, state, &message).await?;
6198                        }
6199                    }
6200                }
6201                CommandAction::SetDoNotDisturbStatus {
6202                    instance,
6203                    mode,
6204                    button_mode,
6205                    ..
6206                } => {
6207                    let instance = instance.get();
6208                    if let Some(messages) = do_not_disturb_state_messages(
6209                        &state.device,
6210                        instance,
6211                        mode,
6212                        button_mode,
6213                        protocol,
6214                    ) {
6215                        cache_feature_projection(&mut state.feature_states, instance, &messages[0]);
6216                        for message in messages {
6217                            send_station_ui_message(stream, state, &message).await?;
6218                        }
6219                    }
6220                }
6221                CommandAction::SetMobilityAppearance {
6222                    mobility_instance,
6223                    appearance,
6224                    ..
6225                } => {
6226                    let mobility_instance = mobility_instance.get();
6227                    let configured = state.device.buttons.iter().any(|button| {
6228                        matches!(
6229                            button,
6230                            ButtonDefinition::Feature(feature)
6231                                if feature.instance == mobility_instance
6232                                    && feature.feature == ButtonType::Mobility
6233                        )
6234                    });
6235                    if !configured {
6236                        return Err(CodecError::InvalidDefinition(format!(
6237                            "device {} has no mobility button instance {mobility_instance}",
6238                            state.device.id
6239                        ))
6240                        .into());
6241                    }
6242                    let previous = state.mobility_appearances.get(&mobility_instance).cloned();
6243                    let mut next_appearances = state.mobility_appearances.clone();
6244                    match &appearance {
6245                        Some(appearance) => {
6246                            next_appearances.insert(mobility_instance, appearance.clone());
6247                        }
6248                        None => {
6249                            next_appearances.remove(&mobility_instance);
6250                        }
6251                    }
6252                    let candidate = mobility_device_candidate(
6253                        &state.device,
6254                        &state.mobility_appearances,
6255                        &next_appearances,
6256                    )?;
6257
6258                    send_button_template(stream, &candidate, protocol).await?;
6259                    if let Some(appearance) = &appearance {
6260                        if let Some(message) = line_status(&candidate, appearance.instance) {
6261                            send_station_ui_message(stream, state, &message).await?;
6262                        }
6263                    } else if let Some(previous) = &previous {
6264                        send_station_ui_message(
6265                            stream,
6266                            state,
6267                            &ServerMessage::LineStatus {
6268                                instance: previous.instance,
6269                                number: String::new(),
6270                                display_name: String::new(),
6271                            },
6272                        )
6273                        .await?;
6274                    }
6275                    state.device = candidate;
6276                    state.mobility_appearances = next_appearances;
6277                }
6278                CommandAction::SetBlfStatus {
6279                    instance,
6280                    state: blf_state,
6281                    caller,
6282                    ..
6283                } => {
6284                    let instance = instance.get();
6285                    let Some(message) = blf_status_message(&state.device, instance, blf_state)
6286                    else {
6287                        return Err(ServerError::UnknownBlfButton {
6288                            device: state.device.id.clone(),
6289                            instance,
6290                        });
6291                    };
6292                    let ServerMessage::FeatureStatus { ref label, .. } = message else {
6293                        unreachable!("BLF status is a feature-state message")
6294                    };
6295                    cache_feature_projection(&mut state.feature_states, instance, &message);
6296                    send_station_ui_message(stream, state, &message).await?;
6297                    let notification = hinted_ringing_notification(
6298                        &state.device,
6299                        label,
6300                        caller.as_ref(),
6301                        blf_state,
6302                    );
6303                    if let Some(notification) = reconcile_blf_alert(
6304                        instance,
6305                        notification,
6306                        &mut state.runtime.active_blf_alerts,
6307                        &mut state.runtime.visible_blf_alert,
6308                    ) {
6309                        for message in status_message_frames(
6310                            notification,
6311                            state.registration.device_type,
6312                            &mut state.persistent_status_message,
6313                        ) {
6314                            send_station_ui_message(stream, state, &message).await?;
6315                        }
6316                    }
6317                }
6318                CommandAction::ShowParkingMenu {
6319                    instance,
6320                    transaction_id,
6321                    lot,
6322                    calls,
6323                    ..
6324                } => {
6325                    let instance = instance.get();
6326                    let transaction_id = transaction_id.get();
6327                    send_message(
6328                        stream,
6329                        &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6330                            application_id: PARKING_APPLICATION_ID,
6331                            line_instance: instance,
6332                            call_reference: 0,
6333                            transaction_id,
6334                            sequence_flag: 0,
6335                            display_priority: 2,
6336                            conference_id: 0,
6337                            application_instance_id: instance,
6338                            routing: 0,
6339                            data: parking_menu_xml(instance, transaction_id, &lot, &calls)?
6340                                .into_bytes(),
6341                        }),
6342                        protocol,
6343                    )
6344                    .await?;
6345                    state.pending_parking_menu = Some(PendingParkingMenu {
6346                        instance,
6347                        transaction_id,
6348                    });
6349                }
6350                CommandAction::ShowConferenceList {
6351                    call_id,
6352                    conference_id,
6353                    participants,
6354                    ..
6355                } => {
6356                    let call = require_call(state, call_id)?.clone();
6357                    let family = if protocol >= ProtocolVersion::V8 {
6358                        ConferenceMenuFamily::IconMenu
6359                    } else {
6360                        ConferenceMenuFamily::Menu
6361                    };
6362                    let data = ConferenceListDocument::new(conference_id, &participants, family)?
6363                        .to_xml()?
6364                        .into_bytes();
6365                    send_message(
6366                        stream,
6367                        &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6368                            application_id: ConferenceListAction::APPLICATION_ID,
6369                            line_instance: call.line_instance,
6370                            call_reference: call.wire_reference,
6371                            transaction_id: conference_id.get(),
6372                            sequence_flag: 0,
6373                            display_priority: 2,
6374                            conference_id: conference_id.get(),
6375                            application_instance_id: call.line_instance,
6376                            routing: 0,
6377                            data,
6378                        }),
6379                        protocol,
6380                    )
6381                    .await?;
6382                }
6383                CommandAction::ShowConferenceParticipantActions {
6384                    call_id,
6385                    conference_id,
6386                    participant,
6387                    removable,
6388                    demotable,
6389                    ..
6390                } => {
6391                    let call = require_call(state, call_id)?.clone();
6392                    let family = if protocol >= ProtocolVersion::V8 {
6393                        ConferenceMenuFamily::IconMenu
6394                    } else {
6395                        ConferenceMenuFamily::Menu
6396                    };
6397                    let data = ConferenceParticipantActionsDocument::new(
6398                        conference_id,
6399                        &participant,
6400                        removable,
6401                        demotable,
6402                        family,
6403                    )?
6404                    .to_xml()?
6405                    .into_bytes();
6406                    send_message(
6407                        stream,
6408                        &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6409                            application_id: ConferenceListAction::APPLICATION_ID,
6410                            line_instance: call.line_instance,
6411                            call_reference: call.wire_reference,
6412                            transaction_id: conference_id.get(),
6413                            sequence_flag: 0,
6414                            display_priority: 2,
6415                            conference_id: conference_id.get(),
6416                            application_instance_id: call.line_instance,
6417                            routing: 0,
6418                            data,
6419                        }),
6420                        protocol,
6421                    )
6422                    .await?;
6423                }
6424                CommandAction::ShowTextService {
6425                    line_instance,
6426                    call_reference,
6427                    transaction_id,
6428                    priority,
6429                    document,
6430                    ..
6431                } => {
6432                    for message in text_service_messages(
6433                        line_instance,
6434                        call_reference,
6435                        transaction_id,
6436                        priority,
6437                        &document,
6438                        protocol,
6439                    )? {
6440                        send_message(stream, &message, protocol).await?;
6441                    }
6442                }
6443                CommandAction::ShowInputService {
6444                    line_instance,
6445                    call_reference,
6446                    application_id,
6447                    transaction_id,
6448                    priority,
6449                    document,
6450                    ..
6451                } => {
6452                    for message in input_service_messages(
6453                        line_instance,
6454                        call_reference,
6455                        application_id,
6456                        transaction_id,
6457                        priority,
6458                        &document,
6459                        protocol,
6460                    )? {
6461                        send_message(stream, &message, protocol).await?;
6462                    }
6463                }
6464                CommandAction::ExecutePhoneActions {
6465                    line_instance,
6466                    call_reference,
6467                    application_id,
6468                    transaction_id,
6469                    priority,
6470                    document,
6471                    ..
6472                } => {
6473                    for message in execute_phone_action_messages(
6474                        line_instance,
6475                        call_reference,
6476                        application_id,
6477                        transaction_id,
6478                        priority,
6479                        &document,
6480                        protocol,
6481                    )? {
6482                        send_message(stream, &message, protocol).await?;
6483                    }
6484                }
6485                CommandAction::ShowImageService {
6486                    line_instance,
6487                    call_reference,
6488                    application_id,
6489                    transaction_id,
6490                    priority,
6491                    document,
6492                    ..
6493                } => {
6494                    for message in image_service_messages(
6495                        line_instance,
6496                        call_reference,
6497                        application_id,
6498                        transaction_id,
6499                        priority,
6500                        &document,
6501                        protocol,
6502                    )? {
6503                        send_message(stream, &message, protocol).await?;
6504                    }
6505                }
6506                CommandAction::ShowStatusService {
6507                    line_instance,
6508                    call_reference,
6509                    application_id,
6510                    transaction_id,
6511                    priority,
6512                    document,
6513                    ..
6514                } => {
6515                    for message in status_service_messages(
6516                        line_instance,
6517                        call_reference,
6518                        application_id,
6519                        transaction_id,
6520                        priority,
6521                        &document,
6522                        protocol,
6523                    )? {
6524                        send_message(stream, &message, protocol).await?;
6525                    }
6526                }
6527                CommandAction::SetBackgroundImage {
6528                    transaction_id,
6529                    document,
6530                    ..
6531                } => {
6532                    let message = background_control_message(
6533                        transaction_id,
6534                        &PhoneBackgroundControlDocument::Set(document),
6535                    )?;
6536                    send_message(stream, &message, protocol).await?;
6537                }
6538                CommandAction::PreviewBackgroundImage {
6539                    transaction_id,
6540                    document,
6541                    ..
6542                } => {
6543                    let message = background_control_message(
6544                        transaction_id,
6545                        &PhoneBackgroundControlDocument::Preview(document),
6546                    )?;
6547                    send_message(stream, &message, protocol).await?;
6548                }
6549                CommandAction::SetRingtone {
6550                    transaction_id,
6551                    document,
6552                    ..
6553                } => {
6554                    let message = ringtone_control_message(transaction_id, &document)?;
6555                    send_message(stream, &message, protocol).await?;
6556                }
6557                CommandAction::StartTone { call_id, tone, .. } => {
6558                    let call = require_call(state, call_id)?.clone();
6559                    let message = if tone == Tone::Silence {
6560                        ServerMessage::StopTone {
6561                            line_instance: call.line_instance,
6562                            call_reference: call.wire_reference,
6563                        }
6564                    } else {
6565                        ServerMessage::StartTone {
6566                            tone,
6567                            direction: ToneDirection::User,
6568                            line_instance: call.line_instance,
6569                            call_reference: call.wire_reference,
6570                        }
6571                    };
6572                    send_message(stream, &message, protocol).await?;
6573                }
6574                CommandAction::StartAnnouncement {
6575                    conference_id,
6576                    announcements,
6577                    end_of_ack,
6578                    participant_ids,
6579                    hearing_participant_mask,
6580                    play_mode,
6581                    ..
6582                } => {
6583                    let _ = (
6584                        conference_id,
6585                        announcements,
6586                        end_of_ack,
6587                        participant_ids,
6588                        hearing_participant_mask,
6589                        play_mode,
6590                    );
6591                    return Err(ServerError::InvalidStationCommand {
6592                        message: "StartAnnouncement",
6593                    });
6594                }
6595                CommandAction::StopAnnouncement { conference_id, .. } => {
6596                    let _ = conference_id;
6597                    return Err(ServerError::InvalidStationCommand {
6598                        message: "StopAnnouncement",
6599                    });
6600                }
6601                CommandAction::AnnouncementFinish {
6602                    conference_id,
6603                    play_status,
6604                    ..
6605                } => {
6606                    let _ = (conference_id, play_status);
6607                    return Err(ServerError::InvalidStationCommand {
6608                        message: "AnnouncementFinish",
6609                    });
6610                }
6611                CommandAction::SetCallInfo { call_id, info, .. } => {
6612                    let statistics_directory_number =
6613                        statistics_directory_for_call_info(&info).to_owned();
6614                    if let Some(stored) = state.calls_by_id.get_mut(&call_id) {
6615                        stored.statistics_directory_number = statistics_directory_number;
6616                    }
6617                    let call = require_call(state, call_id)?.clone();
6618                    send_station_ui_message(
6619                        stream,
6620                        state,
6621                        &ServerMessage::CallInfo {
6622                            info,
6623                            line_instance: call.line_instance,
6624                            call_reference: call.wire_reference,
6625                        },
6626                    )
6627                    .await?;
6628                }
6629                CommandAction::CommitOutboundCall { call_id, info, .. } => {
6630                    let statistics_directory_number =
6631                        statistics_directory_for_call_info(&info).to_owned();
6632                    let call = require_call_mut(state, call_id)?;
6633                    call.state = CallState::Proceed;
6634                    call.history_disposition =
6635                        updated_history_disposition(call.history_disposition, CallState::Proceed);
6636                    call.statistics_directory_number = statistics_directory_number;
6637                    let call = call.clone();
6638                    let number = digit_character(config.dial_terminator)
6639                        .and_then(|terminator| call.dialed_number.strip_suffix(terminator))
6640                        .unwrap_or(&call.dialed_number)
6641                        .to_owned();
6642                    remember_last_number(state, call.line_instance, &number, config);
6643                    state.active_call_id = Some(call.call_id);
6644                    refresh_mwi_lamps(stream, state, protocol).await?;
6645                    for message in [
6646                        ServerMessage::StopTone {
6647                            line_instance: call.line_instance,
6648                            call_reference: call.wire_reference,
6649                        },
6650                        ServerMessage::SetLamp {
6651                            stimulus: ButtonType::Line,
6652                            instance: call.line_instance,
6653                            mode: LampMode::Blink,
6654                        },
6655                        ServerMessage::CallInfo {
6656                            info,
6657                            line_instance: call.line_instance,
6658                            call_reference: call.wire_reference,
6659                        },
6660                        ServerMessage::DialedNumber {
6661                            number,
6662                            line_instance: call.line_instance,
6663                            call_reference: call.wire_reference,
6664                        },
6665                        ServerMessage::CallState {
6666                            state: CallState::Proceed,
6667                            line_instance: call.line_instance,
6668                            call_reference: call.wire_reference,
6669                        },
6670                    ] {
6671                        send_station_ui_message(stream, state, &message).await?;
6672                    }
6673                }
6674                CommandAction::PresentOutboundProceeding { call_id, info, .. } => {
6675                    let statistics_directory_number =
6676                        statistics_directory_for_call_info(&info).to_owned();
6677                    let call = require_call_mut(state, call_id)?;
6678                    call.state = CallState::Proceed;
6679                    call.history_disposition =
6680                        updated_history_disposition(call.history_disposition, CallState::Proceed);
6681                    call.statistics_directory_number = statistics_directory_number;
6682                    let call = call.clone();
6683                    state.active_call_id = Some(call.call_id);
6684                    refresh_mwi_lamps(stream, state, protocol).await?;
6685                    for message in [
6686                        ServerMessage::StopTone {
6687                            line_instance: call.line_instance,
6688                            call_reference: call.wire_reference,
6689                        },
6690                        ServerMessage::CallState {
6691                            state: CallState::Proceed,
6692                            line_instance: call.line_instance,
6693                            call_reference: call.wire_reference,
6694                        },
6695                        ServerMessage::CallInfo {
6696                            info,
6697                            line_instance: call.line_instance,
6698                            call_reference: call.wire_reference,
6699                        },
6700                        ServerMessage::DisplayPrompt {
6701                            timeout_seconds: 0,
6702                            text: "Call Proceed".into(),
6703                            line_instance: call.line_instance,
6704                            call_reference: call.wire_reference,
6705                        },
6706                    ] {
6707                        send_station_ui_message(stream, state, &message).await?;
6708                    }
6709                }
6710                CommandAction::PresentOutboundRinging { call_id, info, .. } => {
6711                    let statistics_directory_number =
6712                        statistics_directory_for_call_info(&info).to_owned();
6713                    let call = require_call_mut(state, call_id)?;
6714                    call.state = CallState::Proceed;
6715                    call.history_disposition =
6716                        updated_history_disposition(call.history_disposition, CallState::Proceed);
6717                    call.statistics_directory_number = statistics_directory_number;
6718                    let call = call.clone();
6719                    state.active_call_id = Some(call.call_id);
6720                    let key_mode = transfer_key_mode(&call, CallState::RingOut);
6721                    state.active_key_mode = key_mode;
6722                    refresh_mwi_lamps(stream, state, protocol).await?;
6723                    for message in [
6724                        ServerMessage::CallState {
6725                            state: CallState::Proceed,
6726                            line_instance: call.line_instance,
6727                            call_reference: call.wire_reference,
6728                        },
6729                        ServerMessage::DisplayPrompt {
6730                            timeout_seconds: 0,
6731                            text: "Ring out".into(),
6732                            line_instance: call.line_instance,
6733                            call_reference: call.wire_reference,
6734                        },
6735                        ServerMessage::StartTone {
6736                            tone: Tone::Alerting,
6737                            direction: ToneDirection::User,
6738                            line_instance: call.line_instance,
6739                            call_reference: call.wire_reference,
6740                        },
6741                        ServerMessage::SelectSoftKeys {
6742                            line_instance: call.line_instance,
6743                            call_reference: call.wire_reference,
6744                            set: key_mode,
6745                            valid_mask: state.device.soft_keys.valid_mask(key_mode),
6746                        },
6747                        ServerMessage::CallInfo {
6748                            info,
6749                            line_instance: call.line_instance,
6750                            call_reference: call.wire_reference,
6751                        },
6752                    ] {
6753                        send_station_ui_message(stream, state, &message).await?;
6754                    }
6755                }
6756                CommandAction::SetCallState {
6757                    call_id,
6758                    state: call_state,
6759                    ..
6760                } => {
6761                    let transfer_source_to_clear =
6762                        state
6763                            .calls_by_id
6764                            .get(&call_id)
6765                            .and_then(|call| match call.transfer_role {
6766                                Some(SessionTransferRole::Source {
6767                                    consultation_call_id,
6768                                }) if call_state != CallState::Transfer => {
6769                                    Some((consultation_call_id, call.line_instance))
6770                                }
6771                                _ => None,
6772                            });
6773                    let call = require_call_mut(state, call_id)?;
6774                    call.state = call_state;
6775                    call.history_disposition =
6776                        updated_history_disposition(call.history_disposition, call_state);
6777                    let call = call.clone();
6778                    if matches!(
6779                        call_state,
6780                        CallState::Proceed | CallState::RingOut | CallState::Connected
6781                    ) {
6782                        remember_last_number(
6783                            state,
6784                            call.line_instance,
6785                            &call.dialed_number,
6786                            config,
6787                        );
6788                    }
6789                    prepare_call_state_ui(stream, &call, call_state, protocol).await?;
6790                    send_message(
6791                        stream,
6792                        &ServerMessage::CallState {
6793                            state: call_state,
6794                            line_instance: call.line_instance,
6795                            call_reference: call.wire_reference,
6796                        },
6797                        protocol,
6798                    )
6799                    .await?;
6800                    finish_call_state_ui(stream, &call, call_state, state.station_context())
6801                        .await?;
6802                    let set = transfer_key_mode(&call, call_state);
6803                    state.active_key_mode = set;
6804                    match call_state {
6805                        CallState::Connected
6806                        | CallState::OffHook
6807                        | CallState::Transfer
6808                        | CallState::RingOut
6809                        | CallState::Proceed
6810                        | CallState::IntercomOneWay => {
6811                            state.active_call_id = Some(call.call_id);
6812                        }
6813                        CallState::OnHook
6814                        | CallState::Hold
6815                        | CallState::HoldYellow
6816                        | CallState::HoldRed
6817                            if state.active_call_id == Some(call.call_id) =>
6818                        {
6819                            state.active_call_id = None;
6820                        }
6821                        _ => {}
6822                    }
6823                    refresh_mwi_lamps(stream, state, protocol).await?;
6824                    send_message(
6825                        stream,
6826                        &ServerMessage::SelectSoftKeys {
6827                            line_instance: call.line_instance,
6828                            call_reference: call.wire_reference,
6829                            set,
6830                            valid_mask: state.device.soft_keys.valid_mask(set),
6831                        },
6832                        protocol,
6833                    )
6834                    .await?;
6835                    if let Some((consultation_call_id, line_instance)) = transfer_source_to_clear {
6836                        if let Some(source) = state.calls_by_id.get_mut(&call_id) {
6837                            source.transfer_role = None;
6838                        }
6839                        if let Some(consultation) = state.calls_by_id.get_mut(&consultation_call_id)
6840                        {
6841                            consultation.transfer_role = None;
6842                        }
6843                        send_message(
6844                            stream,
6845                            &ServerMessage::SetLamp {
6846                                stimulus: ButtonType::Transfer,
6847                                instance: line_instance,
6848                                mode: LampMode::Off,
6849                            },
6850                            protocol,
6851                        )
6852                        .await?;
6853                    }
6854                }
6855                CommandAction::DisplayPrompt {
6856                    call_id,
6857                    timeout_seconds,
6858                    text,
6859                    ..
6860                } => {
6861                    let call = require_call(state, call_id)?.clone();
6862                    send_station_ui_message(
6863                        stream,
6864                        state,
6865                        &ServerMessage::DisplayPrompt {
6866                            timeout_seconds,
6867                            text,
6868                            line_instance: call.line_instance,
6869                            call_reference: call.wire_reference,
6870                        },
6871                    )
6872                    .await?;
6873                }
6874                CommandAction::ClearPrompt { call_id, .. } => {
6875                    let call = require_call(state, call_id)?.clone();
6876                    send_message(
6877                        stream,
6878                        &ServerMessage::ClearPrompt {
6879                            line_instance: call.line_instance,
6880                            call_reference: call.wire_reference,
6881                        },
6882                        protocol,
6883                    )
6884                    .await?;
6885                }
6886                CommandAction::SetStatusMessage { message, beep, .. } => {
6887                    let frames = status_message_frames(
6888                        message,
6889                        state.registration.device_type,
6890                        &mut state.persistent_status_message,
6891                    );
6892                    for frame in frames {
6893                        send_station_ui_message(stream, state, &frame).await?;
6894                    }
6895                    if beep {
6896                        send_message(
6897                            stream,
6898                            &ServerMessage::StartTone {
6899                                tone: Tone::ZipZip,
6900                                direction: ToneDirection::User,
6901                                line_instance: 0,
6902                                call_reference: 0,
6903                            },
6904                            protocol,
6905                        )
6906                        .await?;
6907                    }
6908                }
6909                CommandAction::SetMicrophoneMode { enabled, .. } => {
6910                    send_message(
6911                        stream,
6912                        &ServerMessage::SetMicrophoneMode(if enabled {
6913                            MicrophoneMode::On
6914                        } else {
6915                            MicrophoneMode::Off
6916                        }),
6917                        protocol,
6918                    )
6919                    .await?;
6920                }
6921                CommandAction::SetRecordingStatus {
6922                    call_id, active, ..
6923                } => {
6924                    let call = require_call(state, call_id)?.clone();
6925                    send_message(
6926                        stream,
6927                        &ServerMessage::RecordingStatus {
6928                            call_reference: call.wire_reference,
6929                            active,
6930                        },
6931                        protocol,
6932                    )
6933                    .await?;
6934                }
6935                CommandAction::ResetDevice { reset_type, .. } => {
6936                    send_message(stream, &ServerMessage::Reset(reset_type), protocol).await?;
6937                }
6938                ringing @ (CommandAction::StartRinging { call_id }
6939                | CommandAction::StopRinging { call_id }) => {
6940                    let enabled = matches!(ringing, CommandAction::StartRinging { .. });
6941                    let call = require_call(state, call_id)?.clone();
6942                    send_message(
6943                        stream,
6944                        &ServerMessage::SetRinger {
6945                            mode: if enabled {
6946                                RingerMode::Inside
6947                            } else {
6948                                RingerMode::Off
6949                            },
6950                            duration: RingDuration::Normal,
6951                            line_instance: call.line_instance,
6952                            call_reference: call.wire_reference,
6953                        },
6954                        protocol,
6955                    )
6956                    .await?;
6957                }
6958                CommandAction::OpenReceiveChannel {
6959                    call_id,
6960                    source,
6961                    codec,
6962                    packet_ms,
6963                    max_frames_per_packet,
6964                    dtmf_mode,
6965                    audio_processing,
6966                    ..
6967                } => {
6968                    let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
6969                    let request = allocate_media_request_identity(state, call_id)?;
6970                    let call = require_call_mut(state, call_id)?;
6971                    call.media.requested = true;
6972                    call.media.codec = codec;
6973                    call.media.packet_ms = packet_ms;
6974                    call.media.max_frames_per_packet = max_frames_per_packet;
6975                    call.media.receive.telephone_event_payload = telephone_event_payload;
6976                    call.media.receive.peer = None;
6977                    call.media.receive.state = MediaChannelState::Opening;
6978                    call.media.receive.deadline =
6979                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6980                    call.media.receive.request = Some(request);
6981                    if call.media.transmit.state == MediaChannelState::Closed {
6982                        call.media.transmit.request = None;
6983                    }
6984                    call.media.coupled_transmit_endpoint = None;
6985                    let call = call.clone();
6986                    send_message(
6987                        stream,
6988                        &ServerMessage::OpenReceiveChannel {
6989                            call_reference: call.wire_reference,
6990                            passthrough_party_id: request.token().get(),
6991                            packet_ms,
6992                            codec,
6993                            echo_cancellation: audio_processing.echo_cancellation,
6994                            telephone_event_payload,
6995                            source_address: source
6996                                .map(|endpoint| endpoint.address)
6997                                .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
6998                            source_port: source.map_or(0, |endpoint| endpoint.rtp_port),
6999                            encryption: None,
7000                            wire: None,
7001                        },
7002                        protocol,
7003                    )
7004                    .await?;
7005                }
7006                CommandAction::OpenMultimediaReceiveChannel {
7007                    call_id,
7008                    descriptor,
7009                } => {
7010                    let call_state = require_call(state, call_id)?.state;
7011                    if call_state != CallState::Connected {
7012                        return Err(ServerError::InvalidCallTransaction {
7013                            call_id,
7014                            operation: "open video receive media",
7015                            state: call_state,
7016                        });
7017                    }
7018                    validate_multimedia_receive(state, &descriptor)?;
7019                    let request = allocate_video_receive_identity(state, call_id)?;
7020                    let replacement_close = take_multimedia_receive_close(state, call_id);
7021                    let call = require_call_mut(state, call_id)?;
7022                    let line_instance = call.line_instance;
7023                    let call_reference = CallReference::new(call.wire_reference);
7024                    call.video_receive.leg = Some(VideoReceiveLeg {
7025                        request,
7026                        conference_id: descriptor.conference_id,
7027                        codec: descriptor.payload.codec(),
7028                        requested_address_type: descriptor.requested_address_type,
7029                        state: MediaChannelState::Opening,
7030                        deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
7031                    });
7032
7033                    if let Some(close) = replacement_close {
7034                        send_message(stream, &close, protocol).await?;
7035                    }
7036                    send_message(
7037                        stream,
7038                        &ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
7039                            conference_id: descriptor.conference_id,
7040                            passthrough_party_id: request.token().get().into(),
7041                            line_instance,
7042                            call_reference,
7043                            payload: descriptor.payload,
7044                            conference_creator: descriptor.conference_creator,
7045                            encryption: descriptor.encryption,
7046                            stream_passthrough_id: descriptor.stream_passthrough_id,
7047                            associated_stream_id: descriptor.associated_stream_id,
7048                            source: descriptor.source,
7049                            requested_address_type: descriptor.requested_address_type,
7050                        }),
7051                        protocol,
7052                    )
7053                    .await?;
7054                }
7055                CommandAction::CloseMultimediaReceiveChannel { call_id } => {
7056                    if let Some(close) = take_multimedia_receive_close(state, call_id) {
7057                        send_message(stream, &close, protocol).await?;
7058                    }
7059                }
7060                CommandAction::StartMultimediaTransmission {
7061                    call_id,
7062                    descriptor,
7063                } => {
7064                    let call_state = require_call(state, call_id)?.state;
7065                    if call_state != CallState::Connected {
7066                        return Err(ServerError::InvalidCallTransaction {
7067                            call_id,
7068                            operation: "start video transmit media",
7069                            state: call_state,
7070                        });
7071                    }
7072                    validate_multimedia_transmit(state, &descriptor)?;
7073                    let request = allocate_video_transmit_identity(state, call_id)?;
7074                    let replacement_stop = take_multimedia_transmit_stop(state, call_id);
7075                    let call_reference = {
7076                        let call = require_call_mut(state, call_id)?;
7077                        let call_reference = CallReference::new(call.wire_reference);
7078                        call.video_transmit.leg = Some(VideoTransmitLeg {
7079                            request,
7080                            conference_id: descriptor.conference_id,
7081                            codec: descriptor.payload.codec(),
7082                            address_type: address_type(descriptor.endpoint.address),
7083                            state: MediaChannelState::Opening,
7084                            deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
7085                        });
7086                        call_reference
7087                    };
7088
7089                    if let Some(stop) = replacement_stop {
7090                        send_message(stream, &stop, protocol).await?;
7091                    }
7092                    send_message(
7093                        stream,
7094                        &ServerMessage::StartMultimediaTransmission(MultimediaTransmissionStart {
7095                            conference_id: descriptor.conference_id,
7096                            passthrough_party_id: request.token().get().into(),
7097                            endpoint: descriptor.endpoint,
7098                            call_reference,
7099                            payload: descriptor.payload,
7100                            traffic_class: descriptor.traffic_class,
7101                            encryption: descriptor.encryption,
7102                            stream_passthrough_id: descriptor.stream_passthrough_id,
7103                            associated_stream_id: descriptor.associated_stream_id,
7104                        }),
7105                        protocol,
7106                    )
7107                    .await?;
7108                }
7109                CommandAction::StopMultimediaTransmission { call_id } => {
7110                    if let Some(stop) = take_multimedia_transmit_stop(state, call_id) {
7111                        send_message(stream, &stop, protocol).await?;
7112                    }
7113                }
7114                flow_action @ (CommandAction::SetMultimediaTransmitBitRate {
7115                    call_id,
7116                    passthrough_party_id,
7117                    maximum_bit_rate,
7118                }
7119                | CommandAction::NotifyMultimediaTransmitBitRate {
7120                    call_id,
7121                    passthrough_party_id,
7122                    maximum_bit_rate,
7123                }) => {
7124                    if maximum_bit_rate == 0 {
7125                        return Err(ServerError::InvalidMultimediaTransmitControl(
7126                            "maximum bit rate must be nonzero",
7127                        ));
7128                    }
7129                    let (conference_id, call_reference) =
7130                        multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
7131                    let flow = VideoFlowControl {
7132                        conference_id,
7133                        passthrough_party_id,
7134                        call_reference,
7135                        maximum_bit_rate,
7136                    };
7137                    let message = if matches!(
7138                        flow_action,
7139                        CommandAction::SetMultimediaTransmitBitRate { .. }
7140                    ) {
7141                        ServerMessage::FlowControlCommand(flow)
7142                    } else {
7143                        ServerMessage::FlowControlNotify(flow)
7144                    };
7145                    send_message(stream, &message, protocol).await?;
7146                }
7147                CommandAction::ControlMultimediaTransmission {
7148                    call_id,
7149                    passthrough_party_id,
7150                    control,
7151                } => {
7152                    let (conference_id, call_reference) =
7153                        multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
7154                    let (command, data) = encode_multimedia_transmit_control(control)?;
7155                    send_message(
7156                        stream,
7157                        &ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
7158                            conference_id,
7159                            passthrough_party_id,
7160                            call_reference,
7161                            command,
7162                            data,
7163                        }),
7164                        protocol,
7165                    )
7166                    .await?;
7167                }
7168                CommandAction::OpenOutboundMedia {
7169                    call_id,
7170                    source,
7171                    mut endpoint,
7172                    codec,
7173                    packet_ms,
7174                    max_frames_per_packet,
7175                    dtmf_mode,
7176                    audio_processing,
7177                    traffic_class,
7178                } => {
7179                    let call_state = require_call(state, call_id)?.state;
7180                    if !matches!(call_state, CallState::Proceed | CallState::RingOut) {
7181                        return Err(ServerError::InvalidCallTransaction {
7182                            call_id,
7183                            operation: "open coupled outbound media",
7184                            state: call_state,
7185                        });
7186                    }
7187                    let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
7188                    let source_address = source
7189                        .map(|source| source.address)
7190                        .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
7191                    let source_port = source.map_or(0, |source| source.rtp_port);
7192                    let request = allocate_media_request_identity(state, call_id)?;
7193                    let call = require_call_mut(state, call_id)?;
7194                    call.media.requested = true;
7195                    call.media.codec = codec;
7196                    call.media.packet_ms = packet_ms;
7197                    call.media.max_frames_per_packet = max_frames_per_packet;
7198                    call.media.receive.telephone_event_payload = telephone_event_payload;
7199                    call.media.receive.peer = None;
7200                    call.media.receive.state = MediaChannelState::Opening;
7201                    call.media.receive.deadline =
7202                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
7203                    call.media.receive.request = Some(request);
7204                    call.media.transmit.telephone_event_payload = telephone_event_payload;
7205                    call.media.transmit.peer = None;
7206                    call.media.transmit.state = MediaChannelState::Opening;
7207                    call.media.transmit.deadline =
7208                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
7209                    call.media.transmit.request = Some(request);
7210                    endpoint.telephone_event_payload = telephone_event_payload;
7211                    call.media.coupled_transmit_endpoint = Some(endpoint);
7212                    let call = call.clone();
7213                    send_message(
7214                        stream,
7215                        &ServerMessage::OpenReceiveChannel {
7216                            call_reference: call.wire_reference,
7217                            passthrough_party_id: request.token().get(),
7218                            packet_ms,
7219                            codec,
7220                            echo_cancellation: audio_processing.echo_cancellation,
7221                            telephone_event_payload,
7222                            source_address,
7223                            source_port,
7224                            encryption: None,
7225                            wire: None,
7226                        },
7227                        protocol,
7228                    )
7229                    .await?;
7230                    send_message(
7231                        stream,
7232                        &ServerMessage::StartMediaTransmission {
7233                            call_reference: call.wire_reference,
7234                            passthrough_party_id: request.token().get(),
7235                            endpoint,
7236                            silence_suppression: audio_processing.silence_suppression,
7237                            traffic_class,
7238                            encryption: None,
7239                            wire: None,
7240                        },
7241                        protocol,
7242                    )
7243                    .await?;
7244                }
7245                CommandAction::CloseReceiveChannel { call_id, .. } => {
7246                    let call = require_call_mut(state, call_id)?;
7247                    call.media.coupled_transmit_endpoint = None;
7248                    if call.media.receive.state != MediaChannelState::Closed {
7249                        call.media.receive.state = MediaChannelState::Closed;
7250                        call.media.receive.deadline = None;
7251                        let call = call.clone();
7252                        send_message(
7253                            stream,
7254                            &ServerMessage::CloseReceiveChannel(AudioStreamControl {
7255                                conference_id: ConferenceId::new(call.wire_reference),
7256                                call_reference: CallReference::new(call.wire_reference),
7257                                passthrough_party_id: media_request_party_id(
7258                                    call.media.receive.request,
7259                                    call.wire_reference,
7260                                )
7261                                .into(),
7262                                port_handling_flag: 0,
7263                            }),
7264                            protocol,
7265                        )
7266                        .await?;
7267                    }
7268                }
7269                CommandAction::StartMedia {
7270                    call_id,
7271                    mut endpoint,
7272                    dtmf_mode,
7273                    audio_processing,
7274                    traffic_class,
7275                } => {
7276                    let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
7277                    let request = {
7278                        let call = require_call(state, call_id)?;
7279                        if call.media.transmit.request.is_none() {
7280                            call.media.receive.request
7281                        } else {
7282                            None
7283                        }
7284                    };
7285                    let request = match request {
7286                        Some(request) => request,
7287                        None => allocate_media_request_identity(state, call_id)?,
7288                    };
7289                    let call = require_call_mut(state, call_id)?;
7290                    call.media.requested = true;
7291                    call.media.transmit.telephone_event_payload = telephone_event_payload;
7292                    call.media.transmit.peer = None;
7293                    call.media.transmit.state = MediaChannelState::Opening;
7294                    call.media.transmit.deadline =
7295                        Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
7296                    call.media.transmit.request = Some(request);
7297                    call.media.coupled_transmit_endpoint = None;
7298                    let call = call.clone();
7299                    endpoint.telephone_event_payload = telephone_event_payload;
7300                    send_message(
7301                        stream,
7302                        &ServerMessage::StartMediaTransmission {
7303                            call_reference: call.wire_reference,
7304                            passthrough_party_id: request.token().get(),
7305                            endpoint,
7306                            silence_suppression: audio_processing.silence_suppression,
7307                            traffic_class,
7308                            encryption: None,
7309                            wire: None,
7310                        },
7311                        protocol,
7312                    )
7313                    .await?;
7314                }
7315                CommandAction::StartMulticastReception {
7316                    conference_id,
7317                    call_id,
7318                    route,
7319                    echo_cancellation,
7320                    g723_bitrate,
7321                } => {
7322                    validate_multicast_route(state, route, None)?;
7323                    let wire_call_reference = require_call(state, call_id)?.wire_reference;
7324                    let request = allocate_multicast_request_identity(state)?;
7325                    let key = MulticastKey {
7326                        conference_id,
7327                        call_id,
7328                    };
7329                    if let Some(stop) = take_multicast_stop(state, key, true) {
7330                        send_message(stream, &stop, protocol).await?;
7331                    }
7332                    send_message(
7333                        stream,
7334                        &ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
7335                            conference_id,
7336                            passthrough_party_id: request.token().get().into(),
7337                            call_reference: CallReference::new(wire_call_reference),
7338                            address: route.address,
7339                            port: route.port,
7340                            packet_millis: route.packet_millis,
7341                            codec: route.codec,
7342                            echo_cancellation,
7343                            g723_bitrate,
7344                        }),
7345                        protocol,
7346                    )
7347                    .await?;
7348                    state
7349                        .multicast
7350                        .entry(key)
7351                        .or_insert_with(|| MulticastSession {
7352                            wire_call_reference,
7353                            receive: None,
7354                            transmit: None,
7355                        })
7356                        .receive = Some(MulticastReceive {
7357                        request,
7358                        route,
7359                        state: MulticastReceiveState::AwaitingAcknowledgement {
7360                            deadline: Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT,
7361                        },
7362                    });
7363                }
7364                CommandAction::StopMulticastReception {
7365                    conference_id,
7366                    call_id,
7367                } => {
7368                    let key = MulticastKey {
7369                        conference_id,
7370                        call_id,
7371                    };
7372                    if let Some(stop) = take_multicast_stop(state, key, true) {
7373                        send_message(stream, &stop, protocol).await?;
7374                    }
7375                }
7376                CommandAction::StartMulticastTransmission {
7377                    conference_id,
7378                    call_id,
7379                    route,
7380                    precedence,
7381                    silence_suppression,
7382                    max_frames_per_packet,
7383                    g723_bitrate,
7384                } => {
7385                    validate_multicast_route(state, route, Some(max_frames_per_packet))?;
7386                    let wire_call_reference = require_call(state, call_id)?.wire_reference;
7387                    let request = allocate_multicast_request_identity(state)?;
7388                    let key = MulticastKey {
7389                        conference_id,
7390                        call_id,
7391                    };
7392                    if let Some(stop) = take_multicast_stop(state, key, false) {
7393                        send_message(stream, &stop, protocol).await?;
7394                    }
7395                    send_message(
7396                        stream,
7397                        &ServerMessage::StartMulticastMediaTransmission(
7398                            MulticastMediaTransmission {
7399                                conference_id,
7400                                passthrough_party_id: request.token().get().into(),
7401                                call_reference: CallReference::new(wire_call_reference),
7402                                address: route.address,
7403                                port: route.port,
7404                                packet_millis: route.packet_millis,
7405                                codec: route.codec,
7406                                precedence,
7407                                silence_suppression: silence_suppression.wire_value(),
7408                                max_frames_per_packet,
7409                                g723_bitrate,
7410                            },
7411                        ),
7412                        protocol,
7413                    )
7414                    .await?;
7415                    state
7416                        .multicast
7417                        .entry(key)
7418                        .or_insert_with(|| MulticastSession {
7419                            wire_call_reference,
7420                            receive: None,
7421                            transmit: None,
7422                        })
7423                        .transmit = Some(MulticastTransmit { request, route });
7424                    context
7425                        .event_tx
7426                        .send(Event::device(
7427                            state.device.id.clone(),
7428                            state.generation,
7429                            DeviceEventKind::MulticastTransmissionStarted {
7430                                conference_id,
7431                                call_id,
7432                                route,
7433                            },
7434                        ))
7435                        .await
7436                        .map_err(|_| ServerError::Stopped)?;
7437                }
7438                CommandAction::StopMulticastTransmission {
7439                    conference_id,
7440                    call_id,
7441                } => {
7442                    let key = MulticastKey {
7443                        conference_id,
7444                        call_id,
7445                    };
7446                    if let Some(stop) = take_multicast_stop(state, key, false) {
7447                        send_message(stream, &stop, protocol).await?;
7448                    }
7449                }
7450                CommandAction::StopMedia { call_id, .. } => {
7451                    if let Some(call) = state
7452                        .calls_by_id
7453                        .get_mut(&call_id)
7454                        .filter(|call| call.media.transmit.state != MediaChannelState::Closed)
7455                    {
7456                        call.media.transmit.state = MediaChannelState::Closed;
7457                        call.media.transmit.deadline = None;
7458                        call.media.coupled_transmit_endpoint = None;
7459                        let call = call.clone();
7460                        send_message(
7461                            stream,
7462                            &ServerMessage::StopMediaTransmission(AudioStreamControl {
7463                                conference_id: ConferenceId::new(call.wire_reference),
7464                                call_reference: CallReference::new(call.wire_reference),
7465                                passthrough_party_id: media_request_party_id(
7466                                    call.media.transmit.request,
7467                                    call.wire_reference,
7468                                )
7469                                .into(),
7470                                port_handling_flag: 0,
7471                            }),
7472                            protocol,
7473                        )
7474                        .await?;
7475                    }
7476                }
7477                CommandAction::CloseCall { call_id, .. } => {
7478                    if let Some(call) = state.calls_by_id.get(&call_id).cloned() {
7479                        state.active_key_mode = KeyMode::OnHook;
7480                        stop_call_multicast(stream, state, call_id, protocol).await?;
7481                        if call.state != CallState::OnHook {
7482                            close_call_media_messages(stream, &call, protocol).await?;
7483                            close_call_messages(
7484                                stream,
7485                                &call,
7486                                &state.device.soft_keys,
7487                                protocol,
7488                                context.config.timezone_offset_minutes,
7489                            )
7490                            .await?;
7491                            request_connection_statistics(stream, state, &call, context).await?;
7492                        }
7493                        remove_call(state, call_id);
7494                        refresh_mwi_lamps(stream, state, protocol).await?;
7495                    } else {
7496                        state.cancelled_calls.insert(call_id);
7497                    }
7498                }
7499            }
7500        }
7501    }
7502    Ok(false)
7503}
7504
7505async fn send_mwi_lamp(
7506    stream: &mut dyn StationIo,
7507    state: &SessionState,
7508    line_instance: u32,
7509    enabled: bool,
7510    protocol: ProtocolVersion,
7511) -> Result<(), ServerError> {
7512    let mode = projected_mwi_lamp(state.device.ui, state.active_call_id.is_some(), enabled);
7513    send_message(
7514        stream,
7515        &ServerMessage::SetLamp {
7516            stimulus: ButtonType::Voicemail,
7517            instance: line_instance,
7518            mode,
7519        },
7520        protocol,
7521    )
7522    .await
7523}
7524
7525fn projected_mwi_lamp(ui: crate::types::StationUiPolicy, on_call: bool, enabled: bool) -> LampMode {
7526    if enabled && (ui.mwi_on_call || !on_call) {
7527        ui.mwi_lamp_mode
7528    } else {
7529        LampMode::Off
7530    }
7531}
7532
7533fn updated_history_disposition(
7534    current: CallHistoryDisposition,
7535    state: CallState,
7536) -> CallHistoryDisposition {
7537    if current != CallHistoryDisposition::Missed {
7538        return current;
7539    }
7540    match state {
7541        CallState::Connected => CallHistoryDisposition::Received,
7542        CallState::RemoteMultiline => CallHistoryDisposition::Ignore,
7543        _ => current,
7544    }
7545}
7546
7547async fn refresh_mwi_lamps(
7548    stream: &mut dyn StationIo,
7549    state: &SessionState,
7550    protocol: ProtocolVersion,
7551) -> Result<(), ServerError> {
7552    for (&line_instance, &enabled) in &state.mwi_by_line {
7553        send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
7554    }
7555    Ok(())
7556}
7557
7558fn incoming_ringer(
7559    ringer: Option<IncomingRing>,
7560    incoming_state: CallState,
7561) -> Option<IncomingRing> {
7562    ringer.map(|mut ringer| {
7563        if incoming_state == CallState::CallWaiting {
7564            ringer.duration = RingDuration::Single;
7565            if ringer.mode != RingerMode::Urgent {
7566                ringer.mode = RingerMode::Silent;
7567            }
7568        }
7569        ringer
7570    })
7571}
7572
7573async fn request_connection_statistics(
7574    stream: &mut dyn StationIo,
7575    state: &mut SessionState,
7576    call: &SessionCall,
7577    context: &SessionContext,
7578) -> Result<(), ServerError> {
7579    prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
7580    if !call.media.requested
7581        || state.pending_connection_statistics.len() >= MAX_PENDING_CONNECTION_STATISTICS
7582        || state.statistics_references.len() >= MAX_STATISTICS_REFERENCES_PER_SESSION
7583    {
7584        return Ok(());
7585    }
7586    let directory_number = if call.statistics_directory_number.is_empty() {
7587        call.dialed_number.trim()
7588    } else {
7589        call.statistics_directory_number.trim()
7590    };
7591    let maximum = if state.registration.protocol >= ProtocolVersion::V19 {
7592        24
7593    } else {
7594        23
7595    };
7596    if directory_number.is_empty()
7597        || directory_number.len() > maximum
7598        || directory_number.contains(['\0', '\r', '\n'])
7599    {
7600        warn!(
7601            device_id = %state.device.id,
7602            ?call.call_id,
7603            byte_count = directory_number.len(),
7604            "skipping connection-statistics request with unusable directory number"
7605        );
7606        return Ok(());
7607    }
7608    if !state.statistics_references.insert(call.wire_reference) {
7609        warn!(
7610            device_id = %state.device.id,
7611            ?call.call_id,
7612            call_reference = call.wire_reference,
7613            "skipping connection-statistics request for a reused call reference"
7614        );
7615        return Ok(());
7616    }
7617    let request_generation = context
7618        .next_statistics_generation
7619        .fetch_add(1, Ordering::Relaxed);
7620    let processing = StatisticsProcessing::Clear;
7621    let session_generation = state.generation;
7622    state.pending_connection_statistics.insert(
7623        call.wire_reference,
7624        PendingConnectionStatistics {
7625            session_generation,
7626            request_generation,
7627            call_id: call.call_id,
7628            line_instance: call.line_instance,
7629            codec: call.media.codec,
7630            packet_ms: call.media.packet_ms,
7631            max_frames_per_packet: call.media.max_frames_per_packet,
7632            receive_peer: call.media.receive.peer,
7633            transmit_peer: call.media.transmit.peer,
7634            directory_number: directory_number.to_owned(),
7635            processing,
7636            expires_at: Instant::now() + CONNECTION_STATISTICS_TIMEOUT,
7637        },
7638    );
7639    send_message(
7640        stream,
7641        &ServerMessage::ConnectionStatisticsRequest {
7642            directory_number: directory_number.to_owned(),
7643            call_reference: call.wire_reference,
7644            processing,
7645        },
7646        state.registration.protocol,
7647    )
7648    .await
7649}
7650
7651fn statistics_directory_for_call_info(info: &CallInfo) -> &str {
7652    match info.direction {
7653        crate::types::CallDirection::Inbound => &info.calling_number,
7654        crate::types::CallDirection::Outbound => &info.called_number,
7655    }
7656}
7657
7658fn prune_connection_statistics(
7659    pending_statistics: &mut HashMap<u32, PendingConnectionStatistics>,
7660    now: Instant,
7661) {
7662    pending_statistics.retain(|_, pending| pending.expires_at > now);
7663}
7664
7665async fn collect_connection_statistics(
7666    state: &mut SessionState,
7667    statistics: ConnectionStatistics,
7668    context: &SessionContext,
7669) -> Result<(), ServerError> {
7670    prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
7671    let Some(pending) = state
7672        .pending_connection_statistics
7673        .get(&statistics.call_reference)
7674        .cloned()
7675    else {
7676        warn!(
7677            device_id = %state.device.id,
7678            call_reference = statistics.call_reference,
7679            "ignoring unsolicited or expired connection-statistics response"
7680        );
7681        return Ok(());
7682    };
7683    let current_session = context
7684        .sessions
7685        .lock()
7686        .await
7687        .get(&state.device.id)
7688        .is_some_and(|session| session.generation == pending.session_generation);
7689    if !current_session
7690        || pending.session_generation != state.generation
7691        || statistics.processing != pending.processing
7692        || statistics.directory_number != pending.directory_number
7693    {
7694        warn!(
7695            device_id = %state.device.id,
7696            call_reference = statistics.call_reference,
7697            processing = ?statistics.processing,
7698            "ignoring mismatched connection-statistics response"
7699        );
7700        return Ok(());
7701    }
7702    state
7703        .pending_connection_statistics
7704        .remove(&statistics.call_reference);
7705    let snapshot = MediaStatisticsSnapshot {
7706        request_generation: pending.request_generation,
7707        call_id: pending.call_id,
7708        line_instance: LineInstance::new(pending.line_instance),
7709        codec: pending.codec,
7710        packet_ms: pending.packet_ms,
7711        max_frames_per_packet: pending.max_frames_per_packet,
7712        receive_peer: pending.receive_peer,
7713        transmit_peer: pending.transmit_peer,
7714        packets_sent: statistics.packets_sent,
7715        octets_sent: statistics.octets_sent,
7716        packets_received: statistics.packets_received,
7717        octets_received: statistics.octets_received,
7718        packets_lost: statistics.packets_lost,
7719        jitter_millis: statistics.jitter_millis,
7720        latency_millis: statistics.latency_millis,
7721        quality_byte_count: statistics.quality.as_bytes().len(),
7722    };
7723    {
7724        let mut latest = context
7725            .latest_media_statistics
7726            .write()
7727            .expect("SCCP media-statistics lock poisoned");
7728        let replace = latest
7729            .get(&state.device.id)
7730            .is_none_or(|existing| existing.request_generation < snapshot.request_generation);
7731        if !replace {
7732            return Ok(());
7733        }
7734        latest.insert(state.device.id.clone(), snapshot.clone());
7735    }
7736    context
7737        .event_tx
7738        .send(Event::device(
7739            state.device.id.clone(),
7740            state.generation,
7741            DeviceEventKind::ConnectionStatisticsCollected { snapshot },
7742        ))
7743        .await
7744        .map_err(|_| ServerError::Stopped)
7745}
7746
7747fn status_message_frames(
7748    message: HandsetStatusMessage,
7749    device_type: DeviceType,
7750    persistent: &mut bool,
7751) -> Vec<ServerMessage> {
7752    let prompt_for_timed_message = matches!(
7753        device_type,
7754        DeviceType::Cisco6901
7755            | DeviceType::Cisco6921
7756            | DeviceType::Cisco6941
7757            | DeviceType::Cisco6945
7758            | DeviceType::Cisco6961
7759    );
7760    match message {
7761        HandsetStatusMessage::Display {
7762            text,
7763            timeout_seconds,
7764            priority: Some(priority),
7765        } => vec![ServerMessage::DisplayPriorityNotify {
7766            timeout_seconds: u32::from(timeout_seconds),
7767            priority,
7768            text,
7769        }],
7770        HandsetStatusMessage::Clear {
7771            priority: Some(priority),
7772        } => vec![ServerMessage::ClearPriorityNotify { priority }],
7773        HandsetStatusMessage::Display {
7774            text,
7775            timeout_seconds,
7776            priority: None,
7777        } if timeout_seconds == 0 || prompt_for_timed_message => {
7778            if timeout_seconds == 0 {
7779                *persistent = true;
7780            }
7781            vec![ServerMessage::DisplayPrompt {
7782                timeout_seconds: u32::from(timeout_seconds),
7783                text,
7784                line_instance: 0,
7785                call_reference: 0,
7786            }]
7787        }
7788        HandsetStatusMessage::Display {
7789            text,
7790            timeout_seconds,
7791            priority: None,
7792        } => vec![ServerMessage::DisplayPriorityNotify {
7793            timeout_seconds: u32::from(timeout_seconds),
7794            priority: NotificationPriority::Timed,
7795            text,
7796        }],
7797        HandsetStatusMessage::Clear { priority: None } => {
7798            let clear_prompt = std::mem::take(persistent) || prompt_for_timed_message;
7799            let mut frames = Vec::with_capacity(2);
7800            if clear_prompt {
7801                frames.push(ServerMessage::ClearPrompt {
7802                    line_instance: 0,
7803                    call_reference: 0,
7804                });
7805            }
7806            if !prompt_for_timed_message {
7807                frames.push(ServerMessage::ClearPriorityNotify {
7808                    priority: NotificationPriority::Timed,
7809                });
7810            }
7811            frames
7812        }
7813    }
7814}
7815
7816async fn send_message(
7817    stream: &mut dyn StationIo,
7818    message: &ServerMessage,
7819    session: impl Into<StationSessionContext>,
7820) -> Result<(), ServerError> {
7821    stream
7822        .write_all(&message.encode_for_session(session.into())?)
7823        .await?;
7824    Ok(())
7825}
7826
7827async fn send_station_ui_message(
7828    stream: &mut dyn StationIo,
7829    state: &SessionState,
7830    message: &ServerMessage,
7831) -> Result<(), ServerError> {
7832    let session = state.station_context();
7833    let bytes = if state.features.contains(PhoneFeatures::UTF8) {
7834        message.encode_for_session(session)?
7835    } else {
7836        message.encode_for_legacy_session(session, state.device.ui.legacy_code_page)?
7837    };
7838    stream.write_all(&bytes).await?;
7839    Ok(())
7840}
7841
7842async fn begin_phone_call_ui(
7843    stream: &mut dyn StationIo,
7844    call: &SessionCall,
7845    device: &DeviceDefinition,
7846    session: StationSessionContext,
7847) -> Result<(), ServerError> {
7848    begin_phone_call_ui_with_key_mode(stream, call, device, KeyMode::OffHook, session).await
7849}
7850
7851async fn begin_phone_call_ui_with_key_mode(
7852    stream: &mut dyn StationIo,
7853    call: &SessionCall,
7854    device: &DeviceDefinition,
7855    key_mode: KeyMode,
7856    session: StationSessionContext,
7857) -> Result<(), ServerError> {
7858    let initial_tone = device
7859        .line(call.line_instance)
7860        .map_or(Tone::InsideDial, |line| line.initial_tone);
7861    send_message(
7862        stream,
7863        &ServerMessage::SetSpeakerMode(SpeakerMode::On),
7864        session,
7865    )
7866    .await?;
7867    send_message(
7868        stream,
7869        &ServerMessage::SetLamp {
7870            stimulus: ButtonType::Line,
7871            instance: call.line_instance,
7872            mode: LampMode::On,
7873        },
7874        session,
7875    )
7876    .await?;
7877    send_message(
7878        stream,
7879        &ServerMessage::CallState {
7880            state: CallState::OffHook,
7881            line_instance: call.line_instance,
7882            call_reference: call.wire_reference,
7883        },
7884        session,
7885    )
7886    .await?;
7887    send_message(
7888        stream,
7889        &ServerMessage::ActivateCallPlane {
7890            line_instance: call.line_instance,
7891        },
7892        session,
7893    )
7894    .await?;
7895    send_message(
7896        stream,
7897        &ServerMessage::DisplayPrompt {
7898            timeout_seconds: 0,
7899            text: "Enter number".into(),
7900            line_instance: call.line_instance,
7901            call_reference: call.wire_reference,
7902        },
7903        session,
7904    )
7905    .await?;
7906    send_message(
7907        stream,
7908        &ServerMessage::StartTone {
7909            tone: initial_tone,
7910            direction: ToneDirection::User,
7911            line_instance: call.line_instance,
7912            call_reference: call.wire_reference,
7913        },
7914        session,
7915    )
7916    .await?;
7917    send_message(
7918        stream,
7919        &ServerMessage::SelectSoftKeys {
7920            line_instance: call.line_instance,
7921            call_reference: call.wire_reference,
7922            set: key_mode,
7923            valid_mask: device.soft_keys.valid_mask(key_mode),
7924        },
7925        session,
7926    )
7927    .await
7928}
7929
7930async fn begin_answer_ui(
7931    stream: &mut dyn StationIo,
7932    call: &SessionCall,
7933    protocol: ProtocolVersion,
7934) -> Result<(), ServerError> {
7935    send_message(
7936        stream,
7937        &ServerMessage::SetRinger {
7938            mode: RingerMode::Off,
7939            duration: RingDuration::Normal,
7940            line_instance: call.line_instance,
7941            call_reference: call.wire_reference,
7942        },
7943        protocol,
7944    )
7945    .await?;
7946    send_message(
7947        stream,
7948        &ServerMessage::CallState {
7949            state: CallState::OffHook,
7950            line_instance: call.line_instance,
7951            call_reference: call.wire_reference,
7952        },
7953        protocol,
7954    )
7955    .await?;
7956    send_message(
7957        stream,
7958        &ServerMessage::ActivateCallPlane {
7959            line_instance: call.line_instance,
7960        },
7961        protocol,
7962    )
7963    .await?;
7964    send_message(
7965        stream,
7966        &ServerMessage::StopTone {
7967            line_instance: call.line_instance,
7968            call_reference: call.wire_reference,
7969        },
7970        protocol,
7971    )
7972    .await?;
7973    send_message(
7974        stream,
7975        &ServerMessage::SetLamp {
7976            stimulus: ButtonType::Line,
7977            instance: call.line_instance,
7978            mode: LampMode::On,
7979        },
7980        protocol,
7981    )
7982    .await?;
7983    Ok(())
7984}
7985
7986async fn prepare_call_state_ui(
7987    stream: &mut dyn StationIo,
7988    call: &SessionCall,
7989    state: CallState,
7990    protocol: ProtocolVersion,
7991) -> Result<(), ServerError> {
7992    match state {
7993        CallState::Connected => {
7994            send_message(
7995                stream,
7996                &ServerMessage::SetRinger {
7997                    mode: RingerMode::Off,
7998                    duration: RingDuration::Normal,
7999                    line_instance: call.line_instance,
8000                    call_reference: call.wire_reference,
8001                },
8002                protocol,
8003            )
8004            .await?;
8005            send_message(
8006                stream,
8007                &ServerMessage::SetSpeakerMode(SpeakerMode::On),
8008                protocol,
8009            )
8010            .await?;
8011            send_message(
8012                stream,
8013                &ServerMessage::StopTone {
8014                    line_instance: call.line_instance,
8015                    call_reference: call.wire_reference,
8016                },
8017                protocol,
8018            )
8019            .await?;
8020            send_message(
8021                stream,
8022                &ServerMessage::SetLamp {
8023                    stimulus: ButtonType::Line,
8024                    instance: call.line_instance,
8025                    mode: LampMode::On,
8026                },
8027                protocol,
8028            )
8029            .await?;
8030        }
8031        CallState::RemoteMultiline => {
8032            send_message(
8033                stream,
8034                &ServerMessage::SetRinger {
8035                    mode: RingerMode::Off,
8036                    duration: RingDuration::Normal,
8037                    line_instance: call.line_instance,
8038                    call_reference: call.wire_reference,
8039                },
8040                protocol,
8041            )
8042            .await?;
8043            send_message(
8044                stream,
8045                &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
8046                protocol,
8047            )
8048            .await?;
8049            send_message(
8050                stream,
8051                &ServerMessage::SetLamp {
8052                    stimulus: ButtonType::Line,
8053                    instance: call.line_instance,
8054                    mode: LampMode::On,
8055                },
8056                protocol,
8057            )
8058            .await?;
8059        }
8060        CallState::OnHook => {
8061            send_message(
8062                stream,
8063                &ServerMessage::SetRinger {
8064                    mode: RingerMode::Off,
8065                    duration: RingDuration::Normal,
8066                    line_instance: call.line_instance,
8067                    call_reference: call.wire_reference,
8068                },
8069                protocol,
8070            )
8071            .await?;
8072        }
8073        CallState::Hold | CallState::HoldYellow | CallState::HoldRed => {
8074            send_message(
8075                stream,
8076                &ServerMessage::SetLamp {
8077                    stimulus: ButtonType::Line,
8078                    instance: call.line_instance,
8079                    mode: LampMode::Wink,
8080                },
8081                protocol,
8082            )
8083            .await?;
8084        }
8085        CallState::RingOut | CallState::Proceed => {
8086            send_message(
8087                stream,
8088                &ServerMessage::SetLamp {
8089                    stimulus: ButtonType::Line,
8090                    instance: call.line_instance,
8091                    mode: LampMode::Blink,
8092                },
8093                protocol,
8094            )
8095            .await?;
8096        }
8097        _ => {}
8098    }
8099    Ok(())
8100}
8101
8102async fn finish_call_state_ui(
8103    stream: &mut dyn StationIo,
8104    call: &SessionCall,
8105    state: CallState,
8106    session: StationSessionContext,
8107) -> Result<(), ServerError> {
8108    let prompt = match state {
8109        CallState::Connected => Some("Connected"),
8110        CallState::Hold | CallState::HoldYellow | CallState::HoldRed => Some("Hold"),
8111        CallState::RingOut => Some("Ring out"),
8112        CallState::Proceed => Some("Call proceeding"),
8113        CallState::Busy => Some("Busy"),
8114        CallState::Congestion => Some("Network congestion"),
8115        CallState::InvalidNumber => Some("Unknown number"),
8116        _ => None,
8117    };
8118    if state == CallState::Connected {
8119        send_message(
8120            stream,
8121            &ServerMessage::ActivateCallPlane {
8122                line_instance: call.line_instance,
8123            },
8124            session,
8125        )
8126        .await?;
8127    } else if matches!(
8128        state,
8129        CallState::Hold | CallState::HoldYellow | CallState::HoldRed
8130    ) {
8131        send_message(
8132            stream,
8133            &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
8134            session,
8135        )
8136        .await?;
8137    }
8138    if let Some(text) = prompt {
8139        send_message(
8140            stream,
8141            &ServerMessage::DisplayPrompt {
8142                timeout_seconds: 0,
8143                text: text.into(),
8144                line_instance: call.line_instance,
8145                call_reference: call.wire_reference,
8146            },
8147            session,
8148        )
8149        .await?;
8150    }
8151    Ok(())
8152}
8153
8154fn normalize_line(state: &SessionState, requested: u32) -> u32 {
8155    if requested != 0 && state.device.line(requested).is_some() {
8156        requested
8157    } else {
8158        state.device.first_line().map_or(1, |line| line.instance)
8159    }
8160}
8161
8162fn ensure_phone_call(
8163    state: &mut SessionState,
8164    wire_reference: u32,
8165    line_instance: u32,
8166    next: &AtomicU64,
8167) -> SessionCall {
8168    let reusable = if wire_reference == 0 {
8169        state
8170            .calls_by_id
8171            .values()
8172            .filter(|call| call.state != CallState::OnHook)
8173            .max_by_key(|call| call.call_id.0)
8174    } else {
8175        find_call(state, wire_reference).filter(|call| call.state != CallState::OnHook)
8176    };
8177    if let Some(call) = reusable {
8178        return call.clone();
8179    }
8180    let mut call = reserve_phone_call(state, line_instance, next);
8181    if wire_reference != 0
8182        && wire_reference != call.wire_reference
8183        && !state.statistics_references.contains(&wire_reference)
8184    {
8185        state.calls_by_wire.remove(&call.wire_reference);
8186        call.wire_reference = wire_reference;
8187        state.calls_by_wire.insert(wire_reference, call.call_id);
8188        state.calls_by_id.insert(call.call_id, call.clone());
8189    }
8190    call
8191}
8192
8193fn reserve_phone_call(
8194    state: &mut SessionState,
8195    line_instance: u32,
8196    next: &AtomicU64,
8197) -> SessionCall {
8198    let call_id = CallId(next.fetch_add(1, Ordering::Relaxed));
8199    insert_call(
8200        state,
8201        call_id,
8202        line_instance,
8203        Codec::Pcmu,
8204        CallState::OffHook,
8205    )
8206}
8207
8208fn insert_call(
8209    state: &mut SessionState,
8210    call_id: CallId,
8211    line_instance: u32,
8212    codec: Codec,
8213    call_state: CallState,
8214) -> SessionCall {
8215    let mut wire_reference = (call_id.0 as u32).max(1);
8216    while state.calls_by_wire.contains_key(&wire_reference)
8217        || state.statistics_references.contains(&wire_reference)
8218    {
8219        wire_reference = wire_reference.wrapping_add(1).max(1);
8220    }
8221    let call = SessionCall {
8222        call_id,
8223        wire_reference,
8224        line_instance,
8225        media: CallMedia::new(codec),
8226        video_receive: VideoReceive::default(),
8227        video_transmit: VideoTransmit::default(),
8228        state: call_state,
8229        history_disposition: if matches!(call_state, CallState::RingIn | CallState::CallWaiting) {
8230            CallHistoryDisposition::Missed
8231        } else {
8232            CallHistoryDisposition::Placed
8233        },
8234        dialed_number: String::new(),
8235        statistics_directory_number: String::new(),
8236        transfer_role: None,
8237    };
8238    state.calls_by_wire.insert(wire_reference, call_id);
8239    state.calls_by_id.insert(call_id, call.clone());
8240    call
8241}
8242
8243fn find_call(state: &SessionState, wire_reference: u32) -> Option<&SessionCall> {
8244    if wire_reference != 0 {
8245        state
8246            .calls_by_wire
8247            .get(&wire_reference)
8248            .and_then(|id| state.calls_by_id.get(id))
8249    } else {
8250        state
8251            .active_call_id
8252            .and_then(|call_id| state.calls_by_id.get(&call_id))
8253            .or_else(|| {
8254                (state.calls_by_id.len() == 1)
8255                    .then(|| state.calls_by_id.values().next())
8256                    .flatten()
8257            })
8258    }
8259}
8260
8261fn find_answer_call(
8262    state: &SessionState,
8263    wire_reference: u32,
8264    line_instance: u32,
8265    order: CallSelectionOrder,
8266) -> Option<&SessionCall> {
8267    let matches_line = |call: &&SessionCall| {
8268        matches!(call.state, CallState::RingIn | CallState::CallWaiting)
8269            && (line_instance == 0 || call.line_instance == line_instance)
8270    };
8271    if wire_reference != 0 {
8272        return state
8273            .calls_by_wire
8274            .get(&wire_reference)
8275            .and_then(|call_id| state.calls_by_id.get(call_id))
8276            .filter(matches_line);
8277    }
8278    if let Some(active) = state
8279        .active_call_id
8280        .and_then(|call_id| state.calls_by_id.get(&call_id))
8281        .filter(matches_line)
8282    {
8283        return Some(active);
8284    }
8285    let candidates = state.calls_by_id.values().filter(matches_line);
8286    match order {
8287        CallSelectionOrder::OldestFirst => candidates.min_by_key(|call| call.call_id.0),
8288        CallSelectionOrder::LastFirst => candidates.max_by_key(|call| call.call_id.0),
8289    }
8290}
8291
8292fn find_receive_media_call_id(
8293    state: &SessionState,
8294    wire_reference: u32,
8295    passthrough_party_id: u32,
8296) -> Option<CallId> {
8297    find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
8298        call.media.receive.request
8299    })
8300}
8301
8302fn find_multicast_receive_key(
8303    state: &SessionState,
8304    wire_reference: u32,
8305    passthrough_party_id: u32,
8306) -> Option<MulticastKey> {
8307    state.multicast.iter().find_map(|(key, session)| {
8308        session.receive.as_ref().and_then(|receive| {
8309            (matches!(
8310                receive.state,
8311                MulticastReceiveState::AwaitingAcknowledgement { .. }
8312            ) && session.wire_call_reference == wire_reference
8313                && receive.request.token().get() == passthrough_party_id)
8314                .then_some(*key)
8315        })
8316    })
8317}
8318
8319fn find_multicast_transmit_key(
8320    state: &SessionState,
8321    conference_id: u32,
8322    wire_reference: u32,
8323    passthrough_party_id: u32,
8324    address: IpAddr,
8325    port: u16,
8326) -> Option<MulticastKey> {
8327    state.multicast.iter().find_map(|(key, session)| {
8328        session.transmit.as_ref().and_then(|transmit| {
8329            (key.conference_id.get() == conference_id
8330                && session.wire_call_reference == wire_reference
8331                && transmit.request.token().get() == passthrough_party_id
8332                && canonical_ip_address(transmit.route.address) == canonical_ip_address(address)
8333                && transmit.route.port == port)
8334                .then_some(*key)
8335        })
8336    })
8337}
8338
8339fn find_transmit_media_call_id(
8340    state: &SessionState,
8341    conference_id: u32,
8342    wire_reference: u32,
8343    passthrough_party_id: u32,
8344) -> Option<CallId> {
8345    find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
8346        call.media.transmit.request
8347    })
8348    .filter(|call_id| {
8349        state
8350            .calls_by_id
8351            .get(call_id)
8352            .is_some_and(|call| conference_id == 0 || conference_id == call.wire_reference)
8353    })
8354}
8355
8356fn find_media_call_id(
8357    state: &SessionState,
8358    wire_reference: u32,
8359    passthrough_party_id: u32,
8360    request: impl Fn(&SessionCall) -> Option<MediaRequestIdentity>,
8361) -> Option<CallId> {
8362    state
8363        .calls_by_id
8364        .values()
8365        .find(|call| {
8366            request(call).is_some_and(|identity| {
8367                identity.accepts_ack(passthrough_party_id, wire_reference, call.wire_reference)
8368            })
8369        })
8370        .map(|call| call.call_id)
8371}
8372
8373fn require_call(state: &SessionState, call_id: CallId) -> Result<&SessionCall, ServerError> {
8374    state
8375        .calls_by_id
8376        .get(&call_id)
8377        .ok_or(ServerError::UnknownCall(call_id))
8378}
8379
8380fn require_call_mut(
8381    state: &mut SessionState,
8382    call_id: CallId,
8383) -> Result<&mut SessionCall, ServerError> {
8384    state
8385        .calls_by_id
8386        .get_mut(&call_id)
8387        .ok_or(ServerError::UnknownCall(call_id))
8388}
8389
8390fn address_matches_type(address: IpAddr, requested: IpAddressType) -> bool {
8391    match requested {
8392        IpAddressType::Ipv4 => address.is_ipv4(),
8393        IpAddressType::Ipv6 => address.is_ipv6(),
8394        IpAddressType::Ipv4AndIpv6 => true,
8395        IpAddressType::Invalid | IpAddressType::Unknown(_) => false,
8396    }
8397}
8398
8399fn address_type(address: IpAddr) -> IpAddressType {
8400    if address.is_ipv4() {
8401        IpAddressType::Ipv4
8402    } else {
8403        IpAddressType::Ipv6
8404    }
8405}
8406
8407fn endpoint_is_usable(endpoint: MediaEndpointAddress) -> bool {
8408    endpoint.port != 0 && !endpoint.address.is_unspecified() && !endpoint.address.is_multicast()
8409}
8410
8411fn capability_supports_address(
8412    advertised: Option<IpAddressType>,
8413    requested: IpAddressType,
8414) -> bool {
8415    match advertised {
8416        None => requested == IpAddressType::Ipv4,
8417        Some(IpAddressType::Ipv4AndIpv6) => true,
8418        Some(address_type) => address_type == requested,
8419    }
8420}
8421
8422fn validate_multimedia_receive_descriptor(
8423    descriptor: &MultimediaReceiveDescriptor,
8424) -> Result<(), ServerError> {
8425    if !descriptor
8426        .payload
8427        .is_direction(MultimediaPayloadDirection::Receive)
8428    {
8429        return Err(ServerError::InvalidMultimediaReceive(
8430            "payload was not decoded from a receive message",
8431        ));
8432    }
8433    if descriptor.payload.codec().kind() != CodecKind::Video {
8434        return Err(ServerError::InvalidMultimediaReceive("codec is not video"));
8435    }
8436    if !address_matches_type(descriptor.source.address, descriptor.requested_address_type) {
8437        return Err(ServerError::InvalidMultimediaReceive(
8438            "source address does not match the requested address type",
8439        ));
8440    }
8441    if descriptor.source.address.is_multicast() {
8442        return Err(ServerError::InvalidMultimediaReceive(
8443            "source address must not be multicast",
8444        ));
8445    }
8446    Ok(())
8447}
8448
8449fn validate_multimedia_receive(
8450    state: &SessionState,
8451    descriptor: &MultimediaReceiveDescriptor,
8452) -> Result<(), ServerError> {
8453    validate_multimedia_receive_descriptor(descriptor)?;
8454
8455    if !descriptor.payload.is_valid_for(
8456        MultimediaPayloadDirection::Receive,
8457        state.registration.protocol,
8458    ) {
8459        return Err(ServerError::InvalidMultimediaReceive(
8460            "payload protocol does not match the live session",
8461        ));
8462    }
8463
8464    match state.registration.protocol {
8465        protocol if protocol < ProtocolVersion::V12 => {
8466            if descriptor.source
8467                != (MediaEndpointAddress {
8468                    address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
8469                    port: 0,
8470                })
8471                || descriptor.requested_address_type != IpAddressType::Ipv4
8472            {
8473                return Err(ServerError::InvalidMultimediaReceive(
8474                    "this protocol version cannot carry a source endpoint",
8475                ));
8476            }
8477        }
8478        protocol
8479            if protocol < ProtocolVersion::V17
8480                && (!descriptor.source.address.is_ipv4()
8481                    || descriptor.requested_address_type != IpAddressType::Ipv4) =>
8482        {
8483            return Err(ServerError::InvalidMultimediaReceive(
8484                "this protocol version carries only IPv4 video endpoints",
8485            ));
8486        }
8487        _ => {}
8488    }
8489
8490    let supported = state.media_capabilities.video().iter().any(|capability| {
8491        let encryption_supported = descriptor.encryption.is_none()
8492            || capability.encryption_capability == Some(EncryptionCapability::Capable);
8493        capability.codec == descriptor.payload.codec()
8494            && capability.direction.contains(ReceiveTransmit::RECEIVE)
8495            && capability_supports_address(
8496                capability.address_type,
8497                descriptor.requested_address_type,
8498            )
8499            && encryption_supported
8500    });
8501    supported
8502        .then_some(())
8503        .ok_or(ServerError::UnsupportedMultimediaReceive)
8504}
8505
8506fn validate_multimedia_transmit_descriptor(
8507    descriptor: &MultimediaTransmitDescriptor,
8508) -> Result<(), ServerError> {
8509    if !descriptor
8510        .payload
8511        .is_direction(MultimediaPayloadDirection::Transmit)
8512    {
8513        return Err(ServerError::InvalidMultimediaTransmit(
8514            "payload was not decoded from a transmit message",
8515        ));
8516    }
8517    if descriptor.payload.codec().kind() != CodecKind::Video {
8518        return Err(ServerError::InvalidMultimediaTransmit("codec is not video"));
8519    }
8520    if !endpoint_is_usable(descriptor.endpoint) {
8521        return Err(ServerError::InvalidMultimediaTransmit(
8522            "destination endpoint must be unicast and nonzero",
8523        ));
8524    }
8525    Ok(())
8526}
8527
8528fn validate_multimedia_transmit(
8529    state: &SessionState,
8530    descriptor: &MultimediaTransmitDescriptor,
8531) -> Result<(), ServerError> {
8532    validate_multimedia_transmit_descriptor(descriptor)?;
8533    if !descriptor.payload.is_valid_for(
8534        MultimediaPayloadDirection::Transmit,
8535        state.registration.protocol,
8536    ) {
8537        return Err(ServerError::InvalidMultimediaTransmit(
8538            "payload protocol does not match the live session",
8539        ));
8540    }
8541    if state.registration.protocol < ProtocolVersion::V17 && descriptor.endpoint.address.is_ipv6() {
8542        return Err(ServerError::InvalidMultimediaTransmit(
8543            "this protocol version carries only IPv4 video endpoints",
8544        ));
8545    }
8546    let requested_address = address_type(descriptor.endpoint.address);
8547    let supported = state.media_capabilities.video().iter().any(|capability| {
8548        let encryption_supported = descriptor.encryption.is_none()
8549            || capability.encryption_capability == Some(EncryptionCapability::Capable);
8550        capability.codec == descriptor.payload.codec()
8551            && capability.direction.contains(ReceiveTransmit::TRANSMIT)
8552            && capability_supports_address(capability.address_type, requested_address)
8553            && encryption_supported
8554    });
8555    supported
8556        .then_some(())
8557        .ok_or(ServerError::UnsupportedMultimediaTransmit)
8558}
8559
8560fn allocate_video_receive_identity(
8561    state: &mut SessionState,
8562    call_id: CallId,
8563) -> Result<MediaRequestIdentity, ServerError> {
8564    let generation = require_call(state, call_id)?
8565        .video_receive
8566        .generation
8567        .checked_add(1)
8568        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8569    let token = state
8570        .next_media_token
8571        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8572    let request = MediaRequestIdentity::new(generation, token)
8573        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8574    state.next_media_token = token.checked_next();
8575    require_call_mut(state, call_id)?.video_receive.generation = generation;
8576    Ok(request)
8577}
8578
8579fn multimedia_receive_close_message(call: &SessionCall, leg: &VideoReceiveLeg) -> ServerMessage {
8580    ServerMessage::CloseMultimediaReceiveChannel(MultimediaStreamControl {
8581        conference_id: leg.conference_id,
8582        passthrough_party_id: leg.request.token().get().into(),
8583        call_reference: CallReference::new(call.wire_reference),
8584        port_handling_flag: 0,
8585    })
8586}
8587
8588fn take_multimedia_receive_close(
8589    state: &mut SessionState,
8590    call_id: CallId,
8591) -> Option<ServerMessage> {
8592    let call = state.calls_by_id.get_mut(&call_id)?;
8593    let leg = call.video_receive.leg.take()?;
8594    Some(multimedia_receive_close_message(call, &leg))
8595}
8596
8597fn take_all_multimedia_receive_closes(state: &mut SessionState) -> Vec<ServerMessage> {
8598    let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
8599    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8600    call_ids
8601        .into_iter()
8602        .filter_map(|call_id| take_multimedia_receive_close(state, call_id))
8603        .collect()
8604}
8605
8606fn expire_multimedia_receive_acknowledgements(
8607    state: &mut SessionState,
8608    now: Instant,
8609) -> Vec<ExpiredVideoReceive> {
8610    let mut call_ids = state
8611        .calls_by_id
8612        .iter()
8613        .filter_map(|(&call_id, call)| {
8614            call.video_receive.leg.as_ref().and_then(|leg| {
8615                (leg.state == MediaChannelState::Opening
8616                    && leg.deadline.is_some_and(|deadline| deadline <= now))
8617                .then_some(call_id)
8618            })
8619        })
8620        .collect::<Vec<_>>();
8621    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8622    call_ids
8623        .into_iter()
8624        .filter_map(|call_id| {
8625            let leg = state
8626                .calls_by_id
8627                .get(&call_id)?
8628                .video_receive
8629                .leg
8630                .as_ref()?;
8631            let codec = leg.codec;
8632            let passthrough_party_id = leg.request.token().get().into();
8633            take_multimedia_receive_close(state, call_id).map(|close| ExpiredVideoReceive {
8634                call_id,
8635                codec,
8636                passthrough_party_id,
8637                close,
8638            })
8639        })
8640        .collect()
8641}
8642
8643fn allocate_video_transmit_identity(
8644    state: &mut SessionState,
8645    call_id: CallId,
8646) -> Result<MediaRequestIdentity, ServerError> {
8647    let generation = require_call(state, call_id)?
8648        .video_transmit
8649        .generation
8650        .checked_add(1)
8651        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8652    let token = state
8653        .next_media_token
8654        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8655    let request = MediaRequestIdentity::new(generation, token)
8656        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8657    state.next_media_token = token.checked_next();
8658    require_call_mut(state, call_id)?.video_transmit.generation = generation;
8659    Ok(request)
8660}
8661
8662fn multimedia_transmit_control_identity(
8663    state: &SessionState,
8664    call_id: CallId,
8665    passthrough_party_id: PassthroughPartyId,
8666) -> Result<(ConferenceId, CallReference), ServerError> {
8667    let call = require_call(state, call_id)?;
8668    if call.state != CallState::Connected {
8669        return Err(ServerError::InvalidCallTransaction {
8670            call_id,
8671            operation: "control video transmit media",
8672            state: call.state,
8673        });
8674    }
8675    let leg = call
8676        .video_transmit
8677        .leg
8678        .as_ref()
8679        .filter(|leg| {
8680            leg.state == MediaChannelState::Open
8681                && leg.request.token().get() == passthrough_party_id.get()
8682        })
8683        .ok_or(ServerError::StaleMultimediaTransmitControl {
8684            call_id,
8685            passthrough_party_id,
8686        })?;
8687    Ok((leg.conference_id, CallReference::new(call.wire_reference)))
8688}
8689
8690fn encode_multimedia_transmit_control(
8691    control: MultimediaTransmitControl,
8692) -> Result<(MiscCommandType, BoundedBytes<36>), ServerError> {
8693    let (command, words) = match control {
8694        MultimediaTransmitControl::FreezePicture => {
8695            (MiscCommandType::VideoFreezePicture, Vec::new())
8696        }
8697        MultimediaTransmitControl::FastPictureUpdate {
8698            first_gob,
8699            gob_count,
8700        } => (
8701            MiscCommandType::VideoFastUpdatePicture,
8702            vec![first_gob, gob_count],
8703        ),
8704        MultimediaTransmitControl::FastGobUpdate {
8705            first_gob,
8706            gob_count,
8707        } => (
8708            MiscCommandType::VideoFastUpdateGob,
8709            vec![first_gob, gob_count],
8710        ),
8711        MultimediaTransmitControl::FastMacroblockUpdate {
8712            first_gob,
8713            first_macroblock,
8714            macroblock_count,
8715        } => (
8716            MiscCommandType::VideoFastUpdateMacroblock,
8717            vec![first_gob, first_macroblock, macroblock_count],
8718        ),
8719        MultimediaTransmitControl::LostPicture {
8720            picture_number,
8721            long_term_picture_index,
8722        } => (
8723            MiscCommandType::LostPicture,
8724            vec![picture_number, long_term_picture_index],
8725        ),
8726        MultimediaTransmitControl::LostPartialPicture {
8727            picture_number,
8728            long_term_picture_index,
8729            first_macroblock,
8730            macroblock_count,
8731        } => (
8732            MiscCommandType::LostPartialPicture,
8733            vec![
8734                picture_number,
8735                long_term_picture_index,
8736                first_macroblock,
8737                macroblock_count,
8738            ],
8739        ),
8740        MultimediaTransmitControl::RecoveryReferencePicture { pictures } => {
8741            let words =
8742                std::iter::once(pictures.as_slice().len() as u32)
8743                    .chain(pictures.as_slice().iter().flat_map(|picture| {
8744                        [picture.picture_number, picture.long_term_picture_index]
8745                    }))
8746                    .collect();
8747            (MiscCommandType::RecoveryReferencePicture, words)
8748        }
8749        MultimediaTransmitControl::TemporalSpatialTradeoff { value } => {
8750            (MiscCommandType::TemporalSpatialTradeoff, vec![value])
8751        }
8752    };
8753    let data = words
8754        .into_iter()
8755        .flat_map(u32::to_le_bytes)
8756        .collect::<Vec<_>>();
8757    let data = BoundedBytes::new(data.into_boxed_slice()).map_err(|_| {
8758        ServerError::InvalidMultimediaTransmitControl("parameter area exceeds 36 bytes")
8759    })?;
8760    Ok((command, data))
8761}
8762
8763fn multimedia_transmit_stop_message(call: &SessionCall, leg: &VideoTransmitLeg) -> ServerMessage {
8764    ServerMessage::StopMultimediaTransmission(MultimediaStreamControl {
8765        conference_id: leg.conference_id,
8766        passthrough_party_id: leg.request.token().get().into(),
8767        call_reference: CallReference::new(call.wire_reference),
8768        port_handling_flag: 0,
8769    })
8770}
8771
8772fn take_multimedia_transmit_stop(
8773    state: &mut SessionState,
8774    call_id: CallId,
8775) -> Option<ServerMessage> {
8776    let call = state.calls_by_id.get_mut(&call_id)?;
8777    let leg = call.video_transmit.leg.take()?;
8778    Some(multimedia_transmit_stop_message(call, &leg))
8779}
8780
8781fn take_all_multimedia_transmit_stops(state: &mut SessionState) -> Vec<ServerMessage> {
8782    let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
8783    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8784    call_ids
8785        .into_iter()
8786        .filter_map(|call_id| take_multimedia_transmit_stop(state, call_id))
8787        .collect()
8788}
8789
8790fn expire_multimedia_transmit_acknowledgements(
8791    state: &mut SessionState,
8792    now: Instant,
8793) -> Vec<ExpiredVideoTransmit> {
8794    let mut call_ids = state
8795        .calls_by_id
8796        .iter()
8797        .filter_map(|(&call_id, call)| {
8798            call.video_transmit.leg.as_ref().and_then(|leg| {
8799                (leg.state == MediaChannelState::Opening
8800                    && leg.deadline.is_some_and(|deadline| deadline <= now))
8801                .then_some(call_id)
8802            })
8803        })
8804        .collect::<Vec<_>>();
8805    call_ids.sort_unstable_by_key(|call_id| call_id.get());
8806    call_ids
8807        .into_iter()
8808        .filter_map(|call_id| {
8809            let leg = state
8810                .calls_by_id
8811                .get(&call_id)?
8812                .video_transmit
8813                .leg
8814                .as_ref()?;
8815            let codec = leg.codec;
8816            let passthrough_party_id = leg.request.token().get().into();
8817            take_multimedia_transmit_stop(state, call_id).map(|stop| ExpiredVideoTransmit {
8818                call_id,
8819                codec,
8820                passthrough_party_id,
8821                stop,
8822            })
8823        })
8824        .collect()
8825}
8826
8827fn validate_multicast_route(
8828    state: &SessionState,
8829    route: MulticastMediaRoute,
8830    max_frames_per_packet: Option<u32>,
8831) -> Result<(), ServerError> {
8832    if !route.address.is_multicast() {
8833        return Err(ServerError::InvalidMulticastMedia(
8834            "address must be multicast",
8835        ));
8836    }
8837    if route.address.is_ipv6() && state.registration.protocol < ProtocolVersion::V17 {
8838        return Err(ServerError::InvalidMulticastMedia(
8839            "IPv6 requires protocol v17 or later",
8840        ));
8841    }
8842    if route.port == 0 {
8843        return Err(ServerError::InvalidMulticastMedia("port must be nonzero"));
8844    }
8845    if route.packet_millis == 0 {
8846        return Err(ServerError::InvalidMulticastMedia(
8847            "packet duration must be nonzero",
8848        ));
8849    }
8850    if route.codec.kind() != CodecKind::Audio {
8851        return Err(ServerError::UnsupportedMulticastCodec);
8852    }
8853    let capability = state
8854        .media_capabilities
8855        .audio()
8856        .iter()
8857        .find(|capability| capability.codec == route.codec)
8858        .filter(|capability| capability.max_frames_per_packet != 0)
8859        .ok_or(ServerError::UnsupportedMulticastCodec)?;
8860    if let Some(requested) = max_frames_per_packet
8861        && (requested == 0 || requested > capability.max_frames_per_packet)
8862    {
8863        return Err(ServerError::InvalidMulticastMedia(
8864            "packet framing exceeds the advertised capability",
8865        ));
8866    }
8867    Ok(())
8868}
8869
8870fn allocate_multicast_request_identity(
8871    state: &mut SessionState,
8872) -> Result<MediaRequestIdentity, ServerError> {
8873    let generation = state
8874        .next_multicast_generation
8875        .checked_add(1)
8876        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8877    let token = state
8878        .next_media_token
8879        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8880    let identity = MediaRequestIdentity::new(generation, token)
8881        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8882    state.next_multicast_generation = generation;
8883    state.next_media_token = token.checked_next();
8884    Ok(identity)
8885}
8886
8887fn multicast_stop_message(
8888    key: MulticastKey,
8889    wire_call_reference: u32,
8890    request: MediaRequestIdentity,
8891    receive: bool,
8892) -> ServerMessage {
8893    if receive {
8894        ServerMessage::StopMulticastMediaReception {
8895            conference_id: key.conference_id,
8896            passthrough_party_id: request.token().get().into(),
8897            call_reference: CallReference::new(wire_call_reference),
8898        }
8899    } else {
8900        ServerMessage::StopMulticastMediaTransmission {
8901            conference_id: key.conference_id,
8902            passthrough_party_id: request.token().get().into(),
8903            call_reference: CallReference::new(wire_call_reference),
8904        }
8905    }
8906}
8907
8908fn take_multicast_stop(
8909    state: &mut SessionState,
8910    key: MulticastKey,
8911    receive: bool,
8912) -> Option<ServerMessage> {
8913    let session = state.multicast.get_mut(&key)?;
8914    let request = if receive {
8915        session.receive.take().map(|leg| leg.request)
8916    } else {
8917        session.transmit.take().map(|leg| leg.request)
8918    }?;
8919    let message = multicast_stop_message(key, session.wire_call_reference, request, receive);
8920    if session.receive.is_none() && session.transmit.is_none() {
8921        state.multicast.remove(&key);
8922    }
8923    Some(message)
8924}
8925
8926fn expire_multicast_reception_acknowledgements(
8927    state: &mut SessionState,
8928    now: Instant,
8929) -> Vec<(MulticastKey, ServerMessage)> {
8930    let mut expired = state
8931        .multicast
8932        .iter()
8933        .filter_map(|(key, session)| {
8934            session.receive.as_ref().and_then(|receive| {
8935                matches!(
8936                    receive.state,
8937                    MulticastReceiveState::AwaitingAcknowledgement { deadline }
8938                        if deadline <= now
8939                )
8940                .then_some(*key)
8941            })
8942        })
8943        .collect::<Vec<_>>();
8944    expired.sort_unstable_by_key(|key| (key.conference_id.get(), key.call_id.get()));
8945    expired
8946        .into_iter()
8947        .filter_map(|key| take_multicast_stop(state, key, true).map(|stop| (key, stop)))
8948        .collect()
8949}
8950
8951fn take_multicast_stops_for_call(state: &mut SessionState, call_id: CallId) -> Vec<ServerMessage> {
8952    let mut keys = state
8953        .multicast
8954        .keys()
8955        .copied()
8956        .filter(|key| key.call_id == call_id)
8957        .collect::<Vec<_>>();
8958    keys.sort_unstable_by_key(|key| key.conference_id.get());
8959    keys.into_iter()
8960        .flat_map(|key| {
8961            [
8962                take_multicast_stop(state, key, true),
8963                take_multicast_stop(state, key, false),
8964            ]
8965            .into_iter()
8966            .flatten()
8967        })
8968        .collect()
8969}
8970
8971fn take_all_multicast_stops(state: &mut SessionState) -> Vec<ServerMessage> {
8972    let mut sessions = std::mem::take(&mut state.multicast)
8973        .into_iter()
8974        .collect::<Vec<_>>();
8975    sessions.sort_unstable_by_key(|(key, _)| (key.conference_id.get(), key.call_id.get()));
8976    sessions
8977        .into_iter()
8978        .flat_map(|(key, session)| {
8979            [
8980                session.receive.map(|leg| {
8981                    multicast_stop_message(key, session.wire_call_reference, leg.request, true)
8982                }),
8983                session.transmit.map(|leg| {
8984                    multicast_stop_message(key, session.wire_call_reference, leg.request, false)
8985                }),
8986            ]
8987            .into_iter()
8988            .flatten()
8989        })
8990        .collect()
8991}
8992
8993async fn drain_session_media(stream: &mut dyn StationIo, state: &mut SessionState) {
8994    let protocol = state.registration.protocol;
8995    let messages = take_all_multimedia_receive_closes(state)
8996        .into_iter()
8997        .chain(take_all_multimedia_transmit_stops(state))
8998        .chain(take_all_multicast_stops(state));
8999    for message in messages {
9000        if send_message(stream, &message, protocol).await.is_err() {
9001            break;
9002        }
9003    }
9004}
9005
9006async fn stop_call_multicast(
9007    stream: &mut dyn StationIo,
9008    state: &mut SessionState,
9009    call_id: CallId,
9010    protocol: ProtocolVersion,
9011) -> Result<(), ServerError> {
9012    for message in take_multicast_stops_for_call(state, call_id) {
9013        send_message(stream, &message, protocol).await?;
9014    }
9015    Ok(())
9016}
9017
9018fn allocate_media_request_identity(
9019    state: &mut SessionState,
9020    call_id: CallId,
9021) -> Result<MediaRequestIdentity, ServerError> {
9022    let generation = require_call(state, call_id)?
9023        .media
9024        .generation
9025        .checked_add(1)
9026        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
9027    let token = state
9028        .next_media_token
9029        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
9030    let identity = MediaRequestIdentity::new(generation, token)
9031        .ok_or(ServerError::MediaRequestIdentityExhausted)?;
9032    state.next_media_token = token.checked_next();
9033    require_call_mut(state, call_id)?.media.generation = generation;
9034    Ok(identity)
9035}
9036
9037fn media_request_party_id(
9038    request: Option<MediaRequestIdentity>,
9039    stable_call_reference: u32,
9040) -> u32 {
9041    request.map_or(stable_call_reference, |identity| identity.token().get())
9042}
9043
9044fn remove_call(state: &mut SessionState, call_id: CallId) {
9045    if let Some(call) = state.calls_by_id.remove(&call_id) {
9046        state.calls_by_wire.remove(&call.wire_reference);
9047        if state.active_call_id == Some(call_id) {
9048            state.active_call_id = None;
9049        }
9050    }
9051}
9052
9053fn canonical_ip_address(address: IpAddr) -> IpAddr {
9054    match address {
9055        IpAddr::V6(address) => address
9056            .to_ipv4_mapped()
9057            .map_or(IpAddr::V6(address), IpAddr::V4),
9058        address => address,
9059    }
9060}
9061
9062fn server_response_address(
9063    local: IpAddr,
9064    configured_ipv4_fallback: Ipv4Addr,
9065    configured_ipv6_fallback: Option<Ipv6Addr>,
9066) -> IpAddr {
9067    match canonical_ip_address(local) {
9068        IpAddr::V4(address) if address.is_unspecified() => IpAddr::V4(configured_ipv4_fallback),
9069        IpAddr::V6(address) if address.is_unspecified() => {
9070            configured_ipv6_fallback.map_or(IpAddr::V4(configured_ipv4_fallback), IpAddr::V6)
9071        }
9072        local => local,
9073    }
9074}
9075
9076fn server_response_endpoints(
9077    context: &SessionContext,
9078    protocol: ProtocolVersion,
9079) -> Result<Vec<SignalingServerEndpoint>, ServerError> {
9080    let local_endpoint = || {
9081        let address = server_response_address(
9082            context.local.ip(),
9083            context.config.advertised_address,
9084            context.config.advertised_ipv6_address,
9085        );
9086        let address = if protocol < ProtocolVersion::V17 && address.is_ipv6() {
9087            IpAddr::V4(context.config.advertised_address)
9088        } else {
9089            address
9090        };
9091        if address.is_unspecified() {
9092            return Err(ServerError::InvalidConfig(
9093                "server-list fallback address is unspecified".into(),
9094            ));
9095        }
9096        Ok(SignalingServerEndpoint {
9097            name: context.config.server_name.clone(),
9098            address,
9099            port: NonZeroU16::new(context.local.port()).ok_or_else(|| {
9100                ServerError::InvalidConfig("accepted local endpoint has port zero".into())
9101            })?,
9102        })
9103    };
9104    if context.config.signaling_servers.is_empty() {
9105        return local_endpoint().map(|endpoint| vec![endpoint]);
9106    }
9107
9108    let mut routes = context.config.signaling_servers.iter().collect::<Vec<_>>();
9109    routes.sort_unstable_by_key(|route| route.priority);
9110    let endpoints = routes
9111        .into_iter()
9112        .filter(|route| protocol >= ProtocolVersion::V17 || route.address.is_ipv4())
9113        .filter_map(|route| route.endpoint(context.transport))
9114        .collect::<Vec<_>>();
9115    if endpoints.is_empty() {
9116        local_endpoint().map(|endpoint| vec![endpoint])
9117    } else {
9118        Ok(endpoints)
9119    }
9120}
9121
9122async fn close_call_media_messages(
9123    stream: &mut dyn StationIo,
9124    call: &SessionCall,
9125    protocol: ProtocolVersion,
9126) -> Result<(), ServerError> {
9127    if let Some(leg) = &call.video_receive.leg {
9128        send_message(
9129            stream,
9130            &multimedia_receive_close_message(call, leg),
9131            protocol,
9132        )
9133        .await?;
9134    }
9135    if let Some(leg) = &call.video_transmit.leg {
9136        send_message(
9137            stream,
9138            &multimedia_transmit_stop_message(call, leg),
9139            protocol,
9140        )
9141        .await?;
9142    }
9143    if call.media.receive.state != MediaChannelState::Closed {
9144        send_message(
9145            stream,
9146            &ServerMessage::CloseReceiveChannel(AudioStreamControl {
9147                conference_id: ConferenceId::new(call.wire_reference),
9148                call_reference: CallReference::new(call.wire_reference),
9149                passthrough_party_id: media_request_party_id(
9150                    call.media.receive.request,
9151                    call.wire_reference,
9152                )
9153                .into(),
9154                port_handling_flag: 0,
9155            }),
9156            protocol,
9157        )
9158        .await?;
9159    }
9160    if call.media.transmit.state != MediaChannelState::Closed {
9161        send_message(
9162            stream,
9163            &ServerMessage::StopMediaTransmission(AudioStreamControl {
9164                conference_id: ConferenceId::new(call.wire_reference),
9165                call_reference: CallReference::new(call.wire_reference),
9166                passthrough_party_id: media_request_party_id(
9167                    call.media.transmit.request,
9168                    call.wire_reference,
9169                )
9170                .into(),
9171                port_handling_flag: 0,
9172            }),
9173            protocol,
9174        )
9175        .await?;
9176    }
9177    Ok(())
9178}
9179
9180async fn close_call_messages(
9181    stream: &mut dyn StationIo,
9182    call: &SessionCall,
9183    soft_keys: &SoftKeyProfile,
9184    protocol: ProtocolVersion,
9185    timezone_offset_minutes: i16,
9186) -> Result<(), ServerError> {
9187    send_message(
9188        stream,
9189        &ServerMessage::StopTone {
9190            line_instance: call.line_instance,
9191            call_reference: call.wire_reference,
9192        },
9193        protocol,
9194    )
9195    .await?;
9196    send_message(
9197        stream,
9198        &ServerMessage::SetLamp {
9199            stimulus: ButtonType::Line,
9200            instance: call.line_instance,
9201            mode: LampMode::Off,
9202        },
9203        protocol,
9204    )
9205    .await?;
9206    send_message(
9207        stream,
9208        &ServerMessage::ClearPrompt {
9209            line_instance: call.line_instance,
9210            call_reference: call.wire_reference,
9211        },
9212        protocol,
9213    )
9214    .await?;
9215    send_message(
9216        stream,
9217        &ServerMessage::CallState {
9218            state: CallState::OnHook,
9219            line_instance: call.line_instance,
9220            call_reference: call.wire_reference,
9221        },
9222        protocol,
9223    )
9224    .await?;
9225    send_message(
9226        stream,
9227        &ServerMessage::SelectSoftKeys {
9228            line_instance: 0,
9229            call_reference: 0,
9230            set: KeyMode::OnHook,
9231            valid_mask: soft_keys.valid_mask(KeyMode::OnHook),
9232        },
9233        protocol,
9234    )
9235    .await?;
9236    send_message(
9237        stream,
9238        &time_date_message(timezone_offset_minutes),
9239        protocol,
9240    )
9241    .await?;
9242    send_message(
9243        stream,
9244        &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
9245        protocol,
9246    )
9247    .await?;
9248    // Publish the matching OnHook state before stopping alerting.
9249    send_message(
9250        stream,
9251        &ServerMessage::SetRinger {
9252            mode: RingerMode::Off,
9253            duration: RingDuration::Normal,
9254            line_instance: call.line_instance,
9255            call_reference: call.wire_reference,
9256        },
9257        protocol,
9258    )
9259    .await?;
9260    Ok(())
9261}
9262
9263fn time_date_message(timezone_offset_minutes: i16) -> ServerMessage {
9264    time_date_message_at(SystemTime::now(), timezone_offset_minutes)
9265}
9266
9267fn time_date_message_at(now: SystemTime, timezone_offset_minutes: i16) -> ServerMessage {
9268    let unix = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
9269    let local = (unix as i128 + i128::from(timezone_offset_minutes) * 60)
9270        .clamp(0, i128::from(u32::MAX)) as u64;
9271    let days = (local / 86_400) as i64;
9272    let seconds = local % 86_400;
9273    let (year, month, day) = civil_from_days(days);
9274    ServerMessage::TimeDate {
9275        year: year as u32,
9276        month,
9277        weekday: ((days + 4).rem_euclid(7) + 1) as u32,
9278        day,
9279        hour: (seconds / 3600) as u32,
9280        minute: ((seconds % 3600) / 60) as u32,
9281        second: (seconds % 60) as u32,
9282        milliseconds: 0,
9283        unix_seconds: local as u32,
9284    }
9285}
9286
9287// Howard Hinnant's civil-from-days algorithm, with days based at Unix epoch.
9288fn civil_from_days(days_since_epoch: i64) -> (i64, u32, u32) {
9289    let z = days_since_epoch + 719_468;
9290    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
9291    let doe = z - era * 146_097;
9292    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
9293    let mut year = yoe + era * 400;
9294    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
9295    let mp = (5 * doy + 2) / 153;
9296    let day = doy - (153 * mp + 2) / 5 + 1;
9297    let month = mp + if mp < 10 { 3 } else { -9 };
9298    year += i64::from(month <= 2);
9299    (year, month as u32, day as u32)
9300}
9301
9302#[cfg(test)]
9303#[path = "server/tests/mod.rs"]
9304mod tests;