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