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