Skip to main content

sccp_protocol/
server.rs

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