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