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