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 .try_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 let appearance = device.line(instance)?;
6025 let is_primary = device
6026 .first_line()
6027 .is_some_and(|primary| primary.id == appearance.id);
6028 let fully_qualified_display_name = if is_primary && !device.description.is_empty() {
6029 device.description.clone()
6030 } else {
6031 appearance.line.number.clone()
6032 };
6033 Some(ServerMessage::LineStatus {
6034 instance: appearance.instance,
6035 directory_number: appearance.line.number.clone(),
6036 fully_qualified_display_name,
6037 display_label: appearance.display_label().to_owned(),
6038 })
6039}
6040
6041fn call_count_response(device: &DeviceDefinition) -> Result<ServerMessage, CodecError> {
6042 let lines = device.lines().collect::<Vec<_>>();
6043 let total_configured_lines = u32::try_from(lines.len()).map_err(|_| {
6044 CodecError::InvalidDefinition(format!(
6045 "device {} has too many lines for a call-count response",
6046 device.id
6047 ))
6048 })?;
6049 let starting_line_instance = lines.first().map_or(0, |line| line.instance);
6050 let line_data = lines
6051 .into_iter()
6052 .take(CALL_COUNT_RESPONSE_MAX_LINE_ENTRIES)
6053 .map(|_| CallCountLineData {
6054 max_calls: DEFAULT_MAX_CALLS_PER_LINE,
6055 busy_trigger: DEFAULT_BUSY_TRIGGER_PER_LINE,
6056 })
6057 .collect();
6058
6059 Ok(ServerMessage::CallCountResponse(CallCountResponse {
6060 total_configured_lines,
6061 starting_line_instance,
6062 line_data,
6063 }))
6064}
6065
6066fn mobility_device_candidate(
6067 current: &DeviceDefinition,
6068 current_appearances: &HashMap<u32, LineAppearance>,
6069 next_appearances: &HashMap<u32, LineAppearance>,
6070) -> Result<DeviceDefinition, CodecError> {
6071 let mut candidate = current.clone();
6072 candidate.buttons.retain(|button| {
6073 !matches!(
6074 button,
6075 ButtonDefinition::Line(line)
6076 if current_appearances.values().any(|appearance| appearance == line)
6077 )
6078 });
6079 let mut index = 0;
6080 while index < candidate.buttons.len() {
6081 let mobility_instance = match &candidate.buttons[index] {
6082 ButtonDefinition::Feature(feature) if feature.feature == ButtonType::Mobility => {
6083 Some(feature.instance)
6084 }
6085 _ => None,
6086 };
6087 if let Some(appearance) = mobility_instance
6088 .and_then(|instance| next_appearances.get(&instance))
6089 .cloned()
6090 {
6091 candidate
6092 .buttons
6093 .insert(index + 1, ButtonDefinition::Line(appearance));
6094 index += 2;
6095 } else {
6096 index += 1;
6097 }
6098 }
6099 candidate.validate()?;
6100 Ok(candidate)
6101}
6102
6103fn speed_dial_status(device: &DeviceDefinition, instance: u32) -> ServerMessage {
6104 let speed_dial = device.buttons.iter().find_map(|button| match button {
6105 ButtonDefinition::SpeedDial(speed_dial) if speed_dial.instance == instance => {
6106 Some((&speed_dial.number, &speed_dial.display_name))
6107 }
6108 _ => None,
6109 });
6110 ServerMessage::SpeedDialStatus {
6111 instance,
6112 number: speed_dial.map_or_else(String::new, |(number, _)| number.clone()),
6113 display_name: speed_dial.map_or_else(String::new, |(_, display_name)| display_name.clone()),
6114 }
6115}
6116
6117#[cfg(test)]
6118fn feature_status(
6119 device: &DeviceDefinition,
6120 instance: u32,
6121 capabilities: u32,
6122) -> Option<ServerMessage> {
6123 feature_status_for_station(
6124 device,
6125 instance,
6126 capabilities,
6127 ProtocolVersion::V15,
6128 DeviceType::Undefined,
6129 PhoneFeatures::empty(),
6130 )
6131}
6132
6133fn feature_status_for_station(
6134 device: &DeviceDefinition,
6135 instance: u32,
6136 _capabilities: u32,
6137 protocol: ProtocolVersion,
6138 device_type: DeviceType,
6139 features: PhoneFeatures,
6140) -> Option<ServerMessage> {
6141 if let Some(speed_dial) = device.blf_button(instance) {
6142 return Some(ServerMessage::FeatureStatus {
6143 instance,
6144 button_type: ButtonType::BlfSpeedDial,
6145 label: speed_dial.display_name.clone(),
6146 state: BusyLampFieldState::UnknownState.wire_value(),
6147 });
6148 }
6149 if let Some(recording) = device.recording_button(instance) {
6150 return Some(ServerMessage::FeatureStatus {
6151 instance,
6152 button_type: if recording_uses_multiblink(protocol, device_type) {
6153 ButtonType::MultiblinkFeature
6154 } else {
6155 ButtonType::Feature
6156 },
6157 label: recording_button_label(&recording.label, RecordingButtonState::Off, features),
6158 state: recording_button_status_word(RecordingButtonState::Off, protocol, device_type),
6159 });
6160 }
6161 device
6162 .feature_button(instance)
6163 .map(|feature| ServerMessage::FeatureStatus {
6164 instance,
6165 button_type: ButtonType::from(feature.feature.wire_value()),
6166 label: feature.label.clone(),
6167 state: 0,
6168 })
6169}
6170
6171fn feature_state_messages(
6172 device: &DeviceDefinition,
6173 instance: u32,
6174 enabled: bool,
6175) -> Option<[ServerMessage; 2]> {
6176 let feature = device.feature_button(instance)?;
6177 Some([
6178 ServerMessage::FeatureStatus {
6179 instance,
6180 button_type: ButtonType::from(feature.feature.wire_value()),
6181 label: feature.label.clone(),
6182 state: u32::from(enabled),
6183 },
6184 ServerMessage::SetLamp {
6185 stimulus: feature.feature,
6186 instance,
6187 mode: if enabled { LampMode::On } else { LampMode::Off },
6188 },
6189 ])
6190}
6191
6192fn cache_feature_projection(
6193 cache: &mut HashMap<u32, SessionFeatureState>,
6194 instance: u32,
6195 message: &ServerMessage,
6196) {
6197 let ServerMessage::FeatureStatus {
6198 button_type,
6199 label,
6200 state,
6201 ..
6202 } = message
6203 else {
6204 debug_assert!(false, "feature projection cache requires FeatureStatus");
6205 return;
6206 };
6207 cache.insert(
6208 instance,
6209 SessionFeatureState {
6210 button_type: *button_type,
6211 label: label.clone(),
6212 state: *state,
6213 },
6214 );
6215}
6216
6217fn apply_cached_feature_projection(
6218 cache: &HashMap<u32, SessionFeatureState>,
6219 instance: u32,
6220 message: &mut ServerMessage,
6221) {
6222 let Some(cached) = cache.get(&instance) else {
6223 return;
6224 };
6225 if let ServerMessage::FeatureStatus {
6226 button_type,
6227 label,
6228 state,
6229 ..
6230 } = message
6231 {
6232 *button_type = cached.button_type;
6233 label.clone_from(&cached.label);
6234 *state = cached.state;
6235 }
6236}
6237
6238fn recording_button_status_word(
6239 state: RecordingButtonState,
6240 protocol: ProtocolVersion,
6241 device_type: DeviceType,
6242) -> u32 {
6243 const ARMED_STATUS_WORD: u32 = 0x02_03_02;
6244 const ACTIVE_STATUS_WORD: u32 = 0x03_02_03;
6245 const ARMED_ACTIVE_STATUS_WORD: u32 = 0x03_02_05;
6246
6247 if !recording_uses_multiblink(protocol, device_type) {
6248 return (state.is_armed() || state.is_active()) as u32;
6249 }
6250 match state {
6251 RecordingButtonState::Off => 0,
6252 RecordingButtonState::Armed => ARMED_STATUS_WORD,
6253 RecordingButtonState::Active => ACTIVE_STATUS_WORD,
6254 RecordingButtonState::ArmedActive => ARMED_ACTIVE_STATUS_WORD,
6255 }
6256}
6257
6258fn recording_button_label(
6259 configured: &str,
6260 state: RecordingButtonState,
6261 features: PhoneFeatures,
6262) -> String {
6263 const ACTIVE_SUFFIX: &str = " (Recording)";
6264 const MAX_DYNAMIC_FEATURE_LABEL_BYTES: usize = 120;
6265 if !state.is_active() {
6266 return configured.to_owned();
6267 }
6268 let capacity = if features.contains(PhoneFeatures::DYNAMIC_MESSAGES) {
6269 MAX_DYNAMIC_FEATURE_LABEL_BYTES
6270 } else {
6271 crate::types::MAX_STATION_FEATURE_LABEL_BYTES
6272 };
6273 let encoded_len = |text: &str| {
6274 if features.contains(PhoneFeatures::UTF8) {
6275 text.len()
6276 } else {
6277 text.chars().count()
6278 }
6279 };
6280 if encoded_len(configured) + ACTIVE_SUFFIX.len() <= capacity {
6281 format!("{configured}{ACTIVE_SUFFIX}")
6282 } else {
6283 configured.to_owned()
6284 }
6285}
6286
6287fn recording_button_state_messages(
6288 device: &DeviceDefinition,
6289 instance: u32,
6290 state: RecordingButtonState,
6291 protocol: ProtocolVersion,
6292 device_type: DeviceType,
6293 features: PhoneFeatures,
6294) -> Option<[ServerMessage; 2]> {
6295 let recording = device.recording_button(instance)?;
6296 let button_type = if recording_uses_multiblink(protocol, device_type) {
6297 ButtonType::MultiblinkFeature
6298 } else {
6299 ButtonType::Feature
6300 };
6301 let lamp_mode = match state {
6302 RecordingButtonState::Off => LampMode::Off,
6303 RecordingButtonState::Armed => LampMode::On,
6304 RecordingButtonState::Active => LampMode::Wink,
6305 RecordingButtonState::ArmedActive => LampMode::Blink,
6306 };
6307 Some([
6308 ServerMessage::FeatureStatus {
6309 instance,
6310 button_type,
6311 label: recording_button_label(&recording.label, state, features),
6312 state: recording_button_status_word(state, protocol, device_type),
6313 },
6314 ServerMessage::SetLamp {
6315 stimulus: button_type,
6316 instance,
6317 mode: lamp_mode,
6318 },
6319 ])
6320}
6321
6322const fn multiblink_dnd_state(mode: DoNotDisturbMode) -> u32 {
6328 const OFF: u32 = 0x01_00_00;
6329 const REJECT: u32 = 0x02_02_02;
6330 const SILENT: u32 = 0x03_03_02;
6331
6332 match mode {
6333 DoNotDisturbMode::Off => OFF,
6334 DoNotDisturbMode::Reject => REJECT,
6335 DoNotDisturbMode::Silent => SILENT,
6336 }
6337}
6338
6339fn do_not_disturb_state_messages(
6340 device: &DeviceDefinition,
6341 instance: u32,
6342 mode: DoNotDisturbMode,
6343 button_mode: DoNotDisturbButtonMode,
6344 protocol: ProtocolVersion,
6345) -> Option<[ServerMessage; 2]> {
6346 let feature = device
6347 .feature_button(instance)
6348 .filter(|feature| feature.feature == ButtonType::DoNotDisturb)?;
6349 let exact_enabled = match button_mode {
6350 DoNotDisturbButtonMode::Cycle => mode != DoNotDisturbMode::Off,
6351 DoNotDisturbButtonMode::Silent => mode == DoNotDisturbMode::Silent,
6352 DoNotDisturbButtonMode::Reject => mode == DoNotDisturbMode::Reject,
6353 };
6354 let multi_state =
6355 button_mode == DoNotDisturbButtonMode::Cycle && protocol > ProtocolVersion::V15;
6356 let (button_type, state) = if multi_state {
6357 (ButtonType::MultiblinkFeature, multiblink_dnd_state(mode))
6358 } else {
6359 (ButtonType::DoNotDisturb, u32::from(exact_enabled))
6360 };
6361 let lamp = match (exact_enabled, mode) {
6362 (false, _) | (_, DoNotDisturbMode::Off) => LampMode::Off,
6363 (true, DoNotDisturbMode::Silent) => LampMode::Blink,
6364 (true, DoNotDisturbMode::Reject) => LampMode::On,
6365 };
6366 Some([
6367 ServerMessage::FeatureStatus {
6368 instance,
6369 button_type,
6370 label: feature.label.clone(),
6371 state,
6372 },
6373 ServerMessage::SetLamp {
6374 stimulus: feature.feature,
6375 instance,
6376 mode: lamp,
6377 },
6378 ])
6379}
6380
6381fn blf_status_message(
6382 device: &DeviceDefinition,
6383 instance: u32,
6384 state: BlfState,
6385) -> Option<ServerMessage> {
6386 let definition = device.blf_button(instance)?;
6387 let icon = match state {
6388 BlfState::Idle => BusyLampFieldState::Idle,
6389 BlfState::Ringing => BusyLampFieldState::Alerting,
6390 BlfState::Busy | BlfState::Held => BusyLampFieldState::InUse,
6391 BlfState::DoNotDisturb => BusyLampFieldState::DoNotDisturb,
6392 BlfState::Unavailable | BlfState::Unknown => BusyLampFieldState::UnknownState,
6393 };
6394 Some(ServerMessage::FeatureStatus {
6395 instance,
6396 button_type: ButtonType::BlfSpeedDial,
6397 label: definition.display_name.clone(),
6398 state: icon.wire_value(),
6399 })
6400}
6401
6402fn hinted_ringing_notification(
6403 device: &DeviceDefinition,
6404 label: &str,
6405 caller: Option<&BlfCallerInfo>,
6406 state: BlfState,
6407) -> Option<HandsetStatusMessage> {
6408 if !device.ui.hinted_ringing_notification || state != BlfState::Ringing {
6409 return None;
6410 }
6411 let caller = caller.map(BlfCallerInfo::display).unwrap_or_default();
6412 let text = if caller.is_empty() {
6413 format!("{label} is ringing")
6414 } else {
6415 format!("{label} is ringing: {caller}")
6416 };
6417 Some(HandsetStatusMessage::Display {
6418 text: truncate_utf8(&text, 79),
6419 timeout_seconds: 5,
6420 priority: None,
6421 })
6422}
6423
6424fn reconcile_blf_alert(
6425 instance: u32,
6426 notification: Option<HandsetStatusMessage>,
6427 active: &mut BTreeMap<u32, HandsetStatusMessage>,
6428 visible: &mut Option<HandsetStatusMessage>,
6429) -> Option<HandsetStatusMessage> {
6430 match notification {
6431 Some(notification) => {
6432 active.insert(instance, notification);
6433 }
6434 None => {
6435 active.remove(&instance);
6436 }
6437 }
6438 let next = active.first_key_value().map(|(_, message)| message.clone());
6439 if *visible == next {
6440 return None;
6441 }
6442 *visible = next.clone();
6443 Some(next.unwrap_or(HandsetStatusMessage::Clear { priority: None }))
6444}
6445
6446fn truncate_utf8(value: &str, maximum_bytes: usize) -> String {
6447 if value.len() <= maximum_bytes {
6448 return value.to_owned();
6449 }
6450 let end = value
6451 .char_indices()
6452 .map(|(index, _)| index)
6453 .take_while(|index| *index <= maximum_bytes)
6454 .last()
6455 .unwrap_or(0);
6456 value[..end].to_owned()
6457}
6458
6459fn service_url_status(device: &DeviceDefinition, index: u32) -> Option<ServerMessage> {
6460 device.buttons.iter().find_map(|button| match button {
6461 ButtonDefinition::Service(service) if service.instance == index => {
6462 Some(ServerMessage::ServiceUrlStatus {
6463 index,
6464 url: service.url.clone(),
6465 label: service.label.clone(),
6466 extension_text: String::new(),
6467 })
6468 }
6469 _ => None,
6470 })
6471}
6472
6473const fn key_mode_for_call_state(state: CallState) -> KeyMode {
6474 match state {
6475 CallState::Connected => KeyMode::Connected,
6476 CallState::Hold | CallState::HoldYellow | CallState::HoldRed => KeyMode::OnHold,
6477 CallState::RingIn | CallState::CallWaiting => KeyMode::RingIn,
6478 CallState::OffHook
6479 | CallState::Busy
6480 | CallState::Congestion
6481 | CallState::InvalidNumber
6482 | CallState::IntercomOneWay => KeyMode::OffHook,
6483 CallState::Transfer => KeyMode::ConnectedTransfer,
6484 CallState::RingOut | CallState::Proceed => KeyMode::RingOut,
6485 CallState::RemoteMultiline => KeyMode::OnHookStealable,
6486 CallState::OnHook | CallState::Park | CallState::Unknown(_) => KeyMode::OnHook,
6487 }
6488}
6489
6490fn transfer_key_mode(call: &SessionCall, state: CallState) -> KeyMode {
6491 if matches!(
6492 call.transfer_role,
6493 Some(SessionTransferRole::Consultation { .. })
6494 ) && matches!(state, CallState::RingOut | CallState::Connected)
6495 {
6496 KeyMode::ConnectedTransfer
6497 } else {
6498 key_mode_for_call_state(state)
6499 }
6500}
6501
6502fn stimulus_soft_key(stimulus: Stimulus) -> Option<SoftKey> {
6503 Some(match stimulus {
6504 Stimulus::LastNumberRedial => SoftKey::Redial,
6505 Stimulus::Hold => SoftKey::Hold,
6506 Stimulus::Transfer => SoftKey::Transfer,
6507 Stimulus::ForwardAll => SoftKey::ForwardAll,
6508 Stimulus::ForwardBusy => SoftKey::ForwardBusy,
6509 Stimulus::ForwardNoAnswer => SoftKey::ForwardNoAnswer,
6510 Stimulus::Conference => SoftKey::Conference,
6511 Stimulus::MeetMeConference => SoftKey::MeetMe,
6512 Stimulus::CallPark => SoftKey::Park,
6513 Stimulus::CallPickup => SoftKey::Pickup,
6514 Stimulus::GroupCallPickup => SoftKey::GroupPickup,
6515 Stimulus::DoNotDisturb => SoftKey::DoNotDisturb,
6516 Stimulus::ConferenceList => SoftKey::ConferenceList,
6517 Stimulus::NewCall => SoftKey::NewCall,
6518 Stimulus::EndCall => SoftKey::EndCall,
6519 _ => return None,
6520 })
6521}
6522
6523fn parking_menu_xml(
6524 instance: u32,
6525 transaction_id: u32,
6526 lot: &str,
6527 calls: &[ParkingMenuEntry],
6528) -> Result<String, ServerError> {
6529 if calls.len() > PARKING_MENU_MAX_ITEMS {
6530 return Err(PhoneXmlError::LimitExceeded {
6531 kind: "parking menu",
6532 actual: calls.len(),
6533 maximum: PARKING_MENU_MAX_ITEMS,
6534 }
6535 .into());
6536 }
6537 let items = calls
6538 .iter()
6539 .map(|call| {
6540 let party = if !call.caller_name.trim().is_empty() {
6541 call.caller_name.trim()
6542 } else if !call.caller_number.trim().is_empty() {
6543 call.caller_number.trim()
6544 } else {
6545 "Unknown caller"
6546 };
6547 let connected = if !call.connected_name.trim().is_empty() {
6548 format!(" to {}", call.connected_name.trim())
6549 } else if !call.connected_number.trim().is_empty() {
6550 format!(" to {}", call.connected_number.trim())
6551 } else {
6552 String::new()
6553 };
6554 CiscoIpPhoneMenuItem {
6555 name: Some(format!("{}: {}{}", call.slot, party, connected)),
6556 url: Some(format!(
6557 "UserCallData:{}:{instance}:0:{transaction_id}:retrieve/{}/{}",
6558 PARKING_APPLICATION_ID,
6559 utf8_percent_encode(lot, NON_ALPHANUMERIC),
6560 call.slot,
6561 )),
6562 }
6563 })
6564 .collect();
6565 CiscoIpPhoneMenu::new(
6566 format!("Parked calls - {lot}"),
6567 if calls.is_empty() {
6568 "No parked calls"
6569 } else {
6570 "Select a call"
6571 },
6572 items,
6573 )?
6574 .to_xml_with_limit(2_000)
6575 .map_err(ServerError::from)
6576}
6577
6578fn text_service_messages(
6579 line_instance: LineInstance,
6580 call_reference: CallReference,
6581 transaction_id: TransactionId,
6582 priority: PhoneServicePriority,
6583 document: &CiscoIpPhoneText,
6584 protocol: ProtocolVersion,
6585) -> Result<Vec<ServerMessage>, ServerError> {
6586 if protocol <= ProtocolVersion::V17
6587 && document
6588 .text
6589 .as_deref()
6590 .is_some_and(|text| text.chars().count() > PHONE_TEXT_LEGACY_MAX_CHARS)
6591 {
6592 return Err(PhoneXmlError::InvalidField {
6593 field: "legacy phone text body",
6594 expected: "at most 1024 characters",
6595 }
6596 .into());
6597 }
6598 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
6599 2_000
6600 } else {
6601 crate::phone::xml::PHONE_TEXT_MAX_BYTES
6602 };
6603 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
6604 Ok(phone_service_document_messages(
6605 line_instance,
6606 call_reference,
6607 ApplicationId::new(PHONE_TEXT_APPLICATION_ID),
6608 transaction_id,
6609 priority,
6610 &xml,
6611 ))
6612}
6613
6614fn input_service_messages(
6615 line_instance: LineInstance,
6616 call_reference: CallReference,
6617 application_id: ApplicationId,
6618 transaction_id: TransactionId,
6619 priority: PhoneServicePriority,
6620 document: &CiscoIpPhoneInput,
6621 protocol: ProtocolVersion,
6622) -> Result<Vec<ServerMessage>, ServerError> {
6623 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
6624 2_000
6625 } else {
6626 PHONE_INPUT_MAX_BYTES
6627 };
6628 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
6629 Ok(phone_service_document_messages(
6630 line_instance,
6631 call_reference,
6632 application_id,
6633 transaction_id,
6634 priority,
6635 &xml,
6636 ))
6637}
6638
6639fn execute_phone_action_messages(
6640 line_instance: LineInstance,
6641 call_reference: CallReference,
6642 application_id: ApplicationId,
6643 transaction_id: TransactionId,
6644 priority: PhoneServicePriority,
6645 document: &CiscoIpPhoneExecute,
6646 protocol: ProtocolVersion,
6647) -> Result<Vec<ServerMessage>, ServerError> {
6648 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
6649 2_000
6650 } else {
6651 PHONE_EXECUTE_MAX_BYTES
6652 };
6653 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
6654 Ok(phone_service_document_messages(
6655 line_instance,
6656 call_reference,
6657 application_id,
6658 transaction_id,
6659 priority,
6660 &xml,
6661 ))
6662}
6663
6664fn image_service_messages(
6665 line_instance: LineInstance,
6666 call_reference: CallReference,
6667 application_id: ApplicationId,
6668 transaction_id: TransactionId,
6669 priority: PhoneServicePriority,
6670 document: &PhoneImageDocument,
6671 protocol: ProtocolVersion,
6672) -> Result<Vec<ServerMessage>, ServerError> {
6673 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
6674 2_000
6675 } else {
6676 PHONE_IMAGE_MAX_BYTES
6677 };
6678 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
6679 Ok(phone_service_document_messages(
6680 line_instance,
6681 call_reference,
6682 application_id,
6683 transaction_id,
6684 priority,
6685 &xml,
6686 ))
6687}
6688
6689fn status_service_messages(
6690 line_instance: LineInstance,
6691 call_reference: CallReference,
6692 application_id: ApplicationId,
6693 transaction_id: TransactionId,
6694 priority: PhoneServicePriority,
6695 document: &PhoneStatusDocument,
6696 protocol: ProtocolVersion,
6697) -> Result<Vec<ServerMessage>, ServerError> {
6698 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
6699 2_000
6700 } else {
6701 PHONE_STATUS_MAX_BYTES
6702 };
6703 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
6704 Ok(phone_service_document_messages(
6705 line_instance,
6706 call_reference,
6707 application_id,
6708 transaction_id,
6709 priority,
6710 &xml,
6711 ))
6712}
6713
6714fn background_control_message(
6715 transaction_id: TransactionId,
6716 document: &PhoneBackgroundControlDocument,
6717) -> Result<ServerMessage, ServerError> {
6718 let xml = document.to_xml()?.into_bytes();
6719 let [message] = phone_service_document_messages(
6720 LineInstance::new(0),
6721 CallReference::new(0),
6722 ApplicationId::new(PHONE_BACKGROUND_APPLICATION_ID),
6723 transaction_id,
6724 PhoneServicePriority::LOW,
6725 &xml,
6726 )
6727 .try_into()
6728 .map_err(|_| PhoneXmlError::InvalidField {
6729 field: "background control document",
6730 expected: "a single application-data frame",
6731 })?;
6732 Ok(message)
6733}
6734
6735fn ringtone_control_message(
6736 transaction_id: TransactionId,
6737 document: &CiscoIpPhoneSetRingTone,
6738) -> Result<ServerMessage, ServerError> {
6739 let xml = document.to_xml()?.into_bytes();
6740 let [message] = phone_service_document_messages(
6741 LineInstance::new(0),
6742 CallReference::new(0),
6743 ApplicationId::new(PHONE_RINGTONE_APPLICATION_ID),
6744 transaction_id,
6745 PhoneServicePriority::LOW,
6746 &xml,
6747 )
6748 .try_into()
6749 .map_err(|_| PhoneXmlError::InvalidField {
6750 field: "ringtone control document",
6751 expected: "a single application-data frame",
6752 })?;
6753 Ok(message)
6754}
6755
6756#[cfg(test)]
6757fn start_announcement_message(
6758 conference_id: ConferenceId,
6759 announcements: Vec<AnnouncementEntry>,
6760 end_of_ack: bool,
6761 participant_ids: Vec<ParticipantId>,
6762 hearing_participant_mask: u32,
6763 play_mode: u32,
6764) -> ServerMessage {
6765 ServerMessage::StartAnnouncement {
6766 announcements,
6767 end_of_ack: u32::from(end_of_ack),
6768 conference_id: conference_id.get(),
6769 matrix_conference_party_ids: participant_ids
6770 .into_iter()
6771 .map(ParticipantId::get)
6772 .collect(),
6773 hearing_conference_party_mask: hearing_participant_mask,
6774 play_mode,
6775 }
6776}
6777
6778fn phone_service_document_messages(
6779 line_instance: LineInstance,
6780 call_reference: CallReference,
6781 application_id: ApplicationId,
6782 transaction_id: TransactionId,
6783 priority: PhoneServicePriority,
6784 xml: &[u8],
6785) -> Vec<ServerMessage> {
6786 let chunks = xml.chunks(2_000);
6787 let chunk_count = chunks.len();
6788 chunks
6789 .enumerate()
6790 .map(|(index, data)| {
6791 let sequence_flag = if chunk_count == 1 || index + 1 == chunk_count {
6792 2
6793 } else if index == 0 {
6794 0
6795 } else {
6796 1
6797 };
6798 ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6799 application_id: application_id.get(),
6800 line_instance: line_instance.get(),
6801 call_reference: call_reference.get(),
6802 transaction_id: transaction_id.get(),
6803 sequence_flag,
6804 display_priority: priority.wire(),
6805 conference_id: call_reference.get(),
6806 application_instance_id: application_id.get(),
6807 routing: 1,
6808 data: data.to_vec(),
6809 })
6810 })
6811 .collect()
6812}
6813
6814async fn handle_phone_service_message(
6815 state: &mut SessionState,
6816 context: &SessionContext,
6817 message_id: u32,
6818 kind: PhoneServiceMessageKind,
6819 routing: PhoneServiceRouting,
6820 extended: Option<PhoneServiceExtendedRouting>,
6821 data: &[u8],
6822) -> Result<(), ServerError> {
6823 let payload = match parse_phone_service_payload(data, kind) {
6824 Ok(payload) => payload,
6825 Err(error) => {
6826 warn!(
6827 device_id = %state.device.id,
6828 message_id = format_args!("0x{message_id:04x}"),
6829 %error,
6830 "ignoring malformed phone-service response"
6831 );
6832 context
6833 .event_tx
6834 .send(Event::ProtocolWarning {
6835 peer: context.peer,
6836 device_id: Some(state.device.id.clone()),
6837 message_id,
6838 error: error.to_string(),
6839 })
6840 .await
6841 .map_err(|_| ServerError::Stopped)?;
6842 return Ok(());
6843 }
6844 };
6845 let response = PhoneServiceEvent {
6846 kind,
6847 routing,
6848 extended,
6849 payload,
6850 };
6851
6852 if let Some((lot, slot)) = parking_menu_selection(state.pending_parking_menu, &response) {
6853 state.pending_parking_menu = None;
6854 context
6855 .event_tx
6856 .send(Event::device(
6857 state.device.id.clone(),
6858 state.generation,
6859 DeviceEventKind::ParkingMenuSelection { lot, slot },
6860 ))
6861 .await
6862 .map_err(|_| ServerError::Stopped)?;
6863 }
6864 if response.kind == PhoneServiceMessageKind::Data
6865 && response.routing.application_id.get() == ConferenceListAction::APPLICATION_ID
6866 && let PhoneServicePayload::Submission(submission) = &response.payload
6867 && let Some(action) = ConferenceListAction::from_route(&submission.route)
6868 {
6869 context
6870 .event_tx
6871 .send(Event::device(
6872 state.device.id.clone(),
6873 state.generation,
6874 DeviceEventKind::ConferenceListAction { action },
6875 ))
6876 .await
6877 .map_err(|_| ServerError::Stopped)?;
6878 }
6879 context
6880 .event_tx
6881 .send(Event::device(
6882 state.device.id.clone(),
6883 state.generation,
6884 DeviceEventKind::PhoneServiceResponse { response },
6885 ))
6886 .await
6887 .map_err(|_| ServerError::Stopped)
6888}
6889
6890fn parking_menu_selection(
6891 pending: Option<PendingParkingMenu>,
6892 response: &PhoneServiceEvent,
6893) -> Option<(String, u32)> {
6894 let pending = pending?;
6895 if response.kind != PhoneServiceMessageKind::Data
6896 || response.routing.application_id.get() != PARKING_APPLICATION_ID
6897 || response.routing.line_instance.get() != pending.instance
6898 || response.routing.call_reference.get() != 0
6899 || response.routing.transaction_id.get() != pending.transaction_id
6900 || response
6901 .extended
6902 .is_some_and(|extended| extended.application_instance_id != pending.instance)
6903 {
6904 return None;
6905 }
6906 let PhoneServicePayload::Submission(submission) = &response.payload else {
6907 return None;
6908 };
6909 let [action, lot, slot] = submission.route.as_slice() else {
6910 return None;
6911 };
6912 if action != "retrieve" || lot.is_empty() || !submission.values.is_empty() {
6913 return None;
6914 }
6915 let slot = slot.parse().ok()?;
6916 (slot != 0).then(|| (lot.clone(), slot))
6917}
6918
6919fn digit_character(digit: Digit) -> Option<char> {
6920 match digit {
6921 Digit::Number(number @ 0..=9) => Some(char::from(b'0' + number)),
6922 Digit::Star => Some('*'),
6923 Digit::Pound => Some('#'),
6924 Digit::A => Some('A'),
6925 Digit::B => Some('B'),
6926 Digit::C => Some('C'),
6927 Digit::D => Some('D'),
6928 Digit::Number(_) | Digit::Unknown(_) => None,
6929 }
6930}
6931
6932fn normalized_last_number(number: &str, config: &ServerConfig) -> Option<String> {
6933 let number = number.trim();
6934 let number = if config.record_dial_terminator {
6935 number
6936 } else {
6937 digit_character(config.dial_terminator)
6938 .map_or(number, |terminator| number.trim_end_matches(terminator))
6939 };
6940 (!number.is_empty()).then(|| number.to_owned())
6941}
6942
6943fn remember_last_number(
6944 state: &mut SessionState,
6945 line_instance: u32,
6946 number: &str,
6947 config: &ServerConfig,
6948) {
6949 if let Some(number) = normalized_last_number(number, config) {
6950 state.last_number_by_line.insert(line_instance, number);
6951 }
6952}
6953
6954async fn begin_redial(
6955 stream: &mut dyn StationIo,
6956 state: &mut SessionState,
6957 context: &SessionContext,
6958 line_instance: u32,
6959 existing_call_id: Option<CallId>,
6960) -> Result<(), ServerError> {
6961 if state.device.ui.placed_calls_redial_menu
6962 && placed_calls_menu_supported(state.registration.protocol)
6963 {
6964 let document = CiscoIpPhoneExecute::new(vec![CiscoIpPhoneExecuteItem::new(
6965 "Application:PlacedCalls",
6966 )?])?;
6967 for message in execute_phone_action_messages(
6968 LineInstance::new(line_instance),
6969 CallReference::new(0),
6970 ApplicationId::new(0),
6971 TransactionId::new(0),
6972 PhoneServicePriority::NORMAL,
6973 &document,
6974 state.registration.protocol,
6975 )? {
6976 send_message(stream, &message, state.registration.protocol).await?;
6977 }
6978 return Ok(());
6979 }
6980
6981 let Some(number) = state.last_number_by_line.get(&line_instance).cloned() else {
6982 return Ok(());
6983 };
6984 let existing = existing_call_id.and_then(|call_id| {
6985 state
6986 .calls_by_id
6987 .get(&call_id)
6988 .filter(|call| call.line_instance == line_instance && call.state != CallState::OnHook)
6989 .cloned()
6990 });
6991 let (call, created) = existing.map_or_else(
6992 || {
6993 (
6994 ensure_phone_call(state, 0, line_instance, &context.next_call_id),
6995 true,
6996 )
6997 },
6998 |call| (call, false),
6999 );
7000
7001 if created {
7002 state.active_key_mode = KeyMode::OffHook;
7003 begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
7004 context
7005 .event_tx
7006 .send(Event::device(
7007 state.device.id.clone(),
7008 state.generation,
7009 DeviceEventKind::OffHook {
7010 call_id: call.call_id,
7011 line_instance: LineInstance::new(line_instance),
7012 },
7013 ))
7014 .await
7015 .map_err(|_| ServerError::Stopped)?;
7016 }
7017 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
7018 stored.dialed_number.clone_from(&number);
7019 }
7020 send_message(
7021 stream,
7022 &ServerMessage::DialedNumber {
7023 number: number.clone(),
7024 line_instance,
7025 call_reference: call.wire_reference,
7026 },
7027 state.registration.protocol,
7028 )
7029 .await?;
7030 context
7031 .event_tx
7032 .send(Event::device(
7033 state.device.id.clone(),
7034 state.generation,
7035 DeviceEventKind::EnblocCall {
7036 call_id: call.call_id,
7037 line_instance: LineInstance::new(line_instance),
7038 number,
7039 },
7040 ))
7041 .await
7042 .map_err(|_| ServerError::Stopped)?;
7043 Ok(())
7044}
7045
7046fn placed_calls_menu_supported(protocol: ProtocolVersion) -> bool {
7047 protocol >= ProtocolVersion::V8
7048}
7049
7050async fn handle_session_command(
7051 stream: &mut dyn StationIo,
7052 state: &mut SessionState,
7053 command: SessionCommand,
7054 context: &SessionContext,
7055) -> Result<bool, ServerError> {
7056 let config = &context.config;
7057 let protocol = state.registration.protocol;
7058 match command {
7059 SessionCommand::Confirmed { .. } => {
7060 unreachable!("confirmed commands are unwrapped by the session loop")
7061 }
7062 SessionCommand::OfferIncoming {
7063 line_instance,
7064 call_id,
7065 info,
7066 presentation,
7067 ringer,
7068 delivery: _,
7069 } => {
7070 let line_instance = normalize_line(state, line_instance.get());
7071 let statistics_directory_number = statistics_directory_for_call_info(&info).to_owned();
7072 let caller = match (
7073 info.calling_name.trim().is_empty(),
7074 info.calling_number.trim().is_empty(),
7075 ) {
7076 (false, false) => format!("{} ({})", info.calling_name, info.calling_number),
7077 (false, true) => info.calling_name.clone(),
7078 (true, false) => info.calling_number.clone(),
7079 (true, true) => "Unknown number".to_owned(),
7080 };
7081 let incoming_state = presentation.call_state();
7082 let call = insert_call(state, call_id, line_instance, Codec::Pcmu, incoming_state);
7083 if incoming_state == CallState::RingIn && state.active_call_id.is_none() {
7084 state.active_call_id = Some(call.call_id);
7085 }
7086 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
7087 stored.statistics_directory_number = statistics_directory_number;
7088 stored.ringer = ringer;
7089 }
7090 send_message(
7091 stream,
7092 &ServerMessage::ClearPrompt {
7093 line_instance,
7094 call_reference: call.wire_reference,
7095 },
7096 protocol,
7097 )
7098 .await?;
7099 send_message(
7100 stream,
7101 &ServerMessage::CallState {
7102 state: incoming_state,
7103 line_instance,
7104 call_reference: call.wire_reference,
7105 },
7106 protocol,
7107 )
7108 .await?;
7109 send_station_ui_message(
7110 stream,
7111 state,
7112 &ServerMessage::CallInfo {
7113 info: *info,
7114 line_instance,
7115 call_reference: call.wire_reference,
7116 },
7117 )
7118 .await?;
7119 send_message(
7120 stream,
7121 &ServerMessage::SetLamp {
7122 stimulus: ButtonType::Line,
7123 instance: line_instance,
7124 mode: LampMode::Blink,
7125 },
7126 protocol,
7127 )
7128 .await?;
7129 if let Some(ringer) = incoming_ringer(ringer, incoming_state) {
7130 let audible = ringer_is_audible(ringer);
7131 if audible || state.ringer_owner.is_none() {
7132 send_message(
7133 stream,
7134 &ServerMessage::SetRinger {
7135 mode: ringer.mode,
7136 duration: ringer.duration,
7137 line_instance,
7138 call_reference: call.wire_reference,
7139 },
7140 protocol,
7141 )
7142 .await?;
7143 }
7144 if audible {
7145 state.ringer_owner = Some(call.call_id);
7146 }
7147 }
7148 state.active_key_mode = KeyMode::RingIn;
7149 send_message(
7150 stream,
7151 &ServerMessage::SelectSoftKeys {
7152 line_instance,
7153 call_reference: call.wire_reference,
7154 set: KeyMode::RingIn,
7155 valid_mask: state.device.soft_keys.valid_mask(KeyMode::RingIn),
7156 },
7157 protocol,
7158 )
7159 .await?;
7160 send_station_ui_message(
7161 stream,
7162 state,
7163 &ServerMessage::DisplayPrompt {
7164 timeout_seconds: 0,
7165 text: format!("From {caller}"),
7166 line_instance,
7167 call_reference: call.wire_reference,
7168 },
7169 )
7170 .await?;
7171 }
7172 SessionCommand::Public(command) => {
7173 let command = *command;
7174 if let Some(call_id) = command_call_id(&command)
7175 && !matches!(
7176 &command.action,
7177 CommandAction::BeginCall { .. } | CommandAction::CloseCall { .. }
7178 )
7179 && !state.calls_by_id.contains_key(&call_id)
7180 {
7181 debug!(device_id = %state.device.id, ?call_id, command = ?command, "ignoring stale SCCP call command");
7182 return Ok(false);
7183 }
7184 let action = command.action;
7185 match action {
7186 CommandAction::DisconnectDevice { .. } => {
7187 return Ok(true);
7188 }
7189 CommandAction::BeginCall {
7190 line_instance,
7191 call_id,
7192 codec,
7193 } => {
7194 if state.calls_by_id.contains_key(&call_id) {
7195 return Ok(false);
7196 }
7197 let line_instance = normalize_line(state, line_instance.get());
7198 let call =
7199 insert_call(state, call_id, line_instance, codec, CallState::OffHook);
7200 state.active_call_id = Some(call.call_id);
7201 state.active_key_mode = KeyMode::OffHook;
7202 begin_phone_call_ui(stream, &call, &state.device, state.station_context())
7203 .await?;
7204 }
7205 CommandAction::BeginTransfer {
7206 source_call_id,
7207 consultation_line_instance,
7208 consultation_call_id,
7209 codec,
7210 } => {
7211 let consultation_line_instance = consultation_line_instance.get();
7212 if state.calls_by_id.contains_key(&consultation_call_id) {
7213 return Ok(false);
7214 }
7215 let source = require_call_mut(state, source_call_id)?;
7216 if !matches!(
7217 source.state,
7218 CallState::Hold | CallState::HoldYellow | CallState::HoldRed
7219 ) {
7220 return Err(ServerError::InvalidCallTransaction {
7221 call_id: source_call_id,
7222 operation: "begin transfer",
7223 state: source.state,
7224 });
7225 }
7226 source.state = CallState::Transfer;
7227 source.transfer_role = Some(SessionTransferRole::Source {
7228 consultation_call_id,
7229 });
7230 let source = source.clone();
7231 send_message(
7232 stream,
7233 &ServerMessage::CallState {
7234 state: CallState::Transfer,
7235 line_instance: source.line_instance,
7236 call_reference: source.wire_reference,
7237 },
7238 protocol,
7239 )
7240 .await?;
7241 send_station_ui_message(
7242 stream,
7243 state,
7244 &ServerMessage::DisplayPrompt {
7245 timeout_seconds: 0,
7246 text: "Call Transfer".into(),
7247 line_instance: source.line_instance,
7248 call_reference: source.wire_reference,
7249 },
7250 )
7251 .await?;
7252
7253 let line_instance = normalize_line(state, consultation_line_instance);
7254 let mut consultation = insert_call(
7255 state,
7256 consultation_call_id,
7257 line_instance,
7258 codec,
7259 CallState::OffHook,
7260 );
7261 consultation.transfer_role =
7262 Some(SessionTransferRole::Consultation { source_call_id });
7263 state
7264 .calls_by_id
7265 .insert(consultation_call_id, consultation.clone());
7266 state.active_call_id = Some(consultation.call_id);
7267 state.active_key_mode = KeyMode::OffHookFeature;
7268 begin_phone_call_ui_with_key_mode(
7269 stream,
7270 &consultation,
7271 &state.device,
7272 KeyMode::OffHookFeature,
7273 state.station_context(),
7274 )
7275 .await?;
7276 send_message(
7277 stream,
7278 &ServerMessage::SetLamp {
7279 stimulus: ButtonType::Transfer,
7280 instance: source.line_instance,
7281 mode: LampMode::Flash,
7282 },
7283 protocol,
7284 )
7285 .await?;
7286 }
7287 CommandAction::SetCallSelected {
7288 call_id, selected, ..
7289 } => {
7290 let call = require_call(state, call_id)?.clone();
7291 send_message(
7292 stream,
7293 &ServerMessage::CallSelectStatus {
7294 status: u32::from(selected),
7295 call_reference: call.wire_reference,
7296 line_instance: call.line_instance,
7297 },
7298 protocol,
7299 )
7300 .await?;
7301 }
7302 CommandAction::SetMwi {
7303 line_instance,
7304 enabled,
7305 ..
7306 } => {
7307 let line_instance = line_instance.get();
7308 state.mwi_by_line.insert(line_instance, enabled);
7309 send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
7310 }
7311 CommandAction::SetForwardStatus {
7312 line_instance,
7313 forward_all,
7314 forward_busy,
7315 forward_no_answer,
7316 ..
7317 } => {
7318 let line_instance = line_instance.get();
7319 state.forwarding_by_line.insert(
7320 line_instance,
7321 SessionForwarding {
7322 all: forward_all.clone(),
7323 busy: forward_busy.clone(),
7324 no_answer: forward_no_answer.clone(),
7325 },
7326 );
7327 send_message(
7328 stream,
7329 &ServerMessage::ForwardStatus {
7330 line_instance,
7331 forward_all,
7332 forward_busy,
7333 forward_no_answer,
7334 },
7335 protocol,
7336 )
7337 .await?;
7338 }
7339 CommandAction::SetFeatureStatus {
7340 instance, enabled, ..
7341 } => {
7342 let instance = instance.get();
7343 if let Some(messages) = feature_state_messages(&state.device, instance, enabled)
7344 {
7345 cache_feature_projection(&mut state.feature_states, instance, &messages[0]);
7346 for message in messages {
7347 send_station_ui_message(stream, state, &message).await?;
7348 }
7349 }
7350 }
7351 CommandAction::SetDoNotDisturbStatus {
7352 instance,
7353 mode,
7354 button_mode,
7355 ..
7356 } => {
7357 let instance = instance.get();
7358 if let Some(messages) = do_not_disturb_state_messages(
7359 &state.device,
7360 instance,
7361 mode,
7362 button_mode,
7363 protocol,
7364 ) {
7365 cache_feature_projection(&mut state.feature_states, instance, &messages[0]);
7366 for message in messages {
7367 send_station_ui_message(stream, state, &message).await?;
7368 }
7369 }
7370 }
7371 CommandAction::SetRecordingButtonStatus {
7372 state: recording_state,
7373 } => {
7374 let instances = state
7375 .device
7376 .buttons
7377 .iter()
7378 .filter_map(|button| match button {
7379 ButtonDefinition::Recording(recording) => Some(recording.instance),
7380 _ => None,
7381 })
7382 .collect::<Vec<_>>();
7383 for instance in instances {
7384 let Some(messages) = recording_button_state_messages(
7385 &state.device,
7386 instance,
7387 recording_state,
7388 protocol,
7389 state.registration.device_type,
7390 state.features,
7391 ) else {
7392 continue;
7393 };
7394 cache_feature_projection(&mut state.feature_states, instance, &messages[0]);
7395 for message in messages {
7396 send_station_ui_message(stream, state, &message).await?;
7397 }
7398 }
7399 }
7400 CommandAction::SetMobilityAppearance {
7401 mobility_instance,
7402 appearance,
7403 ..
7404 } => {
7405 let mobility_instance = mobility_instance.get();
7406 let configured = state.device.buttons.iter().any(|button| {
7407 matches!(
7408 button,
7409 ButtonDefinition::Feature(feature)
7410 if feature.instance == mobility_instance
7411 && feature.feature == ButtonType::Mobility
7412 )
7413 });
7414 if !configured {
7415 return Err(CodecError::InvalidDefinition(format!(
7416 "device {} has no mobility button instance {mobility_instance}",
7417 state.device.id
7418 ))
7419 .into());
7420 }
7421 let previous = state.mobility_appearances.get(&mobility_instance).cloned();
7422 let mut next_appearances = state.mobility_appearances.clone();
7423 match &appearance {
7424 Some(appearance) => {
7425 next_appearances.insert(mobility_instance, appearance.clone());
7426 }
7427 None => {
7428 next_appearances.remove(&mobility_instance);
7429 }
7430 }
7431 let candidate = mobility_device_candidate(
7432 &state.device,
7433 &state.mobility_appearances,
7434 &next_appearances,
7435 )?;
7436
7437 send_button_template(
7438 stream,
7439 &candidate,
7440 protocol,
7441 state.registration.device_type,
7442 )
7443 .await?;
7444 if let Some(appearance) = &appearance {
7445 if let Some(message) = line_status(&candidate, appearance.instance) {
7446 send_station_ui_message(stream, state, &message).await?;
7447 }
7448 } else if let Some(previous) = &previous {
7449 send_station_ui_message(
7450 stream,
7451 state,
7452 &ServerMessage::LineStatus {
7453 instance: previous.instance,
7454 directory_number: String::new(),
7455 fully_qualified_display_name: String::new(),
7456 display_label: String::new(),
7457 },
7458 )
7459 .await?;
7460 }
7461 state.device = candidate;
7462 state.mobility_appearances = next_appearances;
7463 }
7464 CommandAction::SetBlfStatus {
7465 instance,
7466 state: blf_state,
7467 caller,
7468 ..
7469 } => {
7470 let instance = instance.get();
7471 let Some(message) = blf_status_message(&state.device, instance, blf_state)
7472 else {
7473 return Err(ServerError::UnknownBlfButton {
7474 device: state.device.id.clone(),
7475 instance,
7476 });
7477 };
7478 let ServerMessage::FeatureStatus { ref label, .. } = message else {
7479 unreachable!("BLF status is a feature-state message")
7480 };
7481 cache_feature_projection(&mut state.feature_states, instance, &message);
7482 send_station_ui_message(stream, state, &message).await?;
7483 let notification = hinted_ringing_notification(
7484 &state.device,
7485 label,
7486 caller.as_ref(),
7487 blf_state,
7488 );
7489 if let Some(notification) = reconcile_blf_alert(
7490 instance,
7491 notification,
7492 &mut state.runtime.active_blf_alerts,
7493 &mut state.runtime.visible_blf_alert,
7494 ) {
7495 for message in status_message_frames(
7496 notification,
7497 state.registration.device_type,
7498 &mut state.persistent_status_message,
7499 ) {
7500 send_station_ui_message(stream, state, &message).await?;
7501 }
7502 }
7503 }
7504 CommandAction::ShowParkingMenu {
7505 instance,
7506 transaction_id,
7507 lot,
7508 calls,
7509 ..
7510 } => {
7511 let instance = instance.get();
7512 let transaction_id = transaction_id.get();
7513 send_message(
7514 stream,
7515 &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
7516 application_id: PARKING_APPLICATION_ID,
7517 line_instance: instance,
7518 call_reference: 0,
7519 transaction_id,
7520 sequence_flag: 0,
7521 display_priority: 2,
7522 conference_id: 0,
7523 application_instance_id: instance,
7524 routing: 0,
7525 data: parking_menu_xml(instance, transaction_id, &lot, &calls)?
7526 .into_bytes(),
7527 }),
7528 protocol,
7529 )
7530 .await?;
7531 state.pending_parking_menu = Some(PendingParkingMenu {
7532 instance,
7533 transaction_id,
7534 });
7535 }
7536 CommandAction::ShowConferenceList {
7537 call_id,
7538 conference_id,
7539 participants,
7540 ..
7541 } => {
7542 let call = require_call(state, call_id)?.clone();
7543 let family = if protocol >= ProtocolVersion::V8 {
7544 ConferenceMenuFamily::IconMenu
7545 } else {
7546 ConferenceMenuFamily::Menu
7547 };
7548 let data = ConferenceListDocument::new(conference_id, &participants, family)?
7549 .to_xml()?
7550 .into_bytes();
7551 send_message(
7552 stream,
7553 &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
7554 application_id: ConferenceListAction::APPLICATION_ID,
7555 line_instance: call.line_instance,
7556 call_reference: call.wire_reference,
7557 transaction_id: conference_id.get(),
7558 sequence_flag: 0,
7559 display_priority: 2,
7560 conference_id: conference_id.get(),
7561 application_instance_id: call.line_instance,
7562 routing: 0,
7563 data,
7564 }),
7565 protocol,
7566 )
7567 .await?;
7568 }
7569 CommandAction::ShowConferenceParticipantActions {
7570 call_id,
7571 conference_id,
7572 participant,
7573 removable,
7574 demotable,
7575 ..
7576 } => {
7577 let call = require_call(state, call_id)?.clone();
7578 let family = if protocol >= ProtocolVersion::V8 {
7579 ConferenceMenuFamily::IconMenu
7580 } else {
7581 ConferenceMenuFamily::Menu
7582 };
7583 let data = ConferenceParticipantActionsDocument::new(
7584 conference_id,
7585 &participant,
7586 removable,
7587 demotable,
7588 family,
7589 )?
7590 .to_xml()?
7591 .into_bytes();
7592 send_message(
7593 stream,
7594 &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
7595 application_id: ConferenceListAction::APPLICATION_ID,
7596 line_instance: call.line_instance,
7597 call_reference: call.wire_reference,
7598 transaction_id: conference_id.get(),
7599 sequence_flag: 0,
7600 display_priority: 2,
7601 conference_id: conference_id.get(),
7602 application_instance_id: call.line_instance,
7603 routing: 0,
7604 data,
7605 }),
7606 protocol,
7607 )
7608 .await?;
7609 }
7610 CommandAction::ShowTextService {
7611 line_instance,
7612 call_reference,
7613 transaction_id,
7614 priority,
7615 document,
7616 ..
7617 } => {
7618 for message in text_service_messages(
7619 line_instance,
7620 call_reference,
7621 transaction_id,
7622 priority,
7623 &document,
7624 protocol,
7625 )? {
7626 send_message(stream, &message, protocol).await?;
7627 }
7628 }
7629 CommandAction::ShowInputService {
7630 line_instance,
7631 call_reference,
7632 application_id,
7633 transaction_id,
7634 priority,
7635 document,
7636 ..
7637 } => {
7638 for message in input_service_messages(
7639 line_instance,
7640 call_reference,
7641 application_id,
7642 transaction_id,
7643 priority,
7644 &document,
7645 protocol,
7646 )? {
7647 send_message(stream, &message, protocol).await?;
7648 }
7649 }
7650 CommandAction::ExecutePhoneActions {
7651 line_instance,
7652 call_reference,
7653 application_id,
7654 transaction_id,
7655 priority,
7656 document,
7657 ..
7658 } => {
7659 for message in execute_phone_action_messages(
7660 line_instance,
7661 call_reference,
7662 application_id,
7663 transaction_id,
7664 priority,
7665 &document,
7666 protocol,
7667 )? {
7668 send_message(stream, &message, protocol).await?;
7669 }
7670 }
7671 CommandAction::ShowImageService {
7672 line_instance,
7673 call_reference,
7674 application_id,
7675 transaction_id,
7676 priority,
7677 document,
7678 ..
7679 } => {
7680 for message in image_service_messages(
7681 line_instance,
7682 call_reference,
7683 application_id,
7684 transaction_id,
7685 priority,
7686 &document,
7687 protocol,
7688 )? {
7689 send_message(stream, &message, protocol).await?;
7690 }
7691 }
7692 CommandAction::ShowStatusService {
7693 line_instance,
7694 call_reference,
7695 application_id,
7696 transaction_id,
7697 priority,
7698 document,
7699 ..
7700 } => {
7701 for message in status_service_messages(
7702 line_instance,
7703 call_reference,
7704 application_id,
7705 transaction_id,
7706 priority,
7707 &document,
7708 protocol,
7709 )? {
7710 send_message(stream, &message, protocol).await?;
7711 }
7712 }
7713 CommandAction::SetBackgroundImage {
7714 transaction_id,
7715 document,
7716 ..
7717 } => {
7718 let message = background_control_message(
7719 transaction_id,
7720 &PhoneBackgroundControlDocument::Set(document),
7721 )?;
7722 send_message(stream, &message, protocol).await?;
7723 }
7724 CommandAction::PreviewBackgroundImage {
7725 transaction_id,
7726 document,
7727 ..
7728 } => {
7729 let message = background_control_message(
7730 transaction_id,
7731 &PhoneBackgroundControlDocument::Preview(document),
7732 )?;
7733 send_message(stream, &message, protocol).await?;
7734 }
7735 CommandAction::SetRingtone {
7736 transaction_id,
7737 document,
7738 ..
7739 } => {
7740 let message = ringtone_control_message(transaction_id, &document)?;
7741 send_message(stream, &message, protocol).await?;
7742 }
7743 CommandAction::StartTone { call_id, tone, .. } => {
7744 let call = require_call(state, call_id)?.clone();
7745 let message = if tone == Tone::Silence {
7746 ServerMessage::StopTone {
7747 line_instance: call.line_instance,
7748 call_reference: call.wire_reference,
7749 }
7750 } else {
7751 ServerMessage::StartTone {
7752 tone,
7753 direction: ToneDirection::User,
7754 line_instance: call.line_instance,
7755 call_reference: call.wire_reference,
7756 }
7757 };
7758 send_message(stream, &message, protocol).await?;
7759 }
7760 CommandAction::StartAnnouncement {
7761 conference_id,
7762 announcements,
7763 end_of_ack,
7764 participant_ids,
7765 hearing_participant_mask,
7766 play_mode,
7767 ..
7768 } => {
7769 let _ = (
7770 conference_id,
7771 announcements,
7772 end_of_ack,
7773 participant_ids,
7774 hearing_participant_mask,
7775 play_mode,
7776 );
7777 return Err(ServerError::InvalidStationCommand {
7778 message: "StartAnnouncement",
7779 });
7780 }
7781 CommandAction::StopAnnouncement { conference_id, .. } => {
7782 let _ = conference_id;
7783 return Err(ServerError::InvalidStationCommand {
7784 message: "StopAnnouncement",
7785 });
7786 }
7787 CommandAction::AnnouncementFinish {
7788 conference_id,
7789 play_status,
7790 ..
7791 } => {
7792 let _ = (conference_id, play_status);
7793 return Err(ServerError::InvalidStationCommand {
7794 message: "AnnouncementFinish",
7795 });
7796 }
7797 CommandAction::SetCallInfo { call_id, info, .. } => {
7798 let statistics_directory_number =
7799 statistics_directory_for_call_info(&info).to_owned();
7800 if let Some(stored) = state.calls_by_id.get_mut(&call_id) {
7801 stored.statistics_directory_number = statistics_directory_number;
7802 }
7803 let call = require_call(state, call_id)?.clone();
7804 send_station_ui_message(
7805 stream,
7806 state,
7807 &ServerMessage::CallInfo {
7808 info,
7809 line_instance: call.line_instance,
7810 call_reference: call.wire_reference,
7811 },
7812 )
7813 .await?;
7814 }
7815 CommandAction::CommitOutboundCall { call_id, info, .. } => {
7816 let statistics_directory_number =
7817 statistics_directory_for_call_info(&info).to_owned();
7818 let call = require_call_mut(state, call_id)?;
7819 call.state = CallState::Proceed;
7820 call.history_disposition =
7821 updated_history_disposition(call.history_disposition, CallState::Proceed);
7822 call.statistics_directory_number = statistics_directory_number;
7823 let call = call.clone();
7824 let number = digit_character(config.dial_terminator)
7825 .and_then(|terminator| call.dialed_number.strip_suffix(terminator))
7826 .unwrap_or(&call.dialed_number)
7827 .to_owned();
7828 remember_last_number(state, call.line_instance, &number, config);
7829 state.active_call_id = Some(call.call_id);
7830 refresh_mwi_lamps(stream, state, protocol).await?;
7831 for message in [
7832 ServerMessage::StopTone {
7833 line_instance: call.line_instance,
7834 call_reference: call.wire_reference,
7835 },
7836 ServerMessage::SetLamp {
7837 stimulus: ButtonType::Line,
7838 instance: call.line_instance,
7839 mode: LampMode::Blink,
7840 },
7841 ServerMessage::CallInfo {
7842 info,
7843 line_instance: call.line_instance,
7844 call_reference: call.wire_reference,
7845 },
7846 ServerMessage::DialedNumber {
7847 number,
7848 line_instance: call.line_instance,
7849 call_reference: call.wire_reference,
7850 },
7851 ServerMessage::CallState {
7852 state: CallState::Proceed,
7853 line_instance: call.line_instance,
7854 call_reference: call.wire_reference,
7855 },
7856 ] {
7857 send_station_ui_message(stream, state, &message).await?;
7858 }
7859 }
7860 CommandAction::PresentOutboundProceeding { call_id, info, .. } => {
7861 let statistics_directory_number =
7862 statistics_directory_for_call_info(&info).to_owned();
7863 let call = require_call_mut(state, call_id)?;
7864 call.state = CallState::Proceed;
7865 call.history_disposition =
7866 updated_history_disposition(call.history_disposition, CallState::Proceed);
7867 call.statistics_directory_number = statistics_directory_number;
7868 let call = call.clone();
7869 state.active_call_id = Some(call.call_id);
7870 refresh_mwi_lamps(stream, state, protocol).await?;
7871 for message in [
7872 ServerMessage::StopTone {
7873 line_instance: call.line_instance,
7874 call_reference: call.wire_reference,
7875 },
7876 ServerMessage::CallState {
7877 state: CallState::Proceed,
7878 line_instance: call.line_instance,
7879 call_reference: call.wire_reference,
7880 },
7881 ServerMessage::CallInfo {
7882 info,
7883 line_instance: call.line_instance,
7884 call_reference: call.wire_reference,
7885 },
7886 ServerMessage::DisplayPrompt {
7887 timeout_seconds: 0,
7888 text: "Call Proceed".into(),
7889 line_instance: call.line_instance,
7890 call_reference: call.wire_reference,
7891 },
7892 ] {
7893 send_station_ui_message(stream, state, &message).await?;
7894 }
7895 }
7896 CommandAction::PresentOutboundRinging { call_id, info, .. } => {
7897 let statistics_directory_number =
7898 statistics_directory_for_call_info(&info).to_owned();
7899 let call = require_call_mut(state, call_id)?;
7900 call.state = CallState::Proceed;
7901 call.history_disposition =
7902 updated_history_disposition(call.history_disposition, CallState::Proceed);
7903 call.statistics_directory_number = statistics_directory_number;
7904 let call = call.clone();
7905 state.active_call_id = Some(call.call_id);
7906 let key_mode = transfer_key_mode(&call, CallState::RingOut);
7907 state.active_key_mode = key_mode;
7908 refresh_mwi_lamps(stream, state, protocol).await?;
7909 for message in [
7910 ServerMessage::CallState {
7911 state: CallState::Proceed,
7912 line_instance: call.line_instance,
7913 call_reference: call.wire_reference,
7914 },
7915 ServerMessage::DisplayPrompt {
7916 timeout_seconds: 0,
7917 text: "Ring out".into(),
7918 line_instance: call.line_instance,
7919 call_reference: call.wire_reference,
7920 },
7921 ServerMessage::StopTone {
7922 line_instance: call.line_instance,
7923 call_reference: call.wire_reference,
7924 },
7925 ServerMessage::StartTone {
7926 tone: Tone::Alerting,
7927 direction: ToneDirection::User,
7928 line_instance: call.line_instance,
7929 call_reference: call.wire_reference,
7930 },
7931 ServerMessage::SelectSoftKeys {
7932 line_instance: call.line_instance,
7933 call_reference: call.wire_reference,
7934 set: key_mode,
7935 valid_mask: state.device.soft_keys.valid_mask(key_mode),
7936 },
7937 ServerMessage::CallInfo {
7938 info,
7939 line_instance: call.line_instance,
7940 call_reference: call.wire_reference,
7941 },
7942 ] {
7943 send_station_ui_message(stream, state, &message).await?;
7944 }
7945 }
7946 CommandAction::SetCallState {
7947 call_id,
7948 state: call_state,
7949 ..
7950 } => {
7951 let transfer_source_to_clear =
7952 state
7953 .calls_by_id
7954 .get(&call_id)
7955 .and_then(|call| match call.transfer_role {
7956 Some(SessionTransferRole::Source {
7957 consultation_call_id,
7958 }) if call_state != CallState::Transfer => {
7959 Some((consultation_call_id, call.line_instance))
7960 }
7961 _ => None,
7962 });
7963 let call = require_call_mut(state, call_id)?;
7964 call.state = call_state;
7965 call.history_disposition =
7966 updated_history_disposition(call.history_disposition, call_state);
7967 let call = call.clone();
7968 if matches!(
7969 call_state,
7970 CallState::Proceed | CallState::RingOut | CallState::Connected
7971 ) {
7972 remember_last_number(
7973 state,
7974 call.line_instance,
7975 &call.dialed_number,
7976 config,
7977 );
7978 }
7979 prepare_call_state_ui(stream, &call, call_state, protocol).await?;
7980 send_message(
7981 stream,
7982 &ServerMessage::CallState {
7983 state: call_state,
7984 line_instance: call.line_instance,
7985 call_reference: call.wire_reference,
7986 },
7987 protocol,
7988 )
7989 .await?;
7990 finish_call_state_ui(stream, &call, call_state, state.station_context())
7991 .await?;
7992 let set = transfer_key_mode(&call, call_state);
7993 state.active_key_mode = set;
7994 match call_state {
7995 CallState::Connected
7996 | CallState::OffHook
7997 | CallState::Transfer
7998 | CallState::RingOut
7999 | CallState::Proceed
8000 | CallState::IntercomOneWay => {
8001 state.active_call_id = Some(call.call_id);
8002 }
8003 CallState::OnHook
8004 | CallState::Hold
8005 | CallState::HoldYellow
8006 | CallState::HoldRed
8007 if state.active_call_id == Some(call.call_id) =>
8008 {
8009 state.active_call_id = None;
8010 }
8011 _ => {}
8012 }
8013 refresh_mwi_lamps(stream, state, protocol).await?;
8014 send_message(
8015 stream,
8016 &ServerMessage::SelectSoftKeys {
8017 line_instance: call.line_instance,
8018 call_reference: call.wire_reference,
8019 set,
8020 valid_mask: state.device.soft_keys.valid_mask(set),
8021 },
8022 protocol,
8023 )
8024 .await?;
8025 if let Some((consultation_call_id, line_instance)) = transfer_source_to_clear {
8026 if let Some(source) = state.calls_by_id.get_mut(&call_id) {
8027 source.transfer_role = None;
8028 }
8029 if let Some(consultation) = state.calls_by_id.get_mut(&consultation_call_id)
8030 {
8031 consultation.transfer_role = None;
8032 }
8033 send_message(
8034 stream,
8035 &ServerMessage::SetLamp {
8036 stimulus: ButtonType::Transfer,
8037 instance: line_instance,
8038 mode: LampMode::Off,
8039 },
8040 protocol,
8041 )
8042 .await?;
8043 }
8044 }
8045 CommandAction::DisplayPrompt {
8046 call_id,
8047 timeout_seconds,
8048 text,
8049 ..
8050 } => {
8051 let call = require_call(state, call_id)?.clone();
8052 send_station_ui_message(
8053 stream,
8054 state,
8055 &ServerMessage::DisplayPrompt {
8056 timeout_seconds,
8057 text,
8058 line_instance: call.line_instance,
8059 call_reference: call.wire_reference,
8060 },
8061 )
8062 .await?;
8063 }
8064 CommandAction::ClearPrompt { call_id, .. } => {
8065 let call = require_call(state, call_id)?.clone();
8066 send_message(
8067 stream,
8068 &ServerMessage::ClearPrompt {
8069 line_instance: call.line_instance,
8070 call_reference: call.wire_reference,
8071 },
8072 protocol,
8073 )
8074 .await?;
8075 }
8076 CommandAction::SetStatusMessage { message, beep, .. } => {
8077 let frames = status_message_frames(
8078 message,
8079 state.registration.device_type,
8080 &mut state.persistent_status_message,
8081 );
8082 for frame in frames {
8083 send_station_ui_message(stream, state, &frame).await?;
8084 }
8085 if beep {
8086 send_message(
8087 stream,
8088 &ServerMessage::StartTone {
8089 tone: Tone::ZipZip,
8090 direction: ToneDirection::User,
8091 line_instance: 0,
8092 call_reference: 0,
8093 },
8094 protocol,
8095 )
8096 .await?;
8097 }
8098 }
8099 CommandAction::SetMicrophoneMode { enabled, .. } => {
8100 send_message(
8101 stream,
8102 &ServerMessage::SetMicrophoneMode(if enabled {
8103 MicrophoneMode::On
8104 } else {
8105 MicrophoneMode::Off
8106 }),
8107 protocol,
8108 )
8109 .await?;
8110 }
8111 CommandAction::SetRecordingStatus {
8112 call_id, active, ..
8113 } => {
8114 let call = require_call(state, call_id)?.clone();
8115 send_message(
8116 stream,
8117 &ServerMessage::RecordingStatus {
8118 call_reference: call.wire_reference,
8119 active,
8120 },
8121 protocol,
8122 )
8123 .await?;
8124 }
8125 CommandAction::ResetDevice { reset_type, .. } => {
8126 send_message(stream, &ServerMessage::Reset(reset_type), protocol).await?;
8127 }
8128 ringing @ (CommandAction::StartRinging { call_id }
8129 | CommandAction::StopRinging { call_id }) => {
8130 let enabled = matches!(ringing, CommandAction::StartRinging { .. });
8131 let call = require_call(state, call_id)?.clone();
8132 if let Some(stored) = state.calls_by_id.get_mut(&call_id) {
8133 stored.ringer = enabled.then_some(IncomingRing::default());
8134 }
8135 if !enabled && state.ringer_owner != Some(call_id) {
8136 return Ok(false);
8137 }
8138 send_message(
8139 stream,
8140 &ServerMessage::SetRinger {
8141 mode: if enabled {
8142 RingerMode::Inside
8143 } else {
8144 RingerMode::Off
8145 },
8146 duration: RingDuration::Normal,
8147 line_instance: call.line_instance,
8148 call_reference: call.wire_reference,
8149 },
8150 protocol,
8151 )
8152 .await?;
8153 state.ringer_owner = enabled.then_some(call_id);
8154 if !enabled {
8155 let order = *context
8156 .call_answer_order
8157 .read()
8158 .expect("SCCP call-answer-order lock poisoned");
8159 if let Some((call_id, promote)) = incoming_successor(state, call_id, order)
8160 {
8161 present_incoming_successor(stream, state, call_id, promote).await?;
8162 }
8163 }
8164 }
8165 CommandAction::OpenReceiveChannel {
8166 call_id,
8167 purpose,
8168 source,
8169 codec,
8170 packet_ms,
8171 max_frames_per_packet,
8172 dtmf_mode,
8173 audio_processing,
8174 ..
8175 } => {
8176 if purpose == ReceiveChannelPurpose::InboundAnswer {
8177 let call_state = require_call(state, call_id)?.state;
8178 if call_state != CallState::OffHook {
8179 return Err(ServerError::InvalidCallTransaction {
8180 call_id,
8181 operation: "open inbound answer media",
8182 state: call_state,
8183 });
8184 }
8185 }
8186 let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
8187 let request = allocate_media_request_identity(state, call_id)?;
8188 let call = require_call_mut(state, call_id)?;
8189 call.media.requested = true;
8190 call.media.codec = codec;
8191 call.media.packet_ms = packet_ms;
8192 call.media.max_frames_per_packet = max_frames_per_packet;
8193 call.media.receive.telephone_event_payload = telephone_event_payload;
8194 call.media.receive.peer = None;
8195 call.media.receive.state = MediaChannelState::Opening;
8196 call.media.receive.deadline = None;
8197 call.media.receive.request = Some(request);
8198 if call.media.transmit.state == MediaChannelState::Closed {
8199 call.media.transmit.request = None;
8200 }
8201 call.media.coupled_transmit_endpoint = None;
8202 let call = call.clone();
8203 if purpose == ReceiveChannelPurpose::InboundAnswer {
8204 send_message(
8205 stream,
8206 &ServerMessage::CallState {
8207 state: CallState::Connected,
8208 line_instance: call.line_instance,
8209 call_reference: call.wire_reference,
8210 },
8211 protocol,
8212 )
8213 .await?;
8214 }
8215 send_message(
8216 stream,
8217 &ServerMessage::OpenReceiveChannel {
8218 call_reference: call.wire_reference,
8219 passthrough_party_id: request.token().get(),
8220 packet_ms,
8221 codec,
8222 echo_cancellation: audio_processing.echo_cancellation,
8223 telephone_event_payload,
8224 source_address: source
8225 .map(|endpoint| endpoint.address)
8226 .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
8227 source_port: source.map_or(0, |endpoint| endpoint.rtp_port),
8228 encryption: None,
8229 wire: None,
8230 },
8231 protocol,
8232 )
8233 .await?;
8234 require_call_mut(state, call_id)?.media.receive.deadline =
8235 Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
8236 }
8237 CommandAction::OpenMultimediaReceiveChannel {
8238 call_id,
8239 descriptor,
8240 } => {
8241 let call_state = require_call(state, call_id)?.state;
8242 if call_state != CallState::Connected {
8243 return Err(ServerError::InvalidCallTransaction {
8244 call_id,
8245 operation: "open video receive media",
8246 state: call_state,
8247 });
8248 }
8249 validate_multimedia_receive(state, &descriptor)?;
8250 let request = allocate_video_receive_identity(state, call_id)?;
8251 let replacement_close = take_multimedia_receive_close(state, call_id);
8252 let call = require_call_mut(state, call_id)?;
8253 let line_instance = call.line_instance;
8254 let call_reference = CallReference::new(call.wire_reference);
8255 call.video_receive.leg = Some(VideoReceiveLeg {
8256 request,
8257 conference_id: descriptor.conference_id,
8258 codec: descriptor.payload.codec(),
8259 requested_address_type: descriptor.requested_address_type,
8260 state: MediaChannelState::Opening,
8261 deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
8262 });
8263
8264 if let Some(close) = replacement_close {
8265 send_message(stream, &close, protocol).await?;
8266 }
8267 send_message(
8268 stream,
8269 &ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
8270 conference_id: descriptor.conference_id,
8271 passthrough_party_id: request.token().get().into(),
8272 line_instance,
8273 call_reference,
8274 payload: descriptor.payload,
8275 conference_creator: descriptor.conference_creator,
8276 encryption: descriptor.encryption,
8277 stream_passthrough_id: descriptor.stream_passthrough_id,
8278 associated_stream_id: descriptor.associated_stream_id,
8279 source: descriptor.source,
8280 requested_address_type: descriptor.requested_address_type,
8281 }),
8282 protocol,
8283 )
8284 .await?;
8285 }
8286 CommandAction::CloseMultimediaReceiveChannel { call_id } => {
8287 if let Some(close) = take_multimedia_receive_close(state, call_id) {
8288 send_message(stream, &close, protocol).await?;
8289 }
8290 }
8291 CommandAction::StartMultimediaTransmission {
8292 call_id,
8293 descriptor,
8294 } => {
8295 let call_state = require_call(state, call_id)?.state;
8296 if call_state != CallState::Connected {
8297 return Err(ServerError::InvalidCallTransaction {
8298 call_id,
8299 operation: "start video transmit media",
8300 state: call_state,
8301 });
8302 }
8303 validate_multimedia_transmit(state, &descriptor)?;
8304 let request = allocate_video_transmit_identity(state, call_id)?;
8305 let replacement_stop = take_multimedia_transmit_stop(state, call_id);
8306 let call_reference = {
8307 let call = require_call_mut(state, call_id)?;
8308 let call_reference = CallReference::new(call.wire_reference);
8309 call.video_transmit.leg = Some(VideoTransmitLeg {
8310 request,
8311 conference_id: descriptor.conference_id,
8312 codec: descriptor.payload.codec(),
8313 address_type: address_type(descriptor.endpoint.address),
8314 state: MediaChannelState::Opening,
8315 deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
8316 });
8317 call_reference
8318 };
8319
8320 if let Some(stop) = replacement_stop {
8321 send_message(stream, &stop, protocol).await?;
8322 }
8323 send_message(
8324 stream,
8325 &ServerMessage::StartMultimediaTransmission(MultimediaTransmissionStart {
8326 conference_id: descriptor.conference_id,
8327 passthrough_party_id: request.token().get().into(),
8328 endpoint: descriptor.endpoint,
8329 call_reference,
8330 payload: descriptor.payload,
8331 traffic_class: descriptor.traffic_class,
8332 encryption: descriptor.encryption,
8333 stream_passthrough_id: descriptor.stream_passthrough_id,
8334 associated_stream_id: descriptor.associated_stream_id,
8335 }),
8336 protocol,
8337 )
8338 .await?;
8339 }
8340 CommandAction::StopMultimediaTransmission { call_id } => {
8341 if let Some(stop) = take_multimedia_transmit_stop(state, call_id) {
8342 send_message(stream, &stop, protocol).await?;
8343 }
8344 }
8345 flow_action @ (CommandAction::SetMultimediaTransmitBitRate {
8346 call_id,
8347 passthrough_party_id,
8348 maximum_bit_rate,
8349 }
8350 | CommandAction::NotifyMultimediaTransmitBitRate {
8351 call_id,
8352 passthrough_party_id,
8353 maximum_bit_rate,
8354 }) => {
8355 if maximum_bit_rate == 0 {
8356 return Err(ServerError::InvalidMultimediaTransmitControl(
8357 "maximum bit rate must be nonzero",
8358 ));
8359 }
8360 let (conference_id, call_reference) =
8361 multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
8362 let flow = VideoFlowControl {
8363 conference_id,
8364 passthrough_party_id,
8365 call_reference,
8366 maximum_bit_rate,
8367 };
8368 let message = if matches!(
8369 flow_action,
8370 CommandAction::SetMultimediaTransmitBitRate { .. }
8371 ) {
8372 ServerMessage::FlowControlCommand(flow)
8373 } else {
8374 ServerMessage::FlowControlNotify(flow)
8375 };
8376 send_message(stream, &message, protocol).await?;
8377 }
8378 CommandAction::ControlMultimediaTransmission {
8379 call_id,
8380 passthrough_party_id,
8381 control,
8382 } => {
8383 let (conference_id, call_reference) =
8384 multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
8385 let (command, data) = encode_multimedia_transmit_control(control)?;
8386 send_message(
8387 stream,
8388 &ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
8389 conference_id,
8390 passthrough_party_id,
8391 call_reference,
8392 command,
8393 data,
8394 }),
8395 protocol,
8396 )
8397 .await?;
8398 }
8399 CommandAction::OpenOutboundMedia {
8400 call_id,
8401 source,
8402 mut endpoint,
8403 codec,
8404 packet_ms,
8405 max_frames_per_packet,
8406 dtmf_mode,
8407 audio_processing,
8408 traffic_class,
8409 } => {
8410 let call_state = require_call(state, call_id)?.state;
8411 if !matches!(call_state, CallState::Proceed | CallState::RingOut) {
8412 return Err(ServerError::InvalidCallTransaction {
8413 call_id,
8414 operation: "open coupled outbound media",
8415 state: call_state,
8416 });
8417 }
8418 let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
8419 let source_address = source
8420 .map(|source| source.address)
8421 .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
8422 let source_port = source.map_or(0, |source| source.rtp_port);
8423 let request = allocate_media_request_identity(state, call_id)?;
8424 let call = require_call_mut(state, call_id)?;
8425 call.media.requested = true;
8426 call.media.codec = codec;
8427 call.media.packet_ms = packet_ms;
8428 call.media.max_frames_per_packet = max_frames_per_packet;
8429 call.media.receive.telephone_event_payload = telephone_event_payload;
8430 call.media.receive.peer = None;
8431 call.media.receive.state = MediaChannelState::Opening;
8432 call.media.receive.deadline = None;
8433 call.media.receive.request = Some(request);
8434 endpoint.telephone_event_payload = telephone_event_payload;
8435 call.media.transmit.telephone_event_payload = telephone_event_payload;
8436 call.media.transmit.peer = Some(endpoint);
8437 call.media.transmit.state = MediaChannelState::Open;
8438 call.media.transmit.deadline = None;
8439 call.media.transmit.request = Some(request);
8440 call.media.transmit_confirmation = TransmitConfirmation::Inactive;
8441 call.media.coupled_transmit_endpoint = Some(endpoint);
8442 let call = call.clone();
8443 send_message(
8444 stream,
8445 &ServerMessage::OpenReceiveChannel {
8446 call_reference: call.wire_reference,
8447 passthrough_party_id: request.token().get(),
8448 packet_ms,
8449 codec,
8450 echo_cancellation: audio_processing.echo_cancellation,
8451 telephone_event_payload,
8452 source_address,
8453 source_port,
8454 encryption: None,
8455 wire: None,
8456 },
8457 protocol,
8458 )
8459 .await?;
8460 send_message(
8461 stream,
8462 &ServerMessage::StartMediaTransmission {
8463 call_reference: call.wire_reference,
8464 passthrough_party_id: request.token().get(),
8465 endpoint,
8466 silence_suppression: audio_processing.silence_suppression,
8467 traffic_class,
8468 encryption: None,
8469 wire: None,
8470 },
8471 protocol,
8472 )
8473 .await?;
8474 let deadline = Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT;
8475 let call = require_call_mut(state, call_id)?;
8476 call.media.receive.deadline = Some(deadline);
8477 call.media.transmit_confirmation = TransmitConfirmation::Awaiting { deadline };
8478 }
8479 CommandAction::CloseReceiveChannel { call_id, .. } => {
8480 let call = require_call_mut(state, call_id)?;
8481 call.media.coupled_transmit_endpoint = None;
8482 if call.media.receive.state != MediaChannelState::Closed {
8483 call.media.receive.state = MediaChannelState::Closed;
8484 call.media.receive.deadline = None;
8485 let call = call.clone();
8486 send_message(
8487 stream,
8488 &ServerMessage::CloseReceiveChannel(AudioStreamControl {
8489 conference_id: ConferenceId::new(call.wire_reference),
8490 call_reference: CallReference::new(call.wire_reference),
8491 passthrough_party_id: media_request_party_id(
8492 call.media.receive.request,
8493 call.wire_reference,
8494 )
8495 .into(),
8496 port_handling_flag: 0,
8497 }),
8498 protocol,
8499 )
8500 .await?;
8501 }
8502 }
8503 CommandAction::StartMedia {
8504 call_id,
8505 mut endpoint,
8506 dtmf_mode,
8507 audio_processing,
8508 traffic_class,
8509 } => {
8510 let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
8511 let request = {
8512 let call = require_call(state, call_id)?;
8513 if call.media.transmit.request.is_none() {
8514 call.media.receive.request
8515 } else {
8516 None
8517 }
8518 };
8519 let request = match request {
8520 Some(request) => request,
8521 None => allocate_media_request_identity(state, call_id)?,
8522 };
8523 let call = require_call_mut(state, call_id)?;
8524 call.media.requested = true;
8525 call.media.transmit.telephone_event_payload = telephone_event_payload;
8526 endpoint.telephone_event_payload = telephone_event_payload;
8527 call.media.transmit.peer = Some(endpoint);
8528 call.media.transmit.state = MediaChannelState::Open;
8529 call.media.transmit.deadline = None;
8530 call.media.transmit.request = Some(request);
8531 call.media.transmit_confirmation = TransmitConfirmation::Awaiting {
8532 deadline: Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT,
8533 };
8534 call.media.coupled_transmit_endpoint = None;
8535 let call = call.clone();
8536 send_message(
8537 stream,
8538 &ServerMessage::StartMediaTransmission {
8539 call_reference: call.wire_reference,
8540 passthrough_party_id: request.token().get(),
8541 endpoint,
8542 silence_suppression: audio_processing.silence_suppression,
8543 traffic_class,
8544 encryption: None,
8545 wire: None,
8546 },
8547 protocol,
8548 )
8549 .await?;
8550 }
8551 CommandAction::StartMulticastReception {
8552 conference_id,
8553 call_id,
8554 route,
8555 echo_cancellation,
8556 g723_bitrate,
8557 } => {
8558 validate_multicast_route(state, route)?;
8559 let wire_call_reference = require_call(state, call_id)?.wire_reference;
8560 let request = allocate_multicast_request_identity(state)?;
8561 let key = MulticastKey {
8562 conference_id,
8563 call_id,
8564 };
8565 if let Some(stop) = take_multicast_stop(state, key, true) {
8566 send_message(stream, &stop, protocol).await?;
8567 }
8568 send_message(
8569 stream,
8570 &ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
8571 conference_id,
8572 passthrough_party_id: request.token().get().into(),
8573 call_reference: CallReference::new(wire_call_reference),
8574 address: route.address,
8575 port: route.port,
8576 packet_millis: route.packet_millis,
8577 codec: route.codec,
8578 echo_cancellation,
8579 g723_bitrate,
8580 }),
8581 protocol,
8582 )
8583 .await?;
8584 state
8585 .multicast
8586 .entry(key)
8587 .or_insert_with(|| MulticastSession {
8588 wire_call_reference,
8589 receive: None,
8590 transmit: None,
8591 })
8592 .receive = Some(MulticastReceive {
8593 request,
8594 route,
8595 state: MulticastReceiveState::AwaitingAcknowledgement {
8596 deadline: Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT,
8597 },
8598 });
8599 }
8600 CommandAction::StopMulticastReception {
8601 conference_id,
8602 call_id,
8603 } => {
8604 let key = MulticastKey {
8605 conference_id,
8606 call_id,
8607 };
8608 if let Some(stop) = take_multicast_stop(state, key, true) {
8609 send_message(stream, &stop, protocol).await?;
8610 }
8611 }
8612 CommandAction::StartMulticastTransmission {
8613 conference_id,
8614 call_id,
8615 route,
8616 precedence,
8617 silence_suppression,
8618 max_frames_per_packet,
8619 g723_bitrate,
8620 } => {
8621 validate_multicast_route(state, route)?;
8622 let wire_call_reference = require_call(state, call_id)?.wire_reference;
8623 let request = allocate_multicast_request_identity(state)?;
8624 let key = MulticastKey {
8625 conference_id,
8626 call_id,
8627 };
8628 if let Some(stop) = take_multicast_stop(state, key, false) {
8629 send_message(stream, &stop, protocol).await?;
8630 }
8631 send_message(
8632 stream,
8633 &ServerMessage::StartMulticastMediaTransmission(
8634 MulticastMediaTransmission {
8635 conference_id,
8636 passthrough_party_id: request.token().get().into(),
8637 call_reference: CallReference::new(wire_call_reference),
8638 address: route.address,
8639 port: route.port,
8640 packet_millis: route.packet_millis,
8641 codec: route.codec,
8642 precedence,
8643 silence_suppression: silence_suppression.wire_value(),
8644 max_frames_per_packet,
8645 g723_bitrate,
8646 },
8647 ),
8648 protocol,
8649 )
8650 .await?;
8651 state
8652 .multicast
8653 .entry(key)
8654 .or_insert_with(|| MulticastSession {
8655 wire_call_reference,
8656 receive: None,
8657 transmit: None,
8658 })
8659 .transmit = Some(MulticastTransmit { request, route });
8660 context
8661 .event_tx
8662 .send(Event::device(
8663 state.device.id.clone(),
8664 state.generation,
8665 DeviceEventKind::MulticastTransmissionStarted {
8666 conference_id,
8667 call_id,
8668 route,
8669 },
8670 ))
8671 .await
8672 .map_err(|_| ServerError::Stopped)?;
8673 }
8674 CommandAction::StopMulticastTransmission {
8675 conference_id,
8676 call_id,
8677 } => {
8678 let key = MulticastKey {
8679 conference_id,
8680 call_id,
8681 };
8682 if let Some(stop) = take_multicast_stop(state, key, false) {
8683 send_message(stream, &stop, protocol).await?;
8684 }
8685 }
8686 CommandAction::StopMedia { call_id, .. } => {
8687 if let Some(call) = state
8688 .calls_by_id
8689 .get_mut(&call_id)
8690 .filter(|call| call.media.transmit.state != MediaChannelState::Closed)
8691 {
8692 call.media.transmit.state = MediaChannelState::Closed;
8693 call.media.transmit.deadline = None;
8694 call.media.transmit_confirmation = TransmitConfirmation::Inactive;
8695 call.media.coupled_transmit_endpoint = None;
8696 let call = call.clone();
8697 send_message(
8698 stream,
8699 &ServerMessage::StopMediaTransmission(AudioStreamControl {
8700 conference_id: ConferenceId::new(call.wire_reference),
8701 call_reference: CallReference::new(call.wire_reference),
8702 passthrough_party_id: media_request_party_id(
8703 call.media.transmit.request,
8704 call.wire_reference,
8705 )
8706 .into(),
8707 port_handling_flag: 0,
8708 }),
8709 protocol,
8710 )
8711 .await?;
8712 }
8713 }
8714 CommandAction::CloseCall { call_id, .. } => {
8715 if let Some(call) = state.calls_by_id.get(&call_id).cloned() {
8716 let order = *context
8717 .call_answer_order
8718 .read()
8719 .expect("SCCP call-answer-order lock poisoned");
8720 let successor = incoming_successor(state, call_id, order);
8721 let successor_has_ringer = successor.is_some_and(|(call_id, _)| {
8722 state
8723 .calls_by_id
8724 .get(&call_id)
8725 .and_then(|call| incoming_ringer(call.ringer, CallState::RingIn))
8726 .is_some_and(ringer_is_audible)
8727 });
8728 let stop_ringer = !successor_has_ringer
8729 && state.ringer_owner.is_none_or(|owner| owner == call_id);
8730 state.active_key_mode = KeyMode::OnHook;
8731 stop_call_multicast(stream, state, call_id, protocol).await?;
8732 if call.state != CallState::OnHook {
8733 close_call_media_messages(stream, &call, protocol).await?;
8734 close_call_messages(
8735 stream,
8736 &call,
8737 &state.device.soft_keys,
8738 protocol,
8739 context.config.timezone_offset_minutes,
8740 stop_ringer,
8741 )
8742 .await?;
8743 request_connection_statistics(stream, state, &call, context).await?;
8744 }
8745 remove_call(state, call_id);
8746 if state.ringer_owner == Some(call_id) {
8747 state.ringer_owner = None;
8748 }
8749 if let Some((call_id, promote)) = successor {
8750 present_incoming_successor(stream, state, call_id, promote).await?;
8751 }
8752 refresh_mwi_lamps(stream, state, protocol).await?;
8753 } else {
8754 state.cancelled_calls.insert(call_id);
8755 }
8756 }
8757 }
8758 }
8759 }
8760 Ok(false)
8761}
8762
8763async fn send_mwi_lamp(
8764 stream: &mut dyn StationIo,
8765 state: &SessionState,
8766 line_instance: u32,
8767 enabled: bool,
8768 protocol: ProtocolVersion,
8769) -> Result<(), ServerError> {
8770 let mode = projected_mwi_lamp(state.device.ui, state.active_call_id.is_some(), enabled);
8771 send_message(
8772 stream,
8773 &ServerMessage::SetLamp {
8774 stimulus: ButtonType::Voicemail,
8775 instance: line_instance,
8776 mode,
8777 },
8778 protocol,
8779 )
8780 .await
8781}
8782
8783fn projected_mwi_lamp(ui: crate::types::StationUiPolicy, on_call: bool, enabled: bool) -> LampMode {
8784 if enabled && (ui.mwi_on_call || !on_call) {
8785 ui.mwi_lamp_mode
8786 } else {
8787 LampMode::Off
8788 }
8789}
8790
8791fn updated_history_disposition(
8792 current: CallHistoryDisposition,
8793 state: CallState,
8794) -> CallHistoryDisposition {
8795 if current != CallHistoryDisposition::Missed {
8796 return current;
8797 }
8798 match state {
8799 CallState::Connected => CallHistoryDisposition::Received,
8800 CallState::RemoteMultiline => CallHistoryDisposition::Ignore,
8801 _ => current,
8802 }
8803}
8804
8805async fn refresh_mwi_lamps(
8806 stream: &mut dyn StationIo,
8807 state: &SessionState,
8808 protocol: ProtocolVersion,
8809) -> Result<(), ServerError> {
8810 for (&line_instance, &enabled) in &state.mwi_by_line {
8811 send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
8812 }
8813 Ok(())
8814}
8815
8816fn incoming_ringer(
8817 ringer: Option<IncomingRing>,
8818 incoming_state: CallState,
8819) -> Option<IncomingRing> {
8820 ringer.map(|mut ringer| {
8821 if incoming_state == CallState::CallWaiting {
8822 ringer.duration = RingDuration::Single;
8823 if ringer.mode != RingerMode::Urgent {
8824 ringer.mode = RingerMode::Silent;
8825 }
8826 }
8827 ringer
8828 })
8829}
8830
8831const fn ringer_is_audible(ringer: IncomingRing) -> bool {
8832 !matches!(ringer.mode, RingerMode::Off | RingerMode::Silent)
8833}
8834
8835fn incoming_successor(
8836 state: &SessionState,
8837 removed_call_id: CallId,
8838 order: CallSelectionOrder,
8839) -> Option<(CallId, bool)> {
8840 let select = |call_state| {
8841 let candidates = state
8842 .calls_by_id
8843 .values()
8844 .filter(|call| call.call_id != removed_call_id && call.state == call_state);
8845 match order {
8846 CallSelectionOrder::OldestFirst => candidates.min_by_key(|call| call.call_id.0),
8847 CallSelectionOrder::LastFirst => candidates.max_by_key(|call| call.call_id.0),
8848 }
8849 };
8850 if let Some(call) = select(CallState::RingIn) {
8851 return Some((call.call_id, false));
8852 }
8853 let has_active_call = state.calls_by_id.values().any(|call| {
8854 call.call_id != removed_call_id
8855 && matches!(
8856 call.state,
8857 CallState::Connected | CallState::Hold | CallState::HoldYellow | CallState::HoldRed
8858 )
8859 });
8860 (!has_active_call)
8861 .then(|| select(CallState::CallWaiting))
8862 .flatten()
8863 .map(|call| (call.call_id, true))
8864}
8865
8866async fn present_incoming_successor(
8867 stream: &mut dyn StationIo,
8868 state: &mut SessionState,
8869 call_id: CallId,
8870 promote: bool,
8871) -> Result<(), ServerError> {
8872 if promote && let Some(call) = state.calls_by_id.get_mut(&call_id) {
8873 call.state = CallState::RingIn;
8874 }
8875 let call = state
8876 .calls_by_id
8877 .get(&call_id)
8878 .expect("incoming successor came from session state")
8879 .clone();
8880 state.active_call_id = Some(call_id);
8881 state.active_key_mode = KeyMode::RingIn;
8882 if promote {
8883 send_message(
8884 stream,
8885 &ServerMessage::CallState {
8886 state: CallState::RingIn,
8887 line_instance: call.line_instance,
8888 call_reference: call.wire_reference,
8889 },
8890 state.registration.protocol,
8891 )
8892 .await?;
8893 }
8894 send_message(
8895 stream,
8896 &ServerMessage::SetLamp {
8897 stimulus: ButtonType::Line,
8898 instance: call.line_instance,
8899 mode: LampMode::Blink,
8900 },
8901 state.registration.protocol,
8902 )
8903 .await?;
8904 if let Some(ringer) = incoming_ringer(call.ringer, CallState::RingIn) {
8905 let audible = ringer_is_audible(ringer);
8906 if audible || state.ringer_owner.is_none() {
8907 send_message(
8908 stream,
8909 &ServerMessage::SetRinger {
8910 mode: ringer.mode,
8911 duration: ringer.duration,
8912 line_instance: call.line_instance,
8913 call_reference: call.wire_reference,
8914 },
8915 state.registration.protocol,
8916 )
8917 .await?;
8918 }
8919 if audible {
8920 state.ringer_owner = Some(call_id);
8921 }
8922 }
8923 send_message(
8924 stream,
8925 &ServerMessage::SelectSoftKeys {
8926 line_instance: call.line_instance,
8927 call_reference: call.wire_reference,
8928 set: KeyMode::RingIn,
8929 valid_mask: state.device.soft_keys.valid_mask(KeyMode::RingIn),
8930 },
8931 state.registration.protocol,
8932 )
8933 .await?;
8934 Ok(())
8935}
8936
8937async fn request_connection_statistics(
8938 stream: &mut dyn StationIo,
8939 state: &mut SessionState,
8940 call: &SessionCall,
8941 context: &SessionContext,
8942) -> Result<(), ServerError> {
8943 prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
8944 if !call.media.requested
8945 || state.pending_connection_statistics.len() >= MAX_PENDING_CONNECTION_STATISTICS
8946 || state.statistics_references.len() >= MAX_STATISTICS_REFERENCES_PER_SESSION
8947 {
8948 return Ok(());
8949 }
8950 let directory_number = if call.statistics_directory_number.is_empty() {
8951 call.dialed_number.trim()
8952 } else {
8953 call.statistics_directory_number.trim()
8954 };
8955 let maximum = if state.registration.protocol >= ProtocolVersion::V19 {
8956 24
8957 } else {
8958 23
8959 };
8960 if directory_number.is_empty()
8961 || directory_number.len() > maximum
8962 || directory_number.contains(['\0', '\r', '\n'])
8963 {
8964 warn!(
8965 device_id = %state.device.id,
8966 ?call.call_id,
8967 byte_count = directory_number.len(),
8968 "skipping connection-statistics request with unusable directory number"
8969 );
8970 return Ok(());
8971 }
8972 if !state.statistics_references.insert(call.wire_reference) {
8973 warn!(
8974 device_id = %state.device.id,
8975 ?call.call_id,
8976 call_reference = call.wire_reference,
8977 "skipping connection-statistics request for a reused call reference"
8978 );
8979 return Ok(());
8980 }
8981 let request_generation = context
8982 .next_statistics_generation
8983 .fetch_add(1, Ordering::Relaxed);
8984 let processing = StatisticsProcessing::Clear;
8985 let session_generation = state.generation;
8986 state.pending_connection_statistics.insert(
8987 call.wire_reference,
8988 PendingConnectionStatistics {
8989 session_generation,
8990 request_generation,
8991 call_id: call.call_id,
8992 line_instance: call.line_instance,
8993 codec: call.media.codec,
8994 packet_ms: call.media.packet_ms,
8995 max_frames_per_packet: call.media.max_frames_per_packet,
8996 receive_peer: call.media.receive.peer,
8997 transmit_peer: call.media.transmit.peer,
8998 directory_number: directory_number.to_owned(),
8999 processing,
9000 expires_at: Instant::now() + CONNECTION_STATISTICS_TIMEOUT,
9001 },
9002 );
9003 send_message(
9004 stream,
9005 &ServerMessage::ConnectionStatisticsRequest {
9006 directory_number: directory_number.to_owned(),
9007 call_reference: call.wire_reference,
9008 processing,
9009 },
9010 state.registration.protocol,
9011 )
9012 .await
9013}
9014
9015fn statistics_directory_for_call_info(info: &CallInfo) -> &str {
9016 match info.direction {
9017 crate::types::CallDirection::Inbound => &info.calling_number,
9018 crate::types::CallDirection::Outbound => &info.called_number,
9019 }
9020}
9021
9022fn prune_connection_statistics(
9023 pending_statistics: &mut HashMap<u32, PendingConnectionStatistics>,
9024 now: Instant,
9025) {
9026 pending_statistics.retain(|_, pending| pending.expires_at > now);
9027}
9028
9029async fn collect_connection_statistics(
9030 state: &mut SessionState,
9031 statistics: ConnectionStatistics,
9032 context: &SessionContext,
9033) -> Result<(), ServerError> {
9034 prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
9035 let Some(pending) = state
9036 .pending_connection_statistics
9037 .get(&statistics.call_reference)
9038 .cloned()
9039 else {
9040 warn!(
9041 device_id = %state.device.id,
9042 call_reference = statistics.call_reference,
9043 "ignoring unsolicited or expired connection-statistics response"
9044 );
9045 return Ok(());
9046 };
9047 let current_session = context
9048 .sessions
9049 .lock()
9050 .await
9051 .get(&state.device.id)
9052 .is_some_and(|session| session.generation == pending.session_generation);
9053 if !current_session
9054 || pending.session_generation != state.generation
9055 || statistics.processing != pending.processing
9056 || statistics.directory_number != pending.directory_number
9057 {
9058 warn!(
9059 device_id = %state.device.id,
9060 call_reference = statistics.call_reference,
9061 processing = ?statistics.processing,
9062 "ignoring mismatched connection-statistics response"
9063 );
9064 return Ok(());
9065 }
9066 state
9067 .pending_connection_statistics
9068 .remove(&statistics.call_reference);
9069 let snapshot = MediaStatisticsSnapshot {
9070 request_generation: pending.request_generation,
9071 call_id: pending.call_id,
9072 line_instance: LineInstance::new(pending.line_instance),
9073 codec: pending.codec,
9074 packet_ms: pending.packet_ms,
9075 max_frames_per_packet: pending.max_frames_per_packet,
9076 receive_peer: pending.receive_peer,
9077 transmit_peer: pending.transmit_peer,
9078 packets_sent: statistics.packets_sent,
9079 octets_sent: statistics.octets_sent,
9080 packets_received: statistics.packets_received,
9081 octets_received: statistics.octets_received,
9082 packets_lost: statistics.packets_lost,
9083 jitter_millis: statistics.jitter_millis,
9084 latency_millis: statistics.latency_millis,
9085 quality_byte_count: statistics.quality.as_bytes().len(),
9086 };
9087 {
9088 let mut latest = context
9089 .latest_media_statistics
9090 .write()
9091 .expect("SCCP media-statistics lock poisoned");
9092 let replace = latest
9093 .get(&state.device.id)
9094 .is_none_or(|existing| existing.request_generation < snapshot.request_generation);
9095 if !replace {
9096 return Ok(());
9097 }
9098 latest.insert(state.device.id.clone(), snapshot.clone());
9099 }
9100 context
9101 .event_tx
9102 .send(Event::device(
9103 state.device.id.clone(),
9104 state.generation,
9105 DeviceEventKind::ConnectionStatisticsCollected { snapshot },
9106 ))
9107 .await
9108 .map_err(|_| ServerError::Stopped)
9109}
9110
9111fn status_message_frames(
9112 message: HandsetStatusMessage,
9113 device_type: DeviceType,
9114 persistent: &mut bool,
9115) -> Vec<ServerMessage> {
9116 let prompt_for_timed_message = matches!(
9117 device_type,
9118 DeviceType::Cisco6901
9119 | DeviceType::Cisco6921
9120 | DeviceType::Cisco6941
9121 | DeviceType::Cisco6945
9122 | DeviceType::Cisco6961
9123 );
9124 match message {
9125 HandsetStatusMessage::Display {
9126 text,
9127 timeout_seconds,
9128 priority: Some(priority),
9129 } => vec![ServerMessage::DisplayPriorityNotify {
9130 timeout_seconds: u32::from(timeout_seconds),
9131 priority,
9132 text,
9133 }],
9134 HandsetStatusMessage::Clear {
9135 priority: Some(priority),
9136 } => vec![ServerMessage::ClearPriorityNotify { priority }],
9137 HandsetStatusMessage::Display {
9138 text,
9139 timeout_seconds,
9140 priority: None,
9141 } if timeout_seconds == 0 || prompt_for_timed_message => {
9142 if timeout_seconds == 0 {
9143 *persistent = true;
9144 }
9145 vec![ServerMessage::DisplayPrompt {
9146 timeout_seconds: u32::from(timeout_seconds),
9147 text,
9148 line_instance: 0,
9149 call_reference: 0,
9150 }]
9151 }
9152 HandsetStatusMessage::Display {
9153 text,
9154 timeout_seconds,
9155 priority: None,
9156 } => vec![ServerMessage::DisplayPriorityNotify {
9157 timeout_seconds: u32::from(timeout_seconds),
9158 priority: NotificationPriority::Timed,
9159 text,
9160 }],
9161 HandsetStatusMessage::Clear { priority: None } => {
9162 let clear_prompt = std::mem::take(persistent) || prompt_for_timed_message;
9163 let mut frames = Vec::with_capacity(2);
9164 if clear_prompt {
9165 frames.push(ServerMessage::ClearPrompt {
9166 line_instance: 0,
9167 call_reference: 0,
9168 });
9169 }
9170 if !prompt_for_timed_message {
9171 frames.push(ServerMessage::ClearPriorityNotify {
9172 priority: NotificationPriority::Timed,
9173 });
9174 }
9175 frames
9176 }
9177 }
9178}
9179
9180async fn send_message(
9181 stream: &mut dyn StationIo,
9182 message: &ServerMessage,
9183 session: impl Into<StationSessionContext>,
9184) -> Result<(), ServerError> {
9185 stream
9186 .write_all(&message.encode_for_session(session.into())?)
9187 .await?;
9188 Ok(())
9189}
9190
9191async fn send_station_ui_message(
9192 stream: &mut dyn StationIo,
9193 state: &SessionState,
9194 message: &ServerMessage,
9195) -> Result<(), ServerError> {
9196 let session = state.station_context();
9197 let bytes = if state.features.contains(PhoneFeatures::UTF8) {
9198 message.encode_for_session(session)?
9199 } else {
9200 message.encode_for_legacy_session(session, state.device.ui.legacy_code_page)?
9201 };
9202 stream.write_all(&bytes).await?;
9203 Ok(())
9204}
9205
9206async fn begin_phone_call_ui(
9207 stream: &mut dyn StationIo,
9208 call: &SessionCall,
9209 device: &DeviceDefinition,
9210 session: StationSessionContext,
9211) -> Result<(), ServerError> {
9212 begin_phone_call_ui_with_key_mode(stream, call, device, KeyMode::OffHook, session).await
9213}
9214
9215async fn begin_phone_call_ui_with_key_mode(
9216 stream: &mut dyn StationIo,
9217 call: &SessionCall,
9218 device: &DeviceDefinition,
9219 key_mode: KeyMode,
9220 session: StationSessionContext,
9221) -> Result<(), ServerError> {
9222 let initial_tone = device
9223 .line(call.line_instance)
9224 .map_or(Tone::InsideDial, |line| line.initial_tone);
9225 send_message(
9226 stream,
9227 &ServerMessage::SetSpeakerMode(SpeakerMode::On),
9228 session,
9229 )
9230 .await?;
9231 send_message(
9232 stream,
9233 &ServerMessage::SetLamp {
9234 stimulus: ButtonType::Line,
9235 instance: call.line_instance,
9236 mode: LampMode::On,
9237 },
9238 session,
9239 )
9240 .await?;
9241 send_message(
9242 stream,
9243 &ServerMessage::CallState {
9244 state: CallState::OffHook,
9245 line_instance: call.line_instance,
9246 call_reference: call.wire_reference,
9247 },
9248 session,
9249 )
9250 .await?;
9251 send_message(
9252 stream,
9253 &ServerMessage::ActivateCallPlane {
9254 line_instance: call.line_instance,
9255 },
9256 session,
9257 )
9258 .await?;
9259 send_message(
9260 stream,
9261 &ServerMessage::DisplayPrompt {
9262 timeout_seconds: 0,
9263 text: "Enter number".into(),
9264 line_instance: call.line_instance,
9265 call_reference: call.wire_reference,
9266 },
9267 session,
9268 )
9269 .await?;
9270 send_message(
9271 stream,
9272 &ServerMessage::StartTone {
9273 tone: initial_tone,
9274 direction: ToneDirection::User,
9275 line_instance: call.line_instance,
9276 call_reference: call.wire_reference,
9277 },
9278 session,
9279 )
9280 .await?;
9281 send_message(
9282 stream,
9283 &ServerMessage::SelectSoftKeys {
9284 line_instance: call.line_instance,
9285 call_reference: call.wire_reference,
9286 set: key_mode,
9287 valid_mask: device.soft_keys.valid_mask(key_mode),
9288 },
9289 session,
9290 )
9291 .await
9292}
9293
9294async fn begin_answer_ui(
9295 stream: &mut dyn StationIo,
9296 call: &SessionCall,
9297 protocol: ProtocolVersion,
9298) -> Result<(), ServerError> {
9299 send_message(
9300 stream,
9301 &ServerMessage::SetRinger {
9302 mode: RingerMode::Off,
9303 duration: RingDuration::Normal,
9304 line_instance: call.line_instance,
9305 call_reference: call.wire_reference,
9306 },
9307 protocol,
9308 )
9309 .await?;
9310 send_message(
9311 stream,
9312 &ServerMessage::CallState {
9313 state: CallState::OffHook,
9314 line_instance: call.line_instance,
9315 call_reference: call.wire_reference,
9316 },
9317 protocol,
9318 )
9319 .await?;
9320 send_message(
9321 stream,
9322 &ServerMessage::ActivateCallPlane {
9323 line_instance: call.line_instance,
9324 },
9325 protocol,
9326 )
9327 .await?;
9328 send_message(
9329 stream,
9330 &ServerMessage::StopTone {
9331 line_instance: call.line_instance,
9332 call_reference: call.wire_reference,
9333 },
9334 protocol,
9335 )
9336 .await?;
9337 send_message(
9338 stream,
9339 &ServerMessage::SetLamp {
9340 stimulus: ButtonType::Line,
9341 instance: call.line_instance,
9342 mode: LampMode::On,
9343 },
9344 protocol,
9345 )
9346 .await?;
9347 Ok(())
9348}
9349
9350async fn prepare_call_state_ui(
9351 stream: &mut dyn StationIo,
9352 call: &SessionCall,
9353 state: CallState,
9354 protocol: ProtocolVersion,
9355) -> Result<(), ServerError> {
9356 match state {
9357 CallState::Connected => {
9358 send_message(
9359 stream,
9360 &ServerMessage::SetRinger {
9361 mode: RingerMode::Off,
9362 duration: RingDuration::Normal,
9363 line_instance: call.line_instance,
9364 call_reference: call.wire_reference,
9365 },
9366 protocol,
9367 )
9368 .await?;
9369 send_message(
9370 stream,
9371 &ServerMessage::SetSpeakerMode(SpeakerMode::On),
9372 protocol,
9373 )
9374 .await?;
9375 send_message(
9376 stream,
9377 &ServerMessage::StopTone {
9378 line_instance: call.line_instance,
9379 call_reference: call.wire_reference,
9380 },
9381 protocol,
9382 )
9383 .await?;
9384 send_message(
9385 stream,
9386 &ServerMessage::SetLamp {
9387 stimulus: ButtonType::Line,
9388 instance: call.line_instance,
9389 mode: LampMode::On,
9390 },
9391 protocol,
9392 )
9393 .await?;
9394 }
9395 CallState::RemoteMultiline => {
9396 send_message(
9397 stream,
9398 &ServerMessage::SetRinger {
9399 mode: RingerMode::Off,
9400 duration: RingDuration::Normal,
9401 line_instance: call.line_instance,
9402 call_reference: call.wire_reference,
9403 },
9404 protocol,
9405 )
9406 .await?;
9407 send_message(
9408 stream,
9409 &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
9410 protocol,
9411 )
9412 .await?;
9413 send_message(
9414 stream,
9415 &ServerMessage::SetLamp {
9416 stimulus: ButtonType::Line,
9417 instance: call.line_instance,
9418 mode: LampMode::On,
9419 },
9420 protocol,
9421 )
9422 .await?;
9423 }
9424 CallState::OnHook => {
9425 send_message(
9426 stream,
9427 &ServerMessage::SetRinger {
9428 mode: RingerMode::Off,
9429 duration: RingDuration::Normal,
9430 line_instance: call.line_instance,
9431 call_reference: call.wire_reference,
9432 },
9433 protocol,
9434 )
9435 .await?;
9436 }
9437 CallState::Hold | CallState::HoldYellow | CallState::HoldRed => {
9438 send_message(
9439 stream,
9440 &ServerMessage::SetLamp {
9441 stimulus: ButtonType::Line,
9442 instance: call.line_instance,
9443 mode: LampMode::Wink,
9444 },
9445 protocol,
9446 )
9447 .await?;
9448 }
9449 CallState::RingOut | CallState::Proceed => {
9450 send_message(
9451 stream,
9452 &ServerMessage::SetLamp {
9453 stimulus: ButtonType::Line,
9454 instance: call.line_instance,
9455 mode: LampMode::Blink,
9456 },
9457 protocol,
9458 )
9459 .await?;
9460 }
9461 _ => {}
9462 }
9463 Ok(())
9464}
9465
9466async fn finish_call_state_ui(
9467 stream: &mut dyn StationIo,
9468 call: &SessionCall,
9469 state: CallState,
9470 session: StationSessionContext,
9471) -> Result<(), ServerError> {
9472 let prompt = match state {
9473 CallState::Connected => Some("Connected"),
9474 CallState::Hold | CallState::HoldYellow | CallState::HoldRed => Some("Hold"),
9475 CallState::RingOut => Some("Ring out"),
9476 CallState::Proceed => Some("Call proceeding"),
9477 CallState::Busy => Some("Busy"),
9478 CallState::Congestion => Some("Network congestion"),
9479 CallState::InvalidNumber => Some("Unknown number"),
9480 _ => None,
9481 };
9482 if state == CallState::Connected {
9483 send_message(
9484 stream,
9485 &ServerMessage::ActivateCallPlane {
9486 line_instance: call.line_instance,
9487 },
9488 session,
9489 )
9490 .await?;
9491 } else if matches!(
9492 state,
9493 CallState::Hold | CallState::HoldYellow | CallState::HoldRed
9494 ) {
9495 send_message(
9496 stream,
9497 &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
9498 session,
9499 )
9500 .await?;
9501 }
9502 if let Some(text) = prompt {
9503 send_message(
9504 stream,
9505 &ServerMessage::DisplayPrompt {
9506 timeout_seconds: 0,
9507 text: text.into(),
9508 line_instance: call.line_instance,
9509 call_reference: call.wire_reference,
9510 },
9511 session,
9512 )
9513 .await?;
9514 }
9515 Ok(())
9516}
9517
9518fn normalize_line(state: &SessionState, requested: u32) -> u32 {
9519 if requested != 0 && state.device.line(requested).is_some() {
9520 requested
9521 } else {
9522 state.device.first_line().map_or(1, |line| line.instance)
9523 }
9524}
9525
9526fn ensure_phone_call(
9527 state: &mut SessionState,
9528 wire_reference: u32,
9529 line_instance: u32,
9530 next: &AtomicU64,
9531) -> SessionCall {
9532 let reusable = if wire_reference == 0 {
9533 state
9534 .calls_by_id
9535 .values()
9536 .filter(|call| call.state != CallState::OnHook)
9537 .max_by_key(|call| call.call_id.0)
9538 } else {
9539 find_call(state, wire_reference).filter(|call| call.state != CallState::OnHook)
9540 };
9541 if let Some(call) = reusable {
9542 return call.clone();
9543 }
9544 let mut call = reserve_phone_call(state, line_instance, next);
9545 if wire_reference != 0
9546 && wire_reference != call.wire_reference
9547 && !state.statistics_references.contains(&wire_reference)
9548 {
9549 state.calls_by_wire.remove(&call.wire_reference);
9550 call.wire_reference = wire_reference;
9551 state.calls_by_wire.insert(wire_reference, call.call_id);
9552 state.calls_by_id.insert(call.call_id, call.clone());
9553 }
9554 call
9555}
9556
9557fn reserve_phone_call(
9558 state: &mut SessionState,
9559 line_instance: u32,
9560 next: &AtomicU64,
9561) -> SessionCall {
9562 let call_id = CallId(next.fetch_add(1, Ordering::Relaxed));
9563 insert_call(
9564 state,
9565 call_id,
9566 line_instance,
9567 Codec::Pcmu,
9568 CallState::OffHook,
9569 )
9570}
9571
9572fn insert_call(
9573 state: &mut SessionState,
9574 call_id: CallId,
9575 line_instance: u32,
9576 codec: Codec,
9577 call_state: CallState,
9578) -> SessionCall {
9579 let mut wire_reference = (call_id.0 as u32).max(1);
9580 while state.calls_by_wire.contains_key(&wire_reference)
9581 || state.statistics_references.contains(&wire_reference)
9582 {
9583 wire_reference = wire_reference.wrapping_add(1).max(1);
9584 }
9585 let call = SessionCall {
9586 call_id,
9587 wire_reference,
9588 line_instance,
9589 media: CallMedia::new(codec),
9590 video_receive: VideoReceive::default(),
9591 video_transmit: VideoTransmit::default(),
9592 state: call_state,
9593 ringer: None,
9594 history_disposition: if matches!(call_state, CallState::RingIn | CallState::CallWaiting) {
9595 CallHistoryDisposition::Missed
9596 } else {
9597 CallHistoryDisposition::Placed
9598 },
9599 dialed_number: String::new(),
9600 statistics_directory_number: String::new(),
9601 transfer_role: None,
9602 };
9603 state.calls_by_wire.insert(wire_reference, call_id);
9604 state.calls_by_id.insert(call_id, call.clone());
9605 call
9606}
9607
9608fn find_call(state: &SessionState, wire_reference: u32) -> Option<&SessionCall> {
9609 if wire_reference != 0 {
9610 state
9611 .calls_by_wire
9612 .get(&wire_reference)
9613 .and_then(|id| state.calls_by_id.get(id))
9614 } else {
9615 state
9616 .active_call_id
9617 .and_then(|call_id| state.calls_by_id.get(&call_id))
9618 .or_else(|| {
9619 (state.calls_by_id.len() == 1)
9620 .then(|| state.calls_by_id.values().next())
9621 .flatten()
9622 })
9623 }
9624}
9625
9626fn find_answer_call(
9627 state: &SessionState,
9628 wire_reference: u32,
9629 line_instance: u32,
9630 order: CallSelectionOrder,
9631) -> Option<&SessionCall> {
9632 let matches_line = |call: &&SessionCall| {
9633 matches!(call.state, CallState::RingIn | CallState::CallWaiting)
9634 && (line_instance == 0 || call.line_instance == line_instance)
9635 };
9636 if wire_reference != 0 {
9637 return state
9638 .calls_by_wire
9639 .get(&wire_reference)
9640 .and_then(|call_id| state.calls_by_id.get(call_id))
9641 .filter(matches_line);
9642 }
9643 if let Some(active) = state
9644 .active_call_id
9645 .and_then(|call_id| state.calls_by_id.get(&call_id))
9646 .filter(matches_line)
9647 {
9648 return Some(active);
9649 }
9650 let candidates = state.calls_by_id.values().filter(matches_line);
9651 match order {
9652 CallSelectionOrder::OldestFirst => candidates.min_by_key(|call| call.call_id.0),
9653 CallSelectionOrder::LastFirst => candidates.max_by_key(|call| call.call_id.0),
9654 }
9655}
9656
9657fn find_receive_media_call_id(
9658 state: &SessionState,
9659 wire_reference: u32,
9660 passthrough_party_id: u32,
9661) -> Option<CallId> {
9662 find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
9663 call.media.receive.request
9664 })
9665}
9666
9667fn find_multicast_receive_key(
9668 state: &SessionState,
9669 wire_reference: u32,
9670 passthrough_party_id: u32,
9671) -> Option<MulticastKey> {
9672 state.multicast.iter().find_map(|(key, session)| {
9673 session.receive.as_ref().and_then(|receive| {
9674 (matches!(
9675 receive.state,
9676 MulticastReceiveState::AwaitingAcknowledgement { .. }
9677 ) && session.wire_call_reference == wire_reference
9678 && receive.request.token().get() == passthrough_party_id)
9679 .then_some(*key)
9680 })
9681 })
9682}
9683
9684fn find_multicast_transmit_key(
9685 state: &SessionState,
9686 conference_id: u32,
9687 wire_reference: u32,
9688 passthrough_party_id: u32,
9689 address: IpAddr,
9690 port: u16,
9691) -> Option<MulticastKey> {
9692 state.multicast.iter().find_map(|(key, session)| {
9693 session.transmit.as_ref().and_then(|transmit| {
9694 (key.conference_id.get() == conference_id
9695 && session.wire_call_reference == wire_reference
9696 && transmit.request.token().get() == passthrough_party_id
9697 && canonical_ip_address(transmit.route.address) == canonical_ip_address(address)
9698 && transmit.route.port == port)
9699 .then_some(*key)
9700 })
9701 })
9702}
9703
9704fn find_transmit_media_call_id(
9705 state: &SessionState,
9706 conference_id: u32,
9707 wire_reference: u32,
9708 passthrough_party_id: u32,
9709) -> Option<CallId> {
9710 find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
9711 call.media.transmit.request
9712 })
9713 .filter(|call_id| {
9714 state
9715 .calls_by_id
9716 .get(call_id)
9717 .is_some_and(|call| conference_id == 0 || conference_id == call.wire_reference)
9718 })
9719}
9720
9721fn find_media_call_id(
9722 state: &SessionState,
9723 wire_reference: u32,
9724 passthrough_party_id: u32,
9725 request: impl Fn(&SessionCall) -> Option<MediaRequestIdentity>,
9726) -> Option<CallId> {
9727 state
9728 .calls_by_id
9729 .values()
9730 .find(|call| {
9731 request(call).is_some_and(|identity| {
9732 identity.accepts_ack(passthrough_party_id, wire_reference, call.wire_reference)
9733 })
9734 })
9735 .map(|call| call.call_id)
9736}
9737
9738fn require_call(state: &SessionState, call_id: CallId) -> Result<&SessionCall, ServerError> {
9739 state
9740 .calls_by_id
9741 .get(&call_id)
9742 .ok_or(ServerError::UnknownCall(call_id))
9743}
9744
9745fn require_call_mut(
9746 state: &mut SessionState,
9747 call_id: CallId,
9748) -> Result<&mut SessionCall, ServerError> {
9749 state
9750 .calls_by_id
9751 .get_mut(&call_id)
9752 .ok_or(ServerError::UnknownCall(call_id))
9753}
9754
9755fn address_matches_type(address: IpAddr, requested: IpAddressType) -> bool {
9756 match requested {
9757 IpAddressType::Ipv4 => address.is_ipv4(),
9758 IpAddressType::Ipv6 => address.is_ipv6(),
9759 IpAddressType::Ipv4AndIpv6 => true,
9760 IpAddressType::Invalid | IpAddressType::Unknown(_) => false,
9761 }
9762}
9763
9764fn address_type(address: IpAddr) -> IpAddressType {
9765 if address.is_ipv4() {
9766 IpAddressType::Ipv4
9767 } else {
9768 IpAddressType::Ipv6
9769 }
9770}
9771
9772fn endpoint_is_usable(endpoint: MediaEndpointAddress) -> bool {
9773 endpoint.port != 0 && !endpoint.address.is_unspecified() && !endpoint.address.is_multicast()
9774}
9775
9776fn capability_supports_address(
9777 advertised: Option<IpAddressType>,
9778 requested: IpAddressType,
9779) -> bool {
9780 match advertised {
9781 None => requested == IpAddressType::Ipv4,
9782 Some(IpAddressType::Ipv4AndIpv6) => true,
9783 Some(address_type) => address_type == requested,
9784 }
9785}
9786
9787fn validate_multimedia_receive_descriptor(
9788 descriptor: &MultimediaReceiveDescriptor,
9789) -> Result<(), ServerError> {
9790 if !descriptor
9791 .payload
9792 .is_direction(MultimediaPayloadDirection::Receive)
9793 {
9794 return Err(ServerError::InvalidMultimediaReceive(
9795 "payload was not decoded from a receive message",
9796 ));
9797 }
9798 if descriptor.payload.codec().kind() != CodecKind::Video {
9799 return Err(ServerError::InvalidMultimediaReceive("codec is not video"));
9800 }
9801 if !address_matches_type(descriptor.source.address, descriptor.requested_address_type) {
9802 return Err(ServerError::InvalidMultimediaReceive(
9803 "source address does not match the requested address type",
9804 ));
9805 }
9806 if descriptor.source.address.is_multicast() {
9807 return Err(ServerError::InvalidMultimediaReceive(
9808 "source address must not be multicast",
9809 ));
9810 }
9811 Ok(())
9812}
9813
9814fn validate_multimedia_receive(
9815 state: &SessionState,
9816 descriptor: &MultimediaReceiveDescriptor,
9817) -> Result<(), ServerError> {
9818 validate_multimedia_receive_descriptor(descriptor)?;
9819
9820 if !descriptor.payload.is_valid_for(
9821 MultimediaPayloadDirection::Receive,
9822 state.registration.protocol,
9823 ) {
9824 return Err(ServerError::InvalidMultimediaReceive(
9825 "payload protocol does not match the live session",
9826 ));
9827 }
9828
9829 match state.registration.protocol {
9830 protocol if protocol < ProtocolVersion::V12 => {
9831 if descriptor.source
9832 != (MediaEndpointAddress {
9833 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9834 port: 0,
9835 })
9836 || descriptor.requested_address_type != IpAddressType::Ipv4
9837 {
9838 return Err(ServerError::InvalidMultimediaReceive(
9839 "this protocol version cannot carry a source endpoint",
9840 ));
9841 }
9842 }
9843 protocol
9844 if protocol < ProtocolVersion::V17
9845 && (!descriptor.source.address.is_ipv4()
9846 || descriptor.requested_address_type != IpAddressType::Ipv4) =>
9847 {
9848 return Err(ServerError::InvalidMultimediaReceive(
9849 "this protocol version carries only IPv4 video endpoints",
9850 ));
9851 }
9852 _ => {}
9853 }
9854
9855 let supported = state.media_capabilities.video().iter().any(|capability| {
9856 let encryption_supported = descriptor.encryption.is_none()
9857 || capability.encryption_capability == Some(EncryptionCapability::Capable);
9858 capability.codec == descriptor.payload.codec()
9859 && capability.direction.contains(ReceiveTransmit::RECEIVE)
9860 && capability_supports_address(
9861 capability.address_type,
9862 descriptor.requested_address_type,
9863 )
9864 && encryption_supported
9865 });
9866 supported
9867 .then_some(())
9868 .ok_or(ServerError::UnsupportedMultimediaReceive)
9869}
9870
9871fn validate_multimedia_transmit_descriptor(
9872 descriptor: &MultimediaTransmitDescriptor,
9873) -> Result<(), ServerError> {
9874 if !descriptor
9875 .payload
9876 .is_direction(MultimediaPayloadDirection::Transmit)
9877 {
9878 return Err(ServerError::InvalidMultimediaTransmit(
9879 "payload was not decoded from a transmit message",
9880 ));
9881 }
9882 if descriptor.payload.codec().kind() != CodecKind::Video {
9883 return Err(ServerError::InvalidMultimediaTransmit("codec is not video"));
9884 }
9885 if !endpoint_is_usable(descriptor.endpoint) {
9886 return Err(ServerError::InvalidMultimediaTransmit(
9887 "destination endpoint must be unicast and nonzero",
9888 ));
9889 }
9890 Ok(())
9891}
9892
9893fn validate_multimedia_transmit(
9894 state: &SessionState,
9895 descriptor: &MultimediaTransmitDescriptor,
9896) -> Result<(), ServerError> {
9897 validate_multimedia_transmit_descriptor(descriptor)?;
9898 if !descriptor.payload.is_valid_for(
9899 MultimediaPayloadDirection::Transmit,
9900 state.registration.protocol,
9901 ) {
9902 return Err(ServerError::InvalidMultimediaTransmit(
9903 "payload protocol does not match the live session",
9904 ));
9905 }
9906 if state.registration.protocol < ProtocolVersion::V17 && descriptor.endpoint.address.is_ipv6() {
9907 return Err(ServerError::InvalidMultimediaTransmit(
9908 "this protocol version carries only IPv4 video endpoints",
9909 ));
9910 }
9911 let requested_address = address_type(descriptor.endpoint.address);
9912 let supported = state.media_capabilities.video().iter().any(|capability| {
9913 let encryption_supported = descriptor.encryption.is_none()
9914 || capability.encryption_capability == Some(EncryptionCapability::Capable);
9915 capability.codec == descriptor.payload.codec()
9916 && capability.direction.contains(ReceiveTransmit::TRANSMIT)
9917 && capability_supports_address(capability.address_type, requested_address)
9918 && encryption_supported
9919 });
9920 supported
9921 .then_some(())
9922 .ok_or(ServerError::UnsupportedMultimediaTransmit)
9923}
9924
9925fn allocate_video_receive_identity(
9926 state: &mut SessionState,
9927 call_id: CallId,
9928) -> Result<MediaRequestIdentity, ServerError> {
9929 let generation = require_call(state, call_id)?
9930 .video_receive
9931 .generation
9932 .checked_add(1)
9933 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
9934 let token = state
9935 .next_media_token
9936 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
9937 let request = MediaRequestIdentity::new(generation, token)
9938 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
9939 state.next_media_token = token.checked_next();
9940 require_call_mut(state, call_id)?.video_receive.generation = generation;
9941 Ok(request)
9942}
9943
9944fn multimedia_receive_close_message(call: &SessionCall, leg: &VideoReceiveLeg) -> ServerMessage {
9945 ServerMessage::CloseMultimediaReceiveChannel(MultimediaStreamControl {
9946 conference_id: leg.conference_id,
9947 passthrough_party_id: leg.request.token().get().into(),
9948 call_reference: CallReference::new(call.wire_reference),
9949 port_handling_flag: 0,
9950 })
9951}
9952
9953fn take_multimedia_receive_close(
9954 state: &mut SessionState,
9955 call_id: CallId,
9956) -> Option<ServerMessage> {
9957 let call = state.calls_by_id.get_mut(&call_id)?;
9958 let leg = call.video_receive.leg.take()?;
9959 Some(multimedia_receive_close_message(call, &leg))
9960}
9961
9962fn take_all_multimedia_receive_closes(state: &mut SessionState) -> Vec<ServerMessage> {
9963 let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
9964 call_ids.sort_unstable_by_key(|call_id| call_id.get());
9965 call_ids
9966 .into_iter()
9967 .filter_map(|call_id| take_multimedia_receive_close(state, call_id))
9968 .collect()
9969}
9970
9971fn expire_multimedia_receive_acknowledgements(
9972 state: &mut SessionState,
9973 now: Instant,
9974) -> Vec<ExpiredVideoReceive> {
9975 let mut call_ids = state
9976 .calls_by_id
9977 .iter()
9978 .filter_map(|(&call_id, call)| {
9979 call.video_receive.leg.as_ref().and_then(|leg| {
9980 (leg.state == MediaChannelState::Opening
9981 && leg.deadline.is_some_and(|deadline| deadline <= now))
9982 .then_some(call_id)
9983 })
9984 })
9985 .collect::<Vec<_>>();
9986 call_ids.sort_unstable_by_key(|call_id| call_id.get());
9987 call_ids
9988 .into_iter()
9989 .filter_map(|call_id| {
9990 let leg = state
9991 .calls_by_id
9992 .get(&call_id)?
9993 .video_receive
9994 .leg
9995 .as_ref()?;
9996 let codec = leg.codec;
9997 let passthrough_party_id = leg.request.token().get().into();
9998 take_multimedia_receive_close(state, call_id).map(|close| ExpiredVideoReceive {
9999 call_id,
10000 codec,
10001 passthrough_party_id,
10002 close,
10003 })
10004 })
10005 .collect()
10006}
10007
10008fn allocate_video_transmit_identity(
10009 state: &mut SessionState,
10010 call_id: CallId,
10011) -> Result<MediaRequestIdentity, ServerError> {
10012 let generation = require_call(state, call_id)?
10013 .video_transmit
10014 .generation
10015 .checked_add(1)
10016 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10017 let token = state
10018 .next_media_token
10019 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10020 let request = MediaRequestIdentity::new(generation, token)
10021 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10022 state.next_media_token = token.checked_next();
10023 require_call_mut(state, call_id)?.video_transmit.generation = generation;
10024 Ok(request)
10025}
10026
10027fn multimedia_transmit_control_identity(
10028 state: &SessionState,
10029 call_id: CallId,
10030 passthrough_party_id: PassthroughPartyId,
10031) -> Result<(ConferenceId, CallReference), ServerError> {
10032 let call = require_call(state, call_id)?;
10033 if call.state != CallState::Connected {
10034 return Err(ServerError::InvalidCallTransaction {
10035 call_id,
10036 operation: "control video transmit media",
10037 state: call.state,
10038 });
10039 }
10040 let leg = call
10041 .video_transmit
10042 .leg
10043 .as_ref()
10044 .filter(|leg| {
10045 leg.state == MediaChannelState::Open
10046 && leg.request.token().get() == passthrough_party_id.get()
10047 })
10048 .ok_or(ServerError::StaleMultimediaTransmitControl {
10049 call_id,
10050 passthrough_party_id,
10051 })?;
10052 Ok((leg.conference_id, CallReference::new(call.wire_reference)))
10053}
10054
10055fn encode_multimedia_transmit_control(
10056 control: MultimediaTransmitControl,
10057) -> Result<(MiscCommandType, BoundedBytes<36>), ServerError> {
10058 let (command, words) = match control {
10059 MultimediaTransmitControl::FreezePicture => {
10060 (MiscCommandType::VideoFreezePicture, Vec::new())
10061 }
10062 MultimediaTransmitControl::FastPictureUpdate {
10063 first_gob,
10064 gob_count,
10065 } => (
10066 MiscCommandType::VideoFastUpdatePicture,
10067 vec![first_gob, gob_count],
10068 ),
10069 MultimediaTransmitControl::FastGobUpdate {
10070 first_gob,
10071 gob_count,
10072 } => (
10073 MiscCommandType::VideoFastUpdateGob,
10074 vec![first_gob, gob_count],
10075 ),
10076 MultimediaTransmitControl::FastMacroblockUpdate {
10077 first_gob,
10078 first_macroblock,
10079 macroblock_count,
10080 } => (
10081 MiscCommandType::VideoFastUpdateMacroblock,
10082 vec![first_gob, first_macroblock, macroblock_count],
10083 ),
10084 MultimediaTransmitControl::LostPicture {
10085 picture_number,
10086 long_term_picture_index,
10087 } => (
10088 MiscCommandType::LostPicture,
10089 vec![picture_number, long_term_picture_index],
10090 ),
10091 MultimediaTransmitControl::LostPartialPicture {
10092 picture_number,
10093 long_term_picture_index,
10094 first_macroblock,
10095 macroblock_count,
10096 } => (
10097 MiscCommandType::LostPartialPicture,
10098 vec![
10099 picture_number,
10100 long_term_picture_index,
10101 first_macroblock,
10102 macroblock_count,
10103 ],
10104 ),
10105 MultimediaTransmitControl::RecoveryReferencePicture { pictures } => {
10106 let words =
10107 std::iter::once(pictures.as_slice().len() as u32)
10108 .chain(pictures.as_slice().iter().flat_map(|picture| {
10109 [picture.picture_number, picture.long_term_picture_index]
10110 }))
10111 .collect();
10112 (MiscCommandType::RecoveryReferencePicture, words)
10113 }
10114 MultimediaTransmitControl::TemporalSpatialTradeoff { value } => {
10115 (MiscCommandType::TemporalSpatialTradeoff, vec![value])
10116 }
10117 };
10118 let data = words
10119 .into_iter()
10120 .flat_map(u32::to_le_bytes)
10121 .collect::<Vec<_>>();
10122 let data = BoundedBytes::new(data.into_boxed_slice()).map_err(|_| {
10123 ServerError::InvalidMultimediaTransmitControl("parameter area exceeds 36 bytes")
10124 })?;
10125 Ok((command, data))
10126}
10127
10128fn multimedia_transmit_stop_message(call: &SessionCall, leg: &VideoTransmitLeg) -> ServerMessage {
10129 ServerMessage::StopMultimediaTransmission(MultimediaStreamControl {
10130 conference_id: leg.conference_id,
10131 passthrough_party_id: leg.request.token().get().into(),
10132 call_reference: CallReference::new(call.wire_reference),
10133 port_handling_flag: 0,
10134 })
10135}
10136
10137fn take_multimedia_transmit_stop(
10138 state: &mut SessionState,
10139 call_id: CallId,
10140) -> Option<ServerMessage> {
10141 let call = state.calls_by_id.get_mut(&call_id)?;
10142 let leg = call.video_transmit.leg.take()?;
10143 Some(multimedia_transmit_stop_message(call, &leg))
10144}
10145
10146fn take_all_multimedia_transmit_stops(state: &mut SessionState) -> Vec<ServerMessage> {
10147 let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
10148 call_ids.sort_unstable_by_key(|call_id| call_id.get());
10149 call_ids
10150 .into_iter()
10151 .filter_map(|call_id| take_multimedia_transmit_stop(state, call_id))
10152 .collect()
10153}
10154
10155fn expire_multimedia_transmit_acknowledgements(
10156 state: &mut SessionState,
10157 now: Instant,
10158) -> Vec<ExpiredVideoTransmit> {
10159 let mut call_ids = state
10160 .calls_by_id
10161 .iter()
10162 .filter_map(|(&call_id, call)| {
10163 call.video_transmit.leg.as_ref().and_then(|leg| {
10164 (leg.state == MediaChannelState::Opening
10165 && leg.deadline.is_some_and(|deadline| deadline <= now))
10166 .then_some(call_id)
10167 })
10168 })
10169 .collect::<Vec<_>>();
10170 call_ids.sort_unstable_by_key(|call_id| call_id.get());
10171 call_ids
10172 .into_iter()
10173 .filter_map(|call_id| {
10174 let leg = state
10175 .calls_by_id
10176 .get(&call_id)?
10177 .video_transmit
10178 .leg
10179 .as_ref()?;
10180 let codec = leg.codec;
10181 let passthrough_party_id = leg.request.token().get().into();
10182 take_multimedia_transmit_stop(state, call_id).map(|stop| ExpiredVideoTransmit {
10183 call_id,
10184 codec,
10185 passthrough_party_id,
10186 stop,
10187 })
10188 })
10189 .collect()
10190}
10191
10192fn validate_multicast_route(
10193 state: &SessionState,
10194 route: MulticastMediaRoute,
10195) -> Result<(), ServerError> {
10196 if !route.address.is_multicast() {
10197 return Err(ServerError::InvalidMulticastMedia(
10198 "address must be multicast",
10199 ));
10200 }
10201 if route.address.is_ipv6() && state.registration.protocol < ProtocolVersion::V17 {
10202 return Err(ServerError::InvalidMulticastMedia(
10203 "IPv6 requires protocol v17 or later",
10204 ));
10205 }
10206 if route.port == 0 {
10207 return Err(ServerError::InvalidMulticastMedia("port must be nonzero"));
10208 }
10209 if route.packet_millis == 0 {
10210 return Err(ServerError::InvalidMulticastMedia(
10211 "packet duration must be nonzero",
10212 ));
10213 }
10214 if route.codec.kind() != CodecKind::Audio {
10215 return Err(ServerError::UnsupportedMulticastCodec);
10216 }
10217 let capability = state
10218 .media_capabilities
10219 .audio()
10220 .iter()
10221 .find(|capability| capability.codec == route.codec)
10222 .filter(|capability| capability.max_packet_ms != 0)
10223 .ok_or(ServerError::UnsupportedMulticastCodec)?;
10224 if route.packet_millis > capability.max_packet_ms {
10225 return Err(ServerError::InvalidMulticastMedia(
10226 "packet framing exceeds the advertised capability",
10227 ));
10228 }
10229 Ok(())
10230}
10231
10232fn allocate_multicast_request_identity(
10233 state: &mut SessionState,
10234) -> Result<MediaRequestIdentity, ServerError> {
10235 let generation = state
10236 .next_multicast_generation
10237 .checked_add(1)
10238 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10239 let token = state
10240 .next_media_token
10241 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10242 let identity = MediaRequestIdentity::new(generation, token)
10243 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10244 state.next_multicast_generation = generation;
10245 state.next_media_token = token.checked_next();
10246 Ok(identity)
10247}
10248
10249fn multicast_stop_message(
10250 key: MulticastKey,
10251 wire_call_reference: u32,
10252 request: MediaRequestIdentity,
10253 receive: bool,
10254) -> ServerMessage {
10255 if receive {
10256 ServerMessage::StopMulticastMediaReception {
10257 conference_id: key.conference_id,
10258 passthrough_party_id: request.token().get().into(),
10259 call_reference: CallReference::new(wire_call_reference),
10260 }
10261 } else {
10262 ServerMessage::StopMulticastMediaTransmission {
10263 conference_id: key.conference_id,
10264 passthrough_party_id: request.token().get().into(),
10265 call_reference: CallReference::new(wire_call_reference),
10266 }
10267 }
10268}
10269
10270fn take_multicast_stop(
10271 state: &mut SessionState,
10272 key: MulticastKey,
10273 receive: bool,
10274) -> Option<ServerMessage> {
10275 let session = state.multicast.get_mut(&key)?;
10276 let request = if receive {
10277 session.receive.take().map(|leg| leg.request)
10278 } else {
10279 session.transmit.take().map(|leg| leg.request)
10280 }?;
10281 let message = multicast_stop_message(key, session.wire_call_reference, request, receive);
10282 if session.receive.is_none() && session.transmit.is_none() {
10283 state.multicast.remove(&key);
10284 }
10285 Some(message)
10286}
10287
10288fn expire_multicast_reception_acknowledgements(
10289 state: &mut SessionState,
10290 now: Instant,
10291) -> Vec<(MulticastKey, ServerMessage)> {
10292 let mut expired = state
10293 .multicast
10294 .iter()
10295 .filter_map(|(key, session)| {
10296 session.receive.as_ref().and_then(|receive| {
10297 matches!(
10298 receive.state,
10299 MulticastReceiveState::AwaitingAcknowledgement { deadline }
10300 if deadline <= now
10301 )
10302 .then_some(*key)
10303 })
10304 })
10305 .collect::<Vec<_>>();
10306 expired.sort_unstable_by_key(|key| (key.conference_id.get(), key.call_id.get()));
10307 expired
10308 .into_iter()
10309 .filter_map(|key| take_multicast_stop(state, key, true).map(|stop| (key, stop)))
10310 .collect()
10311}
10312
10313fn take_multicast_stops_for_call(state: &mut SessionState, call_id: CallId) -> Vec<ServerMessage> {
10314 let mut keys = state
10315 .multicast
10316 .keys()
10317 .copied()
10318 .filter(|key| key.call_id == call_id)
10319 .collect::<Vec<_>>();
10320 keys.sort_unstable_by_key(|key| key.conference_id.get());
10321 keys.into_iter()
10322 .flat_map(|key| {
10323 [
10324 take_multicast_stop(state, key, true),
10325 take_multicast_stop(state, key, false),
10326 ]
10327 .into_iter()
10328 .flatten()
10329 })
10330 .collect()
10331}
10332
10333fn take_all_multicast_stops(state: &mut SessionState) -> Vec<ServerMessage> {
10334 let mut sessions = std::mem::take(&mut state.multicast)
10335 .into_iter()
10336 .collect::<Vec<_>>();
10337 sessions.sort_unstable_by_key(|(key, _)| (key.conference_id.get(), key.call_id.get()));
10338 sessions
10339 .into_iter()
10340 .flat_map(|(key, session)| {
10341 [
10342 session.receive.map(|leg| {
10343 multicast_stop_message(key, session.wire_call_reference, leg.request, true)
10344 }),
10345 session.transmit.map(|leg| {
10346 multicast_stop_message(key, session.wire_call_reference, leg.request, false)
10347 }),
10348 ]
10349 .into_iter()
10350 .flatten()
10351 })
10352 .collect()
10353}
10354
10355fn take_all_audio_stops(state: &mut SessionState) -> Vec<ServerMessage> {
10356 let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
10357 call_ids.sort_unstable_by_key(|call_id| call_id.get());
10358 let mut messages = Vec::new();
10359 for call_id in call_ids {
10360 let call = state
10361 .calls_by_id
10362 .get_mut(&call_id)
10363 .expect("call identifier came from session state");
10364 if call.media.transmit.state != MediaChannelState::Closed {
10365 messages.push(ServerMessage::StopMediaTransmission(AudioStreamControl {
10366 conference_id: ConferenceId::new(call.wire_reference),
10367 call_reference: CallReference::new(call.wire_reference),
10368 passthrough_party_id: media_request_party_id(
10369 call.media.transmit.request,
10370 call.wire_reference,
10371 )
10372 .into(),
10373 port_handling_flag: 0,
10374 }));
10375 }
10376 if call.media.receive.state != MediaChannelState::Closed {
10377 messages.push(ServerMessage::CloseReceiveChannel(AudioStreamControl {
10378 conference_id: ConferenceId::new(call.wire_reference),
10379 call_reference: CallReference::new(call.wire_reference),
10380 passthrough_party_id: media_request_party_id(
10381 call.media.receive.request,
10382 call.wire_reference,
10383 )
10384 .into(),
10385 port_handling_flag: 0,
10386 }));
10387 }
10388 call.media.receive.state = MediaChannelState::Closed;
10389 call.media.receive.deadline = None;
10390 call.media.receive.peer = None;
10391 call.media.transmit.state = MediaChannelState::Closed;
10392 call.media.transmit.deadline = None;
10393 call.media.transmit.peer = None;
10394 call.media.transmit_confirmation = TransmitConfirmation::Inactive;
10395 call.media.coupled_transmit_endpoint = None;
10396 }
10397 messages
10398}
10399
10400async fn drain_session_media(
10401 stream: &mut dyn StationIo,
10402 state: &mut SessionState,
10403) -> Result<(), ServerError> {
10404 let protocol = state.registration.protocol;
10405 let messages = take_all_audio_stops(state)
10406 .into_iter()
10407 .chain(take_all_multimedia_receive_closes(state))
10408 .chain(take_all_multimedia_transmit_stops(state))
10409 .chain(take_all_multicast_stops(state));
10410 let mut first_error = None;
10411 for message in messages {
10412 if let Err(error) = send_message(stream, &message, protocol).await
10413 && first_error.is_none()
10414 {
10415 first_error = Some(error);
10416 }
10417 }
10418 match first_error {
10419 Some(error) => Err(error),
10420 None => Ok(()),
10421 }
10422}
10423
10424async fn stop_call_multicast(
10425 stream: &mut dyn StationIo,
10426 state: &mut SessionState,
10427 call_id: CallId,
10428 protocol: ProtocolVersion,
10429) -> Result<(), ServerError> {
10430 for message in take_multicast_stops_for_call(state, call_id) {
10431 send_message(stream, &message, protocol).await?;
10432 }
10433 Ok(())
10434}
10435
10436fn allocate_media_request_identity(
10437 state: &mut SessionState,
10438 call_id: CallId,
10439) -> Result<MediaRequestIdentity, ServerError> {
10440 let generation = require_call(state, call_id)?
10441 .media
10442 .generation
10443 .checked_add(1)
10444 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10445 let token = state
10446 .next_media_token
10447 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10448 let identity = MediaRequestIdentity::new(generation, token)
10449 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
10450 state.next_media_token = token.checked_next();
10451 require_call_mut(state, call_id)?.media.generation = generation;
10452 Ok(identity)
10453}
10454
10455fn media_request_party_id(
10456 request: Option<MediaRequestIdentity>,
10457 stable_call_reference: u32,
10458) -> u32 {
10459 request.map_or(stable_call_reference, |identity| identity.token().get())
10460}
10461
10462fn remove_call(state: &mut SessionState, call_id: CallId) {
10463 if let Some(call) = state.calls_by_id.remove(&call_id) {
10464 state.calls_by_wire.remove(&call.wire_reference);
10465 if state.active_call_id == Some(call_id) {
10466 state.active_call_id = None;
10467 }
10468 if state.ringer_owner == Some(call_id) {
10469 state.ringer_owner = None;
10470 }
10471 }
10472}
10473
10474fn canonical_ip_address(address: IpAddr) -> IpAddr {
10475 match address {
10476 IpAddr::V6(address) => address
10477 .to_ipv4_mapped()
10478 .map_or(IpAddr::V6(address), IpAddr::V4),
10479 address => address,
10480 }
10481}
10482
10483fn server_response_address(
10484 local: IpAddr,
10485 configured_ipv4_fallback: Ipv4Addr,
10486 configured_ipv6_fallback: Option<Ipv6Addr>,
10487) -> IpAddr {
10488 match canonical_ip_address(local) {
10489 IpAddr::V4(address) if address.is_unspecified() => IpAddr::V4(configured_ipv4_fallback),
10490 IpAddr::V6(address) if address.is_unspecified() => {
10491 configured_ipv6_fallback.map_or(IpAddr::V4(configured_ipv4_fallback), IpAddr::V6)
10492 }
10493 local => local,
10494 }
10495}
10496
10497fn server_response_endpoints(
10498 context: &SessionContext,
10499 protocol: ProtocolVersion,
10500) -> Result<Vec<SignalingServerEndpoint>, ServerError> {
10501 let local_endpoint = || {
10502 let address = server_response_address(
10503 context.local.ip(),
10504 context.config.advertised_address,
10505 context.config.advertised_ipv6_address,
10506 );
10507 let address = if protocol < ProtocolVersion::V17 && address.is_ipv6() {
10508 IpAddr::V4(context.config.advertised_address)
10509 } else {
10510 address
10511 };
10512 if address.is_unspecified() {
10513 return Err(ServerError::InvalidConfig(
10514 "server-list fallback address is unspecified".into(),
10515 ));
10516 }
10517 Ok(SignalingServerEndpoint {
10518 name: context.config.server_name.clone(),
10519 address,
10520 port: NonZeroU16::new(context.local.port()).ok_or_else(|| {
10521 ServerError::InvalidConfig("accepted local endpoint has port zero".into())
10522 })?,
10523 })
10524 };
10525 if context.config.signaling_servers.is_empty() {
10526 return local_endpoint().map(|endpoint| vec![endpoint]);
10527 }
10528
10529 let mut routes = context.config.signaling_servers.iter().collect::<Vec<_>>();
10530 routes.sort_unstable_by_key(|route| route.priority);
10531 let endpoints = routes
10532 .into_iter()
10533 .filter(|route| protocol >= ProtocolVersion::V17 || route.address.is_ipv4())
10534 .filter_map(|route| route.endpoint(context.transport))
10535 .collect::<Vec<_>>();
10536 if endpoints.is_empty() {
10537 local_endpoint().map(|endpoint| vec![endpoint])
10538 } else {
10539 Ok(endpoints)
10540 }
10541}
10542
10543async fn close_call_media_messages(
10544 stream: &mut dyn StationIo,
10545 call: &SessionCall,
10546 protocol: ProtocolVersion,
10547) -> Result<(), ServerError> {
10548 if let Some(leg) = &call.video_receive.leg {
10549 send_message(
10550 stream,
10551 &multimedia_receive_close_message(call, leg),
10552 protocol,
10553 )
10554 .await?;
10555 }
10556 if let Some(leg) = &call.video_transmit.leg {
10557 send_message(
10558 stream,
10559 &multimedia_transmit_stop_message(call, leg),
10560 protocol,
10561 )
10562 .await?;
10563 }
10564 if call.media.receive.state != MediaChannelState::Closed {
10565 send_message(
10566 stream,
10567 &ServerMessage::CloseReceiveChannel(AudioStreamControl {
10568 conference_id: ConferenceId::new(call.wire_reference),
10569 call_reference: CallReference::new(call.wire_reference),
10570 passthrough_party_id: media_request_party_id(
10571 call.media.receive.request,
10572 call.wire_reference,
10573 )
10574 .into(),
10575 port_handling_flag: 0,
10576 }),
10577 protocol,
10578 )
10579 .await?;
10580 }
10581 if call.media.transmit.state != MediaChannelState::Closed {
10582 send_message(
10583 stream,
10584 &ServerMessage::StopMediaTransmission(AudioStreamControl {
10585 conference_id: ConferenceId::new(call.wire_reference),
10586 call_reference: CallReference::new(call.wire_reference),
10587 passthrough_party_id: media_request_party_id(
10588 call.media.transmit.request,
10589 call.wire_reference,
10590 )
10591 .into(),
10592 port_handling_flag: 0,
10593 }),
10594 protocol,
10595 )
10596 .await?;
10597 }
10598 Ok(())
10599}
10600
10601async fn close_call_messages(
10602 stream: &mut dyn StationIo,
10603 call: &SessionCall,
10604 soft_keys: &SoftKeyProfile,
10605 protocol: ProtocolVersion,
10606 timezone_offset_minutes: i16,
10607 stop_ringer: bool,
10608) -> Result<(), ServerError> {
10609 send_message(
10610 stream,
10611 &ServerMessage::StopTone {
10612 line_instance: call.line_instance,
10613 call_reference: call.wire_reference,
10614 },
10615 protocol,
10616 )
10617 .await?;
10618 send_message(
10619 stream,
10620 &ServerMessage::SetLamp {
10621 stimulus: ButtonType::Line,
10622 instance: call.line_instance,
10623 mode: LampMode::Off,
10624 },
10625 protocol,
10626 )
10627 .await?;
10628 send_message(
10629 stream,
10630 &ServerMessage::ClearPrompt {
10631 line_instance: call.line_instance,
10632 call_reference: call.wire_reference,
10633 },
10634 protocol,
10635 )
10636 .await?;
10637 send_message(
10638 stream,
10639 &ServerMessage::CallState {
10640 state: CallState::OnHook,
10641 line_instance: call.line_instance,
10642 call_reference: call.wire_reference,
10643 },
10644 protocol,
10645 )
10646 .await?;
10647 send_message(
10648 stream,
10649 &ServerMessage::SelectSoftKeys {
10650 line_instance: 0,
10651 call_reference: 0,
10652 set: KeyMode::OnHook,
10653 valid_mask: soft_keys.valid_mask(KeyMode::OnHook),
10654 },
10655 protocol,
10656 )
10657 .await?;
10658 send_message(
10659 stream,
10660 &time_date_message(timezone_offset_minutes),
10661 protocol,
10662 )
10663 .await?;
10664 send_message(
10665 stream,
10666 &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
10667 protocol,
10668 )
10669 .await?;
10670 if stop_ringer {
10671 send_message(
10672 stream,
10673 &ServerMessage::SetRinger {
10674 mode: RingerMode::Off,
10675 duration: RingDuration::Normal,
10676 line_instance: call.line_instance,
10677 call_reference: call.wire_reference,
10678 },
10679 protocol,
10680 )
10681 .await?;
10682 }
10683 Ok(())
10684}
10685
10686fn time_date_message(timezone_offset_minutes: i16) -> ServerMessage {
10687 time_date_message_at(SystemTime::now(), timezone_offset_minutes)
10688}
10689
10690fn time_date_message_at(now: SystemTime, timezone_offset_minutes: i16) -> ServerMessage {
10691 let unix = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
10692 let local = (unix as i128 + i128::from(timezone_offset_minutes) * 60)
10693 .clamp(0, i128::from(u32::MAX)) as u64;
10694 let days = (local / 86_400) as i64;
10695 let seconds = local % 86_400;
10696 let (year, month, day) = civil_from_days(days);
10697 ServerMessage::TimeDate {
10698 year: year as u32,
10699 month,
10700 weekday: ((days + 4).rem_euclid(7) + 1) as u32,
10701 day,
10702 hour: (seconds / 3600) as u32,
10703 minute: ((seconds % 3600) / 60) as u32,
10704 second: (seconds % 60) as u32,
10705 milliseconds: 0,
10706 unix_seconds: local as u32,
10707 }
10708}
10709
10710fn civil_from_days(days_since_epoch: i64) -> (i64, u32, u32) {
10712 let z = days_since_epoch + 719_468;
10713 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
10714 let doe = z - era * 146_097;
10715 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
10716 let mut year = yoe + era * 400;
10717 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
10718 let mp = (5 * doy + 2) / 153;
10719 let day = doy - (153 * mp + 2) / 5 + 1;
10720 let month = mp + if mp < 10 { 3 } else { -9 };
10721 year += i64::from(month <= 2);
10722 (year, month as u32, day as u32)
10723}
10724
10725#[cfg(test)]
10726#[path = "server/tests/mod.rs"]
10727mod tests;