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