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