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