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