1mod 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;
129pub 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);
135pub const ORDERING_ACKNOWLEDGEMENT_TIMEOUT: Duration = Duration::from_secs(5);
138const MEDIA_PATH_RELEASE_GRACE: Duration = Duration::from_millis(150);
143const CONNECTION_STATISTICS_TIMEOUT: Duration = Duration::from_secs(10);
145const MAX_PENDING_CONNECTION_STATISTICS: usize = 32;
146const MAX_STATISTICS_REFERENCES_PER_SESSION: usize = 4096;
148const 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);
155pub const PARKING_MENU_MAX_ITEMS: usize = 32;
160
161#[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#[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#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
246pub enum DoNotDisturbMode {
247 #[default]
248 Off,
249 Silent,
250 Reject,
251}
252
253#[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#[derive(Clone, Debug, Eq, PartialEq)]
275pub struct MediaStatisticsSnapshot {
276 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 pub quality_byte_count: usize,
298}
299
300#[derive(Clone, Debug, Eq, PartialEq)]
306pub enum HandsetStatusMessage {
307 Display {
308 text: String,
309 timeout_seconds: u8,
311 priority: Option<NotificationPriority>,
312 },
313 Clear {
314 priority: Option<NotificationPriority>,
315 },
316}
317
318#[derive(Clone, Debug)]
323pub struct ServerConfig {
324 pub bind: SocketAddr,
325 pub signaling_qos: SignalingQos,
328 pub advertised_address: Ipv4Addr,
332 pub advertised_ipv6_address: Option<Ipv6Addr>,
334 pub server_name: String,
335 pub keepalive_seconds: u32,
338 pub secondary_keepalive_seconds: u32,
340 pub signaling_servers: Vec<SignalingServerRoute>,
343 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 pub timezone_offset_minutes: i16,
352 pub date_template: crate::types::DateTemplate,
353 pub anonymous_hotline: Option<AnonymousHotlineDefinition>,
357}
358
359#[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#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
386pub enum RegistrationFallback {
387 #[default]
388 Reject,
389 ReturnToPrimary,
390 DeviceIdOdd,
391 DeviceIdEven,
392}
393
394#[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#[derive(Clone, Debug, Eq, PartialEq)]
435pub struct AnonymousHotlineDefinition {
436 label: String,
437}
438
439impl AnonymousHotlineDefinition {
440 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#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
483pub enum CallSelectionOrder {
484 #[default]
485 OldestFirst,
486 LastFirst,
487}
488
489#[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#[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 pub fn validate(self) -> Result<Self, ServerError> {
520 validate_multimedia_receive_descriptor(&self)?;
521 Ok(self)
522 }
523}
524
525#[derive(Clone, Debug, Eq, PartialEq)]
531pub struct MultimediaTransmitDescriptor {
532 pub conference_id: ConferenceId,
533 pub endpoint: MediaEndpointAddress,
534 pub payload: MultimediaPayload,
535 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 pub fn validate(self) -> Result<Self, ServerError> {
546 validate_multimedia_transmit_descriptor(&self)?;
547 Ok(self)
548 }
549}
550
551#[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 RecoveryReferencePicture {
584 pictures: VideoPictureReferences,
585 },
586 TemporalSpatialTradeoff {
587 value: u32,
588 },
589}
590
591#[derive(Clone, Copy, Debug, Eq, PartialEq)]
593pub struct VideoPictureReference {
594 pub picture_number: u32,
595 pub long_term_picture_index: u32,
596}
597
598#[derive(Clone, Debug, Eq, PartialEq)]
600pub struct VideoPictureReferences(Box<[VideoPictureReference]>);
601
602impl VideoPictureReferences {
603 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 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)]
655pub enum Event {
663 SessionError {
664 peer: SocketAddr,
665 error: String,
666 },
667 ProtocolWarning {
670 peer: SocketAddr,
671 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#[derive(Clone, Debug, Eq, PartialEq)]
691pub struct DeviceEvent {
692 pub device_id: DeviceId,
693 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#[derive(Clone, Debug, Eq, PartialEq)]
718pub enum DeviceEventKind {
719 Registered(DeviceRegistration),
722 Disconnected {},
725 Capabilities {
728 capabilities: StationMediaCapabilities,
729 },
730 OffHook {
733 call_id: CallId,
734 line_instance: LineInstance,
735 },
736 OnHook {
739 call_id: CallId,
740 line_instance: LineInstance,
741 },
742 Digit { call_id: CallId, digit: Digit },
745 EnblocCall {
748 call_id: CallId,
749 line_instance: LineInstance,
750 number: String,
751 },
752 SpeedDial {
755 call_id: CallId,
756 line_instance: LineInstance,
757 number: String,
758 await_further_digits: bool,
759 },
760 SoftKey {
763 call_id: Option<CallId>,
765 line_instance: LineInstance,
766 soft_key: SoftKey,
767 },
768 LineButton {
771 line_instance: LineInstance,
772 call_id: Option<CallId>,
773 },
774 HookFlash {
777 call_id: Option<CallId>,
778 line_instance: LineInstance,
779 },
780 FeatureButton { instance: LineInstance },
783 DoNotDisturbButton { instance: LineInstance },
786 MobilityButton { instance: LineInstance },
789 VoicemailButton {
792 call_id: CallId,
793 line_instance: LineInstance,
794 },
795 ParkingLotButton {
798 instance: LineInstance,
800 call_id: Option<CallId>,
802 line_instance: LineInstance,
803 },
804 ParkingMenuSelection { lot: String, slot: u32 },
807 PhoneServiceResponse { response: PhoneServiceEvent },
810 ConferenceListAction { action: ConferenceListAction },
813 ReceiveChannelOpened {
816 call_id: CallId,
817 status: MediaStatus,
818 endpoint: MediaEndpoint,
819 },
820 MultimediaReceiveChannelOpened {
823 call_id: CallId,
824 codec: Codec,
825 endpoint: MediaEndpointAddress,
826 passthrough_party_id: PassthroughPartyId,
827 },
828 MultimediaReceiveChannelFailed {
831 call_id: CallId,
832 codec: Codec,
833 status: MediaStatus,
834 endpoint: MediaEndpointAddress,
835 passthrough_party_id: PassthroughPartyId,
836 },
837 MultimediaReceiveChannelTimedOut {
840 call_id: CallId,
841 codec: Codec,
842 passthrough_party_id: PassthroughPartyId,
843 },
844 MultimediaTransmitStarted {
847 call_id: CallId,
848 codec: Codec,
849 endpoint: MediaEndpointAddress,
850 passthrough_party_id: PassthroughPartyId,
851 },
852 MultimediaTransmitFailed {
855 call_id: CallId,
856 codec: Codec,
857 status: MediaStatus,
858 endpoint: MediaEndpointAddress,
859 passthrough_party_id: PassthroughPartyId,
860 },
861 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 HandsetAcknowledgementTimedOut {
876 call_id: CallId,
877 acknowledgement: HandsetAcknowledgement,
878 },
879 MediaTransmissionFailed {
882 call_id: CallId,
883 status: MediaStatus,
884 endpoint: MediaEndpoint,
885 },
886 MulticastReceptionStarted {
889 conference_id: ConferenceId,
890 call_id: CallId,
891 route: MulticastMediaRoute,
892 },
893 MulticastReceptionFailed {
896 conference_id: ConferenceId,
897 call_id: CallId,
898 status: MediaStatus,
899 },
900 MulticastReceptionTimedOut {
903 conference_id: ConferenceId,
904 call_id: CallId,
905 },
906 MulticastTransmissionStarted {
909 conference_id: ConferenceId,
910 call_id: CallId,
911 route: MulticastMediaRoute,
912 },
913 MulticastTransmissionFailed {
916 conference_id: ConferenceId,
917 call_id: CallId,
918 status: MediaStatus,
919 address: IpAddr,
920 port: u16,
921 },
922 ConnectionStatisticsCollected { snapshot: MediaStatisticsSnapshot },
925 Alarm {
928 severity: AlarmSeverity,
929 text: String,
930 parameters: Option<[u32; 2]>,
931 },
932 XmlAlarm { telemetry: PhoneAlarmTelemetry },
935 LocationInformation { telemetry: PhoneLocationTelemetry },
938 HeadsetStatusChanged { enabled: bool },
941 MediaPathChanged {
944 path: crate::message::values::MediaPathId,
945 event: crate::message::values::MediaPathEvent,
946 },
947 UnhandledMessage { message: ClientMessage },
950}
951
952#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
968pub enum ReceiveChannelPurpose {
969 Media,
971 InboundAnswer,
973}
974
975#[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#[derive(Clone, Debug)]
1003pub enum CommandAction {
1004 BeginCall {
1007 line_instance: LineInstance,
1008 call_id: CallId,
1009 codec: Codec,
1010 },
1011 BeginTransfer {
1014 source_call_id: CallId,
1015 consultation_line_instance: LineInstance,
1016 consultation_call_id: CallId,
1017 codec: Codec,
1018 },
1019 SetCallInfo { call_id: CallId, info: CallInfo },
1022 CommitOutboundCall { call_id: CallId, info: CallInfo },
1025 PresentOutboundProceeding { call_id: CallId, info: CallInfo },
1028 PresentOutboundRinging { call_id: CallId, info: CallInfo },
1031 SetCallState { call_id: CallId, state: CallState },
1034 SetCallSelected { call_id: CallId, selected: bool },
1037 DisplayPrompt {
1040 call_id: CallId,
1041 timeout_seconds: u32,
1042 text: String,
1043 },
1044 ClearPrompt { call_id: CallId },
1047 SetStatusMessage {
1050 message: HandsetStatusMessage,
1051 beep: bool,
1052 },
1053 SetMicrophoneMode { enabled: bool },
1056 SetRecordingStatus { call_id: CallId, active: bool },
1059 ResetDevice { reset_type: ResetType },
1062 SetMwi {
1065 line_instance: LineInstance,
1066 enabled: bool,
1067 },
1068 SetForwardStatus {
1071 line_instance: LineInstance,
1072 forward_all: Option<String>,
1073 forward_busy: Option<String>,
1074 forward_no_answer: Option<String>,
1075 },
1076 SetFeatureStatus {
1079 instance: LineInstance,
1080 enabled: bool,
1081 },
1082 SetDoNotDisturbStatus {
1085 instance: LineInstance,
1086 mode: DoNotDisturbMode,
1087 button_mode: DoNotDisturbButtonMode,
1088 },
1089 SetMobilityAppearance {
1092 mobility_instance: LineInstance,
1093 appearance: Option<LineAppearance>,
1094 },
1095 SetBlfStatus {
1098 instance: LineInstance,
1099 state: BlfState,
1100 caller: Option<BlfCallerInfo>,
1101 },
1102 ShowParkingMenu {
1105 instance: LineInstance,
1106 transaction_id: TransactionId,
1107 lot: String,
1108 calls: Vec<ParkingMenuEntry>,
1109 },
1110 ShowConferenceList {
1113 call_id: CallId,
1114 conference_id: ConferenceId,
1115 participants: Vec<ConferenceListEntry>,
1116 },
1117 ShowConferenceParticipantActions {
1120 call_id: CallId,
1121 conference_id: ConferenceId,
1122 participant: ConferenceListEntry,
1123 removable: bool,
1124 demotable: bool,
1125 },
1126 ShowTextService {
1129 line_instance: LineInstance,
1130 call_reference: CallReference,
1131 transaction_id: TransactionId,
1132 priority: PhoneServicePriority,
1133 document: CiscoIpPhoneText,
1134 },
1135 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 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 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 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 SetBackgroundImage {
1178 transaction_id: TransactionId,
1179 document: CiscoIpPhoneSetBackground,
1180 },
1181 PreviewBackgroundImage {
1184 transaction_id: TransactionId,
1185 document: CiscoIpPhoneSetBackgroundPreview,
1186 },
1187 SetRingtone {
1190 transaction_id: TransactionId,
1191 document: CiscoIpPhoneSetRingTone,
1192 },
1193 StartTone { call_id: CallId, tone: Tone },
1196 StartAnnouncement {
1199 conference_id: ConferenceId,
1200 announcements: Vec<AnnouncementEntry>,
1201 end_of_ack: bool,
1203 participant_ids: Vec<ParticipantId>,
1204 hearing_participant_mask: u32,
1206 play_mode: u32,
1208 },
1209 StopAnnouncement { conference_id: ConferenceId },
1212 AnnouncementFinish {
1215 conference_id: ConferenceId,
1216 play_status: u32,
1217 },
1218 StartRinging { call_id: CallId },
1221 StopRinging { call_id: CallId },
1224 OpenReceiveChannel {
1227 call_id: CallId,
1228 purpose: ReceiveChannelPurpose,
1229 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 OpenMultimediaReceiveChannel {
1241 call_id: CallId,
1242 descriptor: MultimediaReceiveDescriptor,
1243 },
1244 CloseMultimediaReceiveChannel { call_id: CallId },
1247 StartMultimediaTransmission {
1250 call_id: CallId,
1251 descriptor: MultimediaTransmitDescriptor,
1252 },
1253 StopMultimediaTransmission { call_id: CallId },
1256 SetMultimediaTransmitBitRate {
1259 call_id: CallId,
1260 passthrough_party_id: PassthroughPartyId,
1261 maximum_bit_rate: u32,
1262 },
1263 NotifyMultimediaTransmitBitRate {
1266 call_id: CallId,
1267 passthrough_party_id: PassthroughPartyId,
1268 maximum_bit_rate: u32,
1269 },
1270 ControlMultimediaTransmission {
1273 call_id: CallId,
1274 passthrough_party_id: PassthroughPartyId,
1275 control: MultimediaTransmitControl,
1276 },
1277 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 CloseReceiveChannel { call_id: CallId },
1293 StartMedia {
1296 call_id: CallId,
1297 endpoint: MediaEndpoint,
1298 dtmf_mode: DtmfMode,
1299 audio_processing: AudioProcessingPolicy,
1300 traffic_class: MediaTrafficClass,
1301 },
1302 StartMulticastReception {
1305 conference_id: ConferenceId,
1306 call_id: CallId,
1307 route: MulticastMediaRoute,
1308 echo_cancellation: EchoCancellation,
1309 g723_bitrate: G723BitRate,
1310 },
1311 StopMulticastReception {
1314 conference_id: ConferenceId,
1315 call_id: CallId,
1316 },
1317 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 StopMulticastTransmission {
1331 conference_id: ConferenceId,
1332 call_id: CallId,
1333 },
1334 StopMedia { call_id: CallId },
1337 CloseCall { call_id: CallId },
1340 DisconnectDevice {},
1343}
1344
1345#[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#[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#[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 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 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 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 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 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 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 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 pub fn reserve_call_id(&self) -> CallId {
1576 CallId(self.next_call_id.fetch_add(1, Ordering::Relaxed))
1577 }
1578
1579 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 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 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 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 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 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 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 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 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#[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 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 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 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 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 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 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(¤t, &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#[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(®istration.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(®istration.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(®istration.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 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 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 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
6048const 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
10404fn 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;