Skip to main content

sccp_protocol/
server.rs

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