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