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