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