Skip to main content

sccp_protocol/
server.rs

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