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