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