1mod qos;
49mod transport;
50
51pub use qos::{
52 SignalingSocket, SocketQosFailure, SocketQosMark, SocketQosPolicy, SocketQosReport,
53 StationSocketQos, apply_socket_qos,
54};
55pub use transport::{ServerIngress, StationIo};
56
57use std::collections::{HashMap, HashSet};
58use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
59use std::num::NonZeroU16;
60use std::sync::atomic::{AtomicU64, Ordering};
61use std::sync::{Arc, RwLock};
62use std::time::{Duration, SystemTime, UNIX_EPOCH};
63
64use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
65use thiserror::Error;
66use tokio::io::{AsyncReadExt, AsyncWriteExt};
67use tokio::net::TcpListener;
68#[cfg(test)]
69use tokio::net::TcpStream;
70use tokio::sync::{Mutex, mpsc, oneshot};
71use tokio::time::Instant;
72use tracing::{debug, info, warn};
73
74use crate::message::capabilities::StationMediaCapabilities;
75use crate::message::values::{
76 AlarmSeverity, BusyLampFieldState, ButtonType, CallHistoryDisposition, CallState, Codec,
77 CodecKind, DeviceType, Digit, DtmfMode, EchoCancellation, EncryptionCapability, G723BitRate,
78 IpAddressType, KeyMode, LampMode, MediaStatus, MicrophoneMode, MiscCommandType,
79 NotificationPriority, PhoneFeatures, ProtocolVersion, ReceiveTransmit, ResetType, RingDuration,
80 RingerMode, SilenceSuppression, SoftKey, SpeakerMode, StationSessionContext,
81 StatisticsProcessing, Stimulus, SubscriptionCause, Tone, ToneDirection,
82};
83use crate::message::wire::{CodecError, FrameDecoder};
84use crate::message::{
85 AnnouncementEntry, AudioStreamControl, BoundedBytes, ButtonTemplateEntry, ClientMessage,
86 ConnectionStatistics, MediaEncryption, MediaEndpointAddress, MediaRequestIdentity,
87 MediaRequestToken, MiscellaneousCommand, MulticastMediaReception, MulticastMediaTransmission,
88 MultimediaPayload, MultimediaPayloadDirection, MultimediaStreamControl, OpenMultimediaChannel,
89 ServerMessage, SignalingServerEndpoint,
90 StartMultimediaTransmission as MultimediaTransmissionStart, UserDataV1Message,
91 VideoFlowControl,
92};
93#[cfg(test)]
94use crate::message::{ControlMessage, MediaCapability, XmlAlarmMessage};
95use crate::phone::service::{
96 PhoneServiceEvent, PhoneServiceExtendedRouting, PhoneServiceMessageKind, PhoneServicePayload,
97 PhoneServiceRouting, parse_phone_service_payload,
98};
99#[cfg(test)]
100use crate::phone::xml::{
101 self as phone_xml, CiscoIpPhoneGraphicFileMenu, CiscoIpPhoneImageFile, CiscoIpPhoneInputItem,
102 CiscoIpPhoneKeyItem, CiscoIpPhoneSoftKeyItem, CiscoIpPhoneStatus, CiscoIpPhoneStatusFile,
103 CiscoIpPhoneTouchAreaMenuItem, PHONE_EXECUTE_MAX_ITEMS, PHONE_STATUS_BITMAP_MAX_BYTES,
104 PhoneBackgroundHttpUrl, PhoneBitmapData, PhoneExecutePriority, PhoneImageUrl, PhoneInputFlags,
105 PhoneInputParameterName, PhoneRingtoneUrl, PhoneTouchArea, PhoneXmlKey,
106};
107use crate::phone::xml::{
108 CiscoIpPhoneExecute, CiscoIpPhoneExecuteItem, CiscoIpPhoneInput, CiscoIpPhoneMenu,
109 CiscoIpPhoneMenuItem, CiscoIpPhoneSetBackground, CiscoIpPhoneSetBackgroundPreview,
110 CiscoIpPhoneSetRingTone, CiscoIpPhoneText, ConferenceListAction, ConferenceListDocument,
111 ConferenceListEntry, ConferenceMenuFamily, ConferenceParticipantActionsDocument,
112 PHONE_BACKGROUND_APPLICATION_ID, PHONE_EXECUTE_MAX_BYTES, PHONE_IMAGE_MAX_BYTES,
113 PHONE_INPUT_MAX_BYTES, PHONE_RINGTONE_APPLICATION_ID, PHONE_STATUS_MAX_BYTES,
114 PHONE_TEXT_APPLICATION_ID, PHONE_TEXT_LEGACY_MAX_CHARS, PhoneAlarmTelemetry,
115 PhoneBackgroundControlDocument, PhoneImageDocument, PhoneLocationTelemetry,
116 PhoneServicePriority, PhoneStatusDocument, PhoneXmlError, parse_phone_alarm,
117 parse_phone_location,
118};
119use crate::types::SignalingQos;
120use crate::types::{
121 ApplicationId, AudioProcessingPolicy, BlfCallerInfo, BlfState, ButtonDefinition, CallId,
122 CallInfo, CallReference, ConferenceId, DEFAULT_AUDIO_MAX_FRAMES_PER_PACKET,
123 DEFAULT_AUDIO_PACKET_MS, DeviceDefinition, DeviceId, DeviceRegistration, LineAppearance,
124 LineDefinition, LineInstance, MediaEndpoint, MediaTrafficClass, ParticipantId,
125 PassthroughPartyId, SessionGeneration, SoftKeyProfile, StationTransport,
126 StationTransportRequirement, TransactionId,
127};
128use transport::AcceptedStation;
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);
138pub const ORDERING_ACKNOWLEDGEMENT_TIMEOUT: Duration = Duration::from_secs(5);
141const MEDIA_PATH_RELEASE_GRACE: Duration = Duration::from_millis(150);
146const CONNECTION_STATISTICS_TIMEOUT: Duration = Duration::from_secs(10);
148const MAX_PENDING_CONNECTION_STATISTICS: usize = 32;
149const MAX_STATISTICS_REFERENCES_PER_SESSION: usize = 4096;
151const PARKING_APPLICATION_ID: u32 = 9090;
152pub const MIN_REGISTRATION_BACKOFF: Duration = Duration::from_secs(30);
154pub const MAX_REGISTRATION_BACKOFF: Duration = Duration::from_secs(86_400);
156pub const PARKING_MENU_MAX_ITEMS: usize = 32;
161
162#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct ParkingMenuEntry {
169 pub slot: u32,
170 pub caller_name: String,
171 pub caller_number: String,
172 pub connected_name: String,
173 pub connected_number: String,
174}
175
176#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub struct IncomingRing {
182 pub mode: RingerMode,
183 pub duration: RingDuration,
184}
185
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub enum DoNotDisturbMode {
189 Off,
190 Silent,
191 Reject,
192}
193
194#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
196pub enum DoNotDisturbButtonMode {
197 #[default]
198 Cycle,
199 Silent,
200 Reject,
201}
202
203impl Default for IncomingRing {
204 fn default() -> Self {
205 Self {
206 mode: RingerMode::Inside,
207 duration: RingDuration::Normal,
208 }
209 }
210}
211
212#[derive(Clone, Debug, Eq, PartialEq)]
216pub struct MediaStatisticsSnapshot {
217 pub request_generation: u64,
222 pub call_id: CallId,
223 pub line_instance: LineInstance,
224 pub codec: Codec,
225 pub packet_ms: u32,
226 pub max_frames_per_packet: u32,
227 pub receive_peer: Option<MediaEndpoint>,
228 pub transmit_peer: Option<MediaEndpoint>,
229 pub packets_sent: u32,
230 pub octets_sent: u32,
231 pub packets_received: u32,
232 pub octets_received: u32,
233 pub packets_lost: u32,
234 pub jitter_millis: u32,
235 pub latency_millis: u32,
236 pub quality_byte_count: usize,
239}
240
241#[derive(Clone, Debug, Eq, PartialEq)]
247pub enum HandsetStatusMessage {
248 Display {
249 text: String,
250 timeout_seconds: u8,
252 priority: Option<NotificationPriority>,
253 },
254 Clear {
255 priority: Option<NotificationPriority>,
256 },
257}
258
259#[derive(Clone, Debug)]
264pub struct ServerConfig {
265 pub bind: SocketAddr,
266 pub signaling_qos: SignalingQos,
269 pub advertised_address: Ipv4Addr,
273 pub advertised_ipv6_address: Option<Ipv6Addr>,
275 pub server_name: String,
276 pub keepalive_seconds: u32,
279 pub secondary_keepalive_seconds: u32,
281 pub signaling_servers: Vec<SignalingServerRoute>,
284 pub registration_tokens: RegistrationTokenPolicy,
286 pub firmware_version: String,
287 pub dial_terminator: Digit,
288 pub record_dial_terminator: bool,
289 pub call_answer_order: CallSelectionOrder,
290 pub timezone_offset_minutes: i16,
293 pub date_template: crate::types::DateTemplate,
294 pub anonymous_hotline: Option<AnonymousHotlineDefinition>,
298}
299
300#[derive(Clone, Debug, Eq, PartialEq)]
302pub struct SignalingServerRoute {
303 pub priority: u8,
304 pub name: String,
305 pub address: IpAddr,
306 pub clear_port: Option<NonZeroU16>,
307 pub secure_port: Option<NonZeroU16>,
308}
309
310impl SignalingServerRoute {
311 fn endpoint(&self, transport: StationTransport) -> Option<SignalingServerEndpoint> {
312 let port = match transport {
313 StationTransport::Clear => self.clear_port,
314 StationTransport::Secure => self.secure_port,
315 }?;
316 Some(SignalingServerEndpoint {
317 name: self.name.clone(),
318 address: self.address,
319 port,
320 })
321 }
322}
323
324#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
327pub enum RegistrationFallback {
328 #[default]
329 Reject,
330 ReturnToPrimary,
331 DeviceIdOdd,
332 DeviceIdEven,
333}
334
335#[derive(Clone, Debug, Eq, PartialEq)]
337pub struct RegistrationTokenPolicy {
338 pub fallback: RegistrationFallback,
339 pub backoff: Duration,
340 pub server_priority: u8,
341}
342
343impl Default for RegistrationTokenPolicy {
344 fn default() -> Self {
345 Self {
346 fallback: RegistrationFallback::Reject,
347 backoff: Duration::from_secs(60),
348 server_priority: 1,
349 }
350 }
351}
352
353impl RegistrationTokenPolicy {
354 fn accepts(&self, device_id: &DeviceId) -> bool {
355 let last_nibble = device_id
356 .as_str()
357 .strip_prefix("SEP")
358 .filter(|mac| mac.len() == 12 && mac.bytes().all(|byte| byte.is_ascii_hexdigit()))
359 .and_then(|mac| mac.as_bytes().last().copied())
360 .and_then(|byte| char::from(byte).to_digit(16));
361 match self.fallback {
362 RegistrationFallback::Reject => false,
363 RegistrationFallback::ReturnToPrimary => self.server_priority == 1,
364 RegistrationFallback::DeviceIdOdd => last_nibble.is_some_and(|value| value % 2 == 1),
365 RegistrationFallback::DeviceIdEven => last_nibble.is_some_and(|value| value % 2 == 0),
366 }
367 }
368}
369
370#[derive(Clone, Debug, Eq, PartialEq)]
376pub struct AnonymousHotlineDefinition {
377 label: String,
378}
379
380impl AnonymousHotlineDefinition {
381 pub fn new(label: impl Into<String>) -> Result<Self, ServerError> {
385 let label = label.into();
386 if label.is_empty() || label.len() > 79 || label.chars().any(char::is_control) {
387 return Err(ServerError::InvalidConfig(
388 "anonymous-hotline label must contain 1..=79 non-control bytes".into(),
389 ));
390 }
391 Ok(Self { label })
392 }
393
394 fn device_definition(&self, id: DeviceId) -> DeviceDefinition {
395 let soft_keys = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
396 let actions = match mode {
397 KeyMode::OnHook => vec![SoftKey::NewCall],
398 KeyMode::OffHook | KeyMode::RingOut => vec![SoftKey::EndCall],
399 _ => Vec::new(),
400 };
401 (mode, actions)
402 }))
403 .expect("minimal anonymous-hotline soft keys are valid");
404 DeviceDefinition {
405 id,
406 description: self.label.clone(),
407 transport: StationTransportRequirement::Either,
408 signaling_qos: None,
409 buttons: vec![ButtonDefinition::Line(LineAppearance::new(
410 1,
411 LineDefinition {
412 number: "hotline".into(),
413 display_name: self.label.clone(),
414 },
415 ))],
416 soft_keys,
417 ui: Default::default(),
418 }
419 }
420}
421
422#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
424pub enum CallSelectionOrder {
425 #[default]
426 OldestFirst,
427 LastFirst,
428}
429
430#[derive(Clone, Copy, Debug, Eq, PartialEq)]
432pub struct MulticastMediaRoute {
433 pub address: IpAddr,
434 pub port: u16,
435 pub codec: Codec,
436 pub packet_millis: u32,
437}
438
439#[derive(Clone, Debug, Eq, PartialEq)]
445pub struct MultimediaReceiveDescriptor {
446 pub conference_id: ConferenceId,
447 pub payload: MultimediaPayload,
448 pub conference_creator: bool,
449 pub encryption: Option<MediaEncryption>,
450 pub stream_passthrough_id: u32,
451 pub associated_stream_id: u32,
452 pub source: MediaEndpointAddress,
453 pub requested_address_type: IpAddressType,
454}
455
456impl MultimediaReceiveDescriptor {
457 pub fn validate(self) -> Result<Self, ServerError> {
461 validate_multimedia_receive_descriptor(&self)?;
462 Ok(self)
463 }
464}
465
466#[derive(Clone, Debug, Eq, PartialEq)]
472pub struct MultimediaTransmitDescriptor {
473 pub conference_id: ConferenceId,
474 pub endpoint: MediaEndpointAddress,
475 pub payload: MultimediaPayload,
476 pub traffic_class: MediaTrafficClass,
478 pub encryption: Option<MediaEncryption>,
479 pub stream_passthrough_id: u32,
480 pub associated_stream_id: u32,
481}
482
483impl MultimediaTransmitDescriptor {
484 pub fn validate(self) -> Result<Self, ServerError> {
487 validate_multimedia_transmit_descriptor(&self)?;
488 Ok(self)
489 }
490}
491
492#[derive(Clone, Debug, Eq, PartialEq)]
498pub enum MultimediaTransmitControl {
499 FreezePicture,
500 FastPictureUpdate {
501 first_gob: u32,
502 gob_count: u32,
503 },
504 FastGobUpdate {
505 first_gob: u32,
506 gob_count: u32,
507 },
508 FastMacroblockUpdate {
509 first_gob: u32,
510 first_macroblock: u32,
511 macroblock_count: u32,
512 },
513 LostPicture {
514 picture_number: u32,
515 long_term_picture_index: u32,
516 },
517 LostPartialPicture {
518 picture_number: u32,
519 long_term_picture_index: u32,
520 first_macroblock: u32,
521 macroblock_count: u32,
522 },
523 RecoveryReferencePicture {
525 pictures: VideoPictureReferences,
526 },
527 TemporalSpatialTradeoff {
528 value: u32,
529 },
530}
531
532#[derive(Clone, Copy, Debug, Eq, PartialEq)]
534pub struct VideoPictureReference {
535 pub picture_number: u32,
536 pub long_term_picture_index: u32,
537}
538
539#[derive(Clone, Debug, Eq, PartialEq)]
541pub struct VideoPictureReferences(Box<[VideoPictureReference]>);
542
543impl VideoPictureReferences {
544 pub fn new(
547 pictures: impl IntoIterator<Item = VideoPictureReference>,
548 ) -> Result<Self, ServerError> {
549 let pictures = pictures.into_iter().take(5).collect::<Vec<_>>();
550 if pictures.len() > 4 {
551 return Err(ServerError::InvalidMultimediaTransmitControl(
552 "recovery picture count exceeds four",
553 ));
554 }
555 Ok(Self(pictures.into_boxed_slice()))
556 }
557
558 pub fn as_slice(&self) -> &[VideoPictureReference] {
560 &self.0
561 }
562}
563
564impl TryFrom<Vec<VideoPictureReference>> for VideoPictureReferences {
565 type Error = ServerError;
566
567 fn try_from(pictures: Vec<VideoPictureReference>) -> Result<Self, Self::Error> {
568 Self::new(pictures)
569 }
570}
571
572impl Default for ServerConfig {
573 fn default() -> Self {
574 Self {
575 bind: SocketAddr::from(([0, 0, 0, 0], 2000)),
576 signaling_qos: SignalingQos::default(),
577 advertised_address: Ipv4Addr::LOCALHOST,
578 advertised_ipv6_address: None,
579 server_name: "sccp-protocol".to_string(),
580 keepalive_seconds: 30,
581 secondary_keepalive_seconds: 30,
582 signaling_servers: Vec::new(),
583 registration_tokens: RegistrationTokenPolicy::default(),
584 firmware_version: String::new(),
585 dial_terminator: Digit::Pound,
586 record_dial_terminator: false,
587 call_answer_order: CallSelectionOrder::OldestFirst,
588 timezone_offset_minutes: 0,
589 date_template: Default::default(),
590 anonymous_hotline: None,
591 }
592 }
593}
594
595#[derive(Clone, Debug, Eq, PartialEq)]
596pub enum Event {
604 SessionError {
605 peer: SocketAddr,
606 error: String,
607 },
608 ProtocolWarning {
611 peer: SocketAddr,
612 device_id: Option<DeviceId>,
614 message_id: u32,
615 error: String,
616 },
617 Device(DeviceEvent),
618}
619
620impl Event {
621 pub fn device(
622 device_id: DeviceId,
623 session_generation: SessionGeneration,
624 event: DeviceEventKind,
625 ) -> Self {
626 Self::Device(DeviceEvent::new(device_id, session_generation, event))
627 }
628}
629
630#[derive(Clone, Debug, Eq, PartialEq)]
632pub struct DeviceEvent {
633 pub device_id: DeviceId,
634 pub session_generation: SessionGeneration,
636 pub event: DeviceEventKind,
637}
638
639impl DeviceEvent {
640 pub fn new(
641 device_id: DeviceId,
642 session_generation: SessionGeneration,
643 event: DeviceEventKind,
644 ) -> Self {
645 Self {
646 device_id,
647 session_generation,
648 event,
649 }
650 }
651}
652
653#[derive(Clone, Debug, Eq, PartialEq)]
659pub enum DeviceEventKind {
660 Registered(DeviceRegistration),
661 Disconnected {},
662 Capabilities {
663 capabilities: StationMediaCapabilities,
664 },
665 OffHook {
666 call_id: CallId,
667 line_instance: LineInstance,
668 },
669 OnHook {
670 call_id: CallId,
671 line_instance: LineInstance,
672 },
673 Digit {
674 call_id: CallId,
675 digit: Digit,
676 },
677 EnblocCall {
678 call_id: CallId,
679 line_instance: LineInstance,
680 number: String,
681 },
682 SoftKey {
683 call_id: Option<CallId>,
685 line_instance: LineInstance,
686 soft_key: SoftKey,
687 },
688 LineButton {
689 line_instance: LineInstance,
690 call_id: Option<CallId>,
691 },
692 HookFlash {
693 call_id: Option<CallId>,
694 line_instance: LineInstance,
695 },
696 FeatureButton {
697 instance: LineInstance,
698 },
699 DoNotDisturbButton {
700 instance: LineInstance,
701 },
702 MobilityButton {
703 instance: LineInstance,
704 },
705 VoicemailButton {
708 call_id: CallId,
709 line_instance: LineInstance,
710 },
711 ParkingLotButton {
712 instance: LineInstance,
714 call_id: Option<CallId>,
716 line_instance: LineInstance,
717 },
718 ParkingMenuSelection {
719 lot: String,
720 slot: u32,
721 },
722 PhoneServiceResponse {
723 response: PhoneServiceEvent,
724 },
725 ConferenceListAction {
726 action: ConferenceListAction,
727 },
728 ReceiveChannelOpened {
730 call_id: CallId,
731 status: MediaStatus,
732 endpoint: MediaEndpoint,
733 },
734 MultimediaReceiveChannelOpened {
735 call_id: CallId,
736 codec: Codec,
737 endpoint: MediaEndpointAddress,
738 passthrough_party_id: PassthroughPartyId,
739 },
740 MultimediaReceiveChannelFailed {
741 call_id: CallId,
742 codec: Codec,
743 status: MediaStatus,
744 endpoint: MediaEndpointAddress,
745 passthrough_party_id: PassthroughPartyId,
746 },
747 MultimediaReceiveChannelTimedOut {
748 call_id: CallId,
749 codec: Codec,
750 passthrough_party_id: PassthroughPartyId,
751 },
752 MultimediaTransmitStarted {
753 call_id: CallId,
754 codec: Codec,
755 endpoint: MediaEndpointAddress,
756 passthrough_party_id: PassthroughPartyId,
757 },
758 MultimediaTransmitFailed {
759 call_id: CallId,
760 codec: Codec,
761 status: MediaStatus,
762 endpoint: MediaEndpointAddress,
763 passthrough_party_id: PassthroughPartyId,
764 },
765 MultimediaTransmitTimedOut {
766 call_id: CallId,
767 codec: Codec,
768 passthrough_party_id: PassthroughPartyId,
769 },
770 TransmitChannelImplied {
779 call_id: CallId,
780 endpoint: MediaEndpoint,
781 },
782 TransmitChannelStarted {
784 call_id: CallId,
785 status: MediaStatus,
786 endpoint: MediaEndpoint,
787 },
788 HandsetAcknowledgementTimedOut {
789 call_id: CallId,
790 acknowledgement: HandsetAcknowledgement,
791 },
792 MediaTransmissionFailed {
793 call_id: CallId,
794 status: MediaStatus,
795 endpoint: MediaEndpoint,
796 },
797 MulticastReceptionStarted {
798 conference_id: ConferenceId,
799 call_id: CallId,
800 route: MulticastMediaRoute,
801 },
802 MulticastReceptionFailed {
803 conference_id: ConferenceId,
804 call_id: CallId,
805 status: MediaStatus,
806 },
807 MulticastReceptionTimedOut {
808 conference_id: ConferenceId,
809 call_id: CallId,
810 },
811 MulticastTransmissionStarted {
812 conference_id: ConferenceId,
813 call_id: CallId,
814 route: MulticastMediaRoute,
815 },
816 MulticastTransmissionFailed {
817 conference_id: ConferenceId,
818 call_id: CallId,
819 status: MediaStatus,
820 address: IpAddr,
821 port: u16,
822 },
823 ConnectionStatisticsCollected {
825 snapshot: MediaStatisticsSnapshot,
826 },
827 Alarm {
828 severity: AlarmSeverity,
829 text: String,
830 parameters: Option<[u32; 2]>,
831 },
832 XmlAlarm {
833 telemetry: PhoneAlarmTelemetry,
834 },
835 LocationInformation {
836 telemetry: PhoneLocationTelemetry,
837 },
838 HeadsetStatusChanged {
839 enabled: bool,
840 },
841 MediaPathChanged {
842 path: crate::message::values::MediaPathId,
843 event: crate::message::values::MediaPathEvent,
844 },
845 UnhandledMessage {
847 message: ClientMessage,
848 },
849}
850
851#[derive(Clone, Copy, Debug, Eq, PartialEq)]
853pub enum HandsetAcknowledgement {
854 OpenReceiveChannel,
855 StartMediaTransmission,
856}
857
858#[derive(Clone, Debug)]
863pub struct Command {
864 pub device_id: DeviceId,
865 pub action: CommandAction,
866}
867
868impl Command {
869 pub fn new(device_id: DeviceId, action: CommandAction) -> Self {
870 Self { device_id, action }
871 }
872}
873
874#[derive(Clone, Debug)]
886pub enum CommandAction {
887 BeginCall {
890 line_instance: LineInstance,
891 call_id: CallId,
892 codec: Codec,
893 },
894 BeginTransfer {
897 source_call_id: CallId,
898 consultation_line_instance: LineInstance,
899 consultation_call_id: CallId,
900 codec: Codec,
901 },
902 SetCallInfo {
903 call_id: CallId,
904 info: CallInfo,
905 },
906 CommitOutboundCall {
907 call_id: CallId,
908 info: CallInfo,
909 },
910 PresentOutboundProceeding {
911 call_id: CallId,
912 info: CallInfo,
913 },
914 PresentOutboundRinging {
915 call_id: CallId,
916 info: CallInfo,
917 },
918 SetCallState {
919 call_id: CallId,
920 state: CallState,
921 },
922 SetCallSelected {
923 call_id: CallId,
924 selected: bool,
925 },
926 DisplayPrompt {
927 call_id: CallId,
928 timeout_seconds: u32,
929 text: String,
930 },
931 ClearPrompt {
932 call_id: CallId,
933 },
934 SetStatusMessage {
935 message: HandsetStatusMessage,
936 beep: bool,
937 },
938 SetMicrophoneMode {
939 enabled: bool,
940 },
941 SetRecordingStatus {
942 call_id: CallId,
943 active: bool,
944 },
945 ResetDevice {
946 reset_type: ResetType,
947 },
948 SetMwi {
949 line_instance: LineInstance,
950 enabled: bool,
951 },
952 SetForwardStatus {
957 line_instance: LineInstance,
958 forward_all: Option<String>,
959 forward_busy: Option<String>,
960 forward_no_answer: Option<String>,
961 },
962 SetFeatureStatus {
963 instance: LineInstance,
964 enabled: bool,
965 },
966 SetDoNotDisturbStatus {
967 instance: LineInstance,
968 mode: DoNotDisturbMode,
969 button_mode: DoNotDisturbButtonMode,
970 },
971 SetMobilityAppearance {
974 mobility_instance: LineInstance,
975 appearance: Option<LineAppearance>,
976 },
977 SetBlfStatus {
978 instance: LineInstance,
979 number: String,
980 label: String,
981 state: BlfState,
982 caller: Option<BlfCallerInfo>,
983 },
984 ShowParkingMenu {
985 instance: LineInstance,
986 transaction_id: TransactionId,
987 lot: String,
988 calls: Vec<ParkingMenuEntry>,
989 },
990 ShowConferenceList {
991 call_id: CallId,
992 conference_id: ConferenceId,
993 participants: Vec<ConferenceListEntry>,
994 },
995 ShowConferenceParticipantActions {
996 call_id: CallId,
997 conference_id: ConferenceId,
998 participant: ConferenceListEntry,
999 removable: bool,
1000 demotable: bool,
1001 },
1002 ShowTextService {
1003 line_instance: LineInstance,
1004 call_reference: CallReference,
1005 transaction_id: TransactionId,
1006 priority: PhoneServicePriority,
1007 document: CiscoIpPhoneText,
1008 },
1009 ShowInputService {
1012 line_instance: LineInstance,
1013 call_reference: CallReference,
1014 application_id: ApplicationId,
1015 transaction_id: TransactionId,
1016 priority: PhoneServicePriority,
1017 document: CiscoIpPhoneInput,
1018 },
1019 ExecutePhoneActions {
1020 line_instance: LineInstance,
1021 call_reference: CallReference,
1022 application_id: ApplicationId,
1023 transaction_id: TransactionId,
1024 priority: PhoneServicePriority,
1025 document: CiscoIpPhoneExecute,
1026 },
1027 ShowImageService {
1028 line_instance: LineInstance,
1029 call_reference: CallReference,
1030 application_id: ApplicationId,
1031 transaction_id: TransactionId,
1032 priority: PhoneServicePriority,
1033 document: PhoneImageDocument,
1034 },
1035 ShowStatusService {
1036 line_instance: LineInstance,
1037 call_reference: CallReference,
1038 application_id: ApplicationId,
1039 transaction_id: TransactionId,
1040 priority: PhoneServicePriority,
1041 document: PhoneStatusDocument,
1042 },
1043 SetBackgroundImage {
1044 transaction_id: TransactionId,
1045 document: CiscoIpPhoneSetBackground,
1046 },
1047 PreviewBackgroundImage {
1048 transaction_id: TransactionId,
1049 document: CiscoIpPhoneSetBackgroundPreview,
1050 },
1051 SetRingtone {
1052 transaction_id: TransactionId,
1053 document: CiscoIpPhoneSetRingTone,
1054 },
1055 StartTone {
1056 call_id: CallId,
1057 tone: Tone,
1058 },
1059 StartAnnouncement {
1062 conference_id: ConferenceId,
1063 announcements: Vec<AnnouncementEntry>,
1064 end_of_ack: bool,
1066 participant_ids: Vec<ParticipantId>,
1067 hearing_participant_mask: u32,
1069 play_mode: u32,
1071 },
1072 StopAnnouncement {
1073 conference_id: ConferenceId,
1074 },
1075 AnnouncementFinish {
1076 conference_id: ConferenceId,
1077 play_status: u32,
1078 },
1079 StartRinging {
1080 call_id: CallId,
1081 },
1082 StopRinging {
1083 call_id: CallId,
1084 },
1085 OpenReceiveChannel {
1088 call_id: CallId,
1089 source: Option<MediaEndpoint>,
1092 codec: Codec,
1093 packet_ms: u32,
1094 max_frames_per_packet: u32,
1095 dtmf_mode: DtmfMode,
1096 audio_processing: AudioProcessingPolicy,
1097 },
1098 OpenMultimediaReceiveChannel {
1101 call_id: CallId,
1102 descriptor: MultimediaReceiveDescriptor,
1103 },
1104 CloseMultimediaReceiveChannel {
1105 call_id: CallId,
1106 },
1107 StartMultimediaTransmission {
1110 call_id: CallId,
1111 descriptor: MultimediaTransmitDescriptor,
1112 },
1113 StopMultimediaTransmission {
1114 call_id: CallId,
1115 },
1116 SetMultimediaTransmitBitRate {
1119 call_id: CallId,
1120 passthrough_party_id: PassthroughPartyId,
1121 maximum_bit_rate: u32,
1122 },
1123 NotifyMultimediaTransmitBitRate {
1125 call_id: CallId,
1126 passthrough_party_id: PassthroughPartyId,
1127 maximum_bit_rate: u32,
1128 },
1129 ControlMultimediaTransmission {
1131 call_id: CallId,
1132 passthrough_party_id: PassthroughPartyId,
1133 control: MultimediaTransmitControl,
1134 },
1135 OpenOutboundMedia {
1139 call_id: CallId,
1140 source: Option<MediaEndpoint>,
1141 endpoint: MediaEndpoint,
1142 codec: Codec,
1143 packet_ms: u32,
1144 max_frames_per_packet: u32,
1145 dtmf_mode: DtmfMode,
1146 audio_processing: AudioProcessingPolicy,
1147 traffic_class: MediaTrafficClass,
1148 },
1149 CloseReceiveChannel {
1151 call_id: CallId,
1152 },
1153 StartMedia {
1154 call_id: CallId,
1155 endpoint: MediaEndpoint,
1156 dtmf_mode: DtmfMode,
1157 audio_processing: AudioProcessingPolicy,
1158 traffic_class: MediaTrafficClass,
1159 },
1160 StartMulticastReception {
1161 conference_id: ConferenceId,
1162 call_id: CallId,
1163 route: MulticastMediaRoute,
1164 echo_cancellation: EchoCancellation,
1165 g723_bitrate: G723BitRate,
1166 },
1167 StopMulticastReception {
1168 conference_id: ConferenceId,
1169 call_id: CallId,
1170 },
1171 StartMulticastTransmission {
1172 conference_id: ConferenceId,
1173 call_id: CallId,
1174 route: MulticastMediaRoute,
1175 precedence: u32,
1176 silence_suppression: SilenceSuppression,
1177 max_frames_per_packet: u32,
1178 g723_bitrate: G723BitRate,
1179 },
1180 StopMulticastTransmission {
1181 conference_id: ConferenceId,
1182 call_id: CallId,
1183 },
1184 StopMedia {
1186 call_id: CallId,
1187 },
1188 CloseCall {
1191 call_id: CallId,
1192 },
1193 DisconnectDevice {},
1194}
1195
1196#[derive(Debug, Error)]
1204pub enum ServerError {
1205 #[error("failed to bind SCCP server: {0}")]
1206 Bind(#[source] std::io::Error),
1207 #[error("SCCP server I/O failed: {0}")]
1208 Io(#[from] std::io::Error),
1209 #[error("SCCP protocol error: {0}")]
1210 Protocol(#[from] CodecError),
1211 #[error("invalid SCCP server configuration: {0}")]
1212 InvalidConfig(String),
1213 #[error("phone XML error: {0}")]
1214 PhoneXml(#[from] PhoneXmlError),
1215 #[error("device {0} is not connected")]
1216 DeviceNotConnected(DeviceId),
1217 #[error("call {0:?} does not exist")]
1218 UnknownCall(CallId),
1219 #[error("call {call_id:?} cannot {operation} while in state {state:?}")]
1220 InvalidCallTransaction {
1221 call_id: CallId,
1222 operation: &'static str,
1223 state: CallState,
1224 },
1225 #[error("SCCP server has stopped")]
1226 Stopped,
1227 #[error("SCCP server command queue is full")]
1228 CommandQueueFull,
1229 #[error("SCCP command could not be written to the device: {0}")]
1230 CommandWrite(String),
1231 #[error("SCCP command writer acknowledgement timed out")]
1232 CommandAcknowledgementTimeout,
1233 #[error("SCCP media request identity space is exhausted")]
1234 MediaRequestIdentityExhausted,
1235 #[error("SCCP station session generation space is exhausted")]
1236 SessionGenerationExhausted,
1237 #[error("invalid multicast media policy: {0}")]
1238 InvalidMulticastMedia(&'static str),
1239 #[error("station does not advertise the requested multicast codec")]
1240 UnsupportedMulticastCodec,
1241 #[error("invalid multimedia receive policy: {0}")]
1242 InvalidMultimediaReceive(&'static str),
1243 #[error("station does not advertise the requested video receive capability")]
1244 UnsupportedMultimediaReceive,
1245 #[error("invalid multimedia transmit policy: {0}")]
1246 InvalidMultimediaTransmit(&'static str),
1247 #[error("station does not advertise the requested video transmit capability")]
1248 UnsupportedMultimediaTransmit,
1249 #[error("invalid multimedia transmit control: {0}")]
1250 InvalidMultimediaTransmitControl(&'static str),
1251 #[error(
1252 "call {call_id:?} has no open multimedia transmit stream with passthrough token {passthrough_party_id}"
1253 )]
1254 StaleMultimediaTransmitControl {
1255 call_id: CallId,
1256 passthrough_party_id: PassthroughPartyId,
1257 },
1258 #[error("{message} is a control/service-node message, not a station command")]
1259 InvalidStationCommand { message: &'static str },
1260}
1261
1262impl ServerError {
1263 const fn is_nonfatal_command_rejection(&self) -> bool {
1264 matches!(
1265 self,
1266 Self::InvalidCallTransaction { .. }
1267 | Self::InvalidStationCommand { .. }
1268 | Self::InvalidMulticastMedia(_)
1269 | Self::UnsupportedMulticastCodec
1270 | Self::InvalidMultimediaReceive(_)
1271 | Self::UnsupportedMultimediaReceive
1272 | Self::InvalidMultimediaTransmit(_)
1273 | Self::UnsupportedMultimediaTransmit
1274 | Self::InvalidMultimediaTransmitControl(_)
1275 | Self::StaleMultimediaTransmitControl { .. }
1276 )
1277 }
1278}
1279
1280#[derive(Clone, Debug)]
1287pub struct ServerHandle {
1288 command_tx: mpsc::Sender<ServerCommand>,
1289 next_call_id: Arc<AtomicU64>,
1290 latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
1291 call_answer_order: Arc<RwLock<CallSelectionOrder>>,
1292}
1293
1294#[derive(Clone, Debug, Default, Eq, PartialEq)]
1300pub struct ReconfigureResult {
1301 pub added: Vec<DeviceId>,
1302 pub changed: Vec<DeviceId>,
1303 pub removed: Vec<DeviceId>,
1304}
1305
1306impl ReconfigureResult {
1307 pub fn is_unchanged(&self) -> bool {
1308 self.added.is_empty() && self.changed.is_empty() && self.removed.is_empty()
1309 }
1310
1311 fn disconnected_devices(&self) -> impl Iterator<Item = &DeviceId> {
1312 self.changed.iter().chain(&self.removed)
1313 }
1314}
1315
1316impl ServerHandle {
1317 pub fn set_call_answer_order(&self, order: CallSelectionOrder) {
1320 *self
1321 .call_answer_order
1322 .write()
1323 .expect("SCCP call-answer-order lock poisoned") = order;
1324 }
1325
1326 pub fn latest_media_statistics(&self, device_id: &DeviceId) -> Option<MediaStatisticsSnapshot> {
1331 self.latest_media_statistics
1332 .read()
1333 .expect("SCCP media-statistics lock poisoned")
1334 .get(device_id)
1335 .cloned()
1336 }
1337
1338 pub fn media_statistics(&self) -> Vec<(DeviceId, MediaStatisticsSnapshot)> {
1341 self.latest_media_statistics
1342 .read()
1343 .expect("SCCP media-statistics lock poisoned")
1344 .iter()
1345 .map(|(device_id, snapshot)| (device_id.clone(), snapshot.clone()))
1346 .collect()
1347 }
1348
1349 pub async fn send(&self, command: Command) -> Result<(), ServerError> {
1356 self.command_tx
1357 .send(ServerCommand::Public(Box::new(command)))
1358 .await
1359 .map_err(|_| ServerError::Stopped)
1360 }
1361
1362 pub async fn send_confirmed(&self, command: Command) -> Result<(), ServerError> {
1370 let expires_at = Instant::now() + ORDERING_ACKNOWLEDGEMENT_TIMEOUT;
1371 tokio::time::timeout_at(expires_at, async {
1372 let (written_tx, written_rx) = oneshot::channel();
1373 self.command_tx
1374 .send(ServerCommand::Confirmed {
1375 command: Box::new(command),
1376 written: written_tx,
1377 expires_at,
1378 })
1379 .await
1380 .map_err(|_| ServerError::Stopped)?;
1381 written_rx
1382 .await
1383 .map_err(|_| ServerError::Stopped)?
1384 .map_err(ServerError::CommandWrite)
1385 })
1386 .await
1387 .map_err(|_| ServerError::CommandAcknowledgementTimeout)?
1388 }
1389
1390 pub fn try_send(&self, command: Command) -> Result<(), ServerError> {
1393 self.command_tx
1394 .try_send(ServerCommand::Public(Box::new(command)))
1395 .map_err(|error| match error {
1396 mpsc::error::TrySendError::Full(_) => ServerError::CommandQueueFull,
1397 mpsc::error::TrySendError::Closed(_) => ServerError::Stopped,
1398 })
1399 }
1400
1401 pub async fn offer_incoming_call(
1406 &self,
1407 device_id: DeviceId,
1408 line_instance: LineInstance,
1409 info: CallInfo,
1410 ) -> Result<CallId, ServerError> {
1411 let call_id = self.reserve_call_id();
1412 self.offer_incoming_call_with_id(device_id, line_instance, call_id, info)
1413 .await?;
1414 Ok(call_id)
1415 }
1416
1417 pub fn reserve_call_id(&self) -> CallId {
1422 CallId(self.next_call_id.fetch_add(1, Ordering::Relaxed))
1423 }
1424
1425 pub async fn offer_incoming_call_with_id(
1428 &self,
1429 device_id: DeviceId,
1430 line_instance: LineInstance,
1431 call_id: CallId,
1432 info: CallInfo,
1433 ) -> Result<(), ServerError> {
1434 self.offer_incoming_call_with_id_and_ring(device_id, line_instance, call_id, info, true)
1435 .await
1436 }
1437
1438 pub async fn offer_incoming_call_with_id_and_ring(
1439 &self,
1440 device_id: DeviceId,
1441 line_instance: LineInstance,
1442 call_id: CallId,
1443 info: CallInfo,
1444 audible_ring: bool,
1445 ) -> Result<(), ServerError> {
1446 self.offer_incoming_call_with_id_and_ringer(
1447 device_id,
1448 line_instance,
1449 call_id,
1450 info,
1451 audible_ring.then_some(IncomingRing::default()),
1452 )
1453 .await
1454 }
1455
1456 pub async fn offer_incoming_call_with_id_and_ringer(
1461 &self,
1462 device_id: DeviceId,
1463 line_instance: LineInstance,
1464 call_id: CallId,
1465 info: CallInfo,
1466 ringer: Option<IncomingRing>,
1467 ) -> Result<(), ServerError> {
1468 self.command_tx
1469 .send(ServerCommand::OfferIncoming {
1470 device_id,
1471 line_instance,
1472 call_id,
1473 info,
1474 ringer,
1475 })
1476 .await
1477 .map_err(|_| ServerError::Stopped)?;
1478 Ok(())
1479 }
1480
1481 pub fn try_offer_incoming_call_with_id(
1485 &self,
1486 device_id: DeviceId,
1487 line_instance: LineInstance,
1488 call_id: CallId,
1489 info: CallInfo,
1490 ) -> Result<(), ServerError> {
1491 self.try_offer_incoming_call_with_id_and_ring(device_id, line_instance, call_id, info, true)
1492 }
1493
1494 pub fn try_offer_incoming_call_with_id_and_ring(
1499 &self,
1500 device_id: DeviceId,
1501 line_instance: LineInstance,
1502 call_id: CallId,
1503 info: CallInfo,
1504 audible_ring: bool,
1505 ) -> Result<(), ServerError> {
1506 self.try_offer_incoming_call_with_id_and_ringer(
1507 device_id,
1508 line_instance,
1509 call_id,
1510 info,
1511 audible_ring.then_some(IncomingRing::default()),
1512 )
1513 }
1514
1515 pub fn try_offer_incoming_call_with_id_and_ringer(
1516 &self,
1517 device_id: DeviceId,
1518 line_instance: LineInstance,
1519 call_id: CallId,
1520 info: CallInfo,
1521 ringer: Option<IncomingRing>,
1522 ) -> Result<(), ServerError> {
1523 self.command_tx
1524 .try_send(ServerCommand::OfferIncoming {
1525 device_id,
1526 line_instance,
1527 call_id,
1528 info,
1529 ringer,
1530 })
1531 .map_err(|error| match error {
1532 mpsc::error::TrySendError::Full(_) => ServerError::CommandQueueFull,
1533 mpsc::error::TrySendError::Closed(_) => ServerError::Stopped,
1534 })
1535 }
1536
1537 pub async fn shutdown(&self) -> Result<(), ServerError> {
1543 self.command_tx
1544 .send(ServerCommand::Shutdown)
1545 .await
1546 .map_err(|_| ServerError::Stopped)
1547 }
1548
1549 pub async fn reconfigure(
1555 &self,
1556 definitions: impl IntoIterator<Item = DeviceDefinition>,
1557 ) -> Result<ReconfigureResult, ServerError> {
1558 self.reconfigure_affected(definitions, []).await
1559 }
1560
1561 pub async fn reconfigure_affected(
1566 &self,
1567 definitions: impl IntoIterator<Item = DeviceDefinition>,
1568 affected: impl IntoIterator<Item = DeviceId>,
1569 ) -> Result<ReconfigureResult, ServerError> {
1570 let mut by_id = HashMap::new();
1571 for definition in definitions {
1572 definition.validate()?;
1573 by_id.insert(definition.id.clone(), definition);
1574 }
1575 let (applied_tx, applied_rx) = oneshot::channel();
1576 self.command_tx
1577 .send(ServerCommand::Reconfigure {
1578 definitions: by_id,
1579 affected: affected.into_iter().collect(),
1580 applied: applied_tx,
1581 })
1582 .await
1583 .map_err(|_| ServerError::Stopped)?;
1584 applied_rx.await.map_err(|_| ServerError::Stopped)
1585 }
1586
1587 pub async fn reconfigure_station_policy(
1590 &self,
1591 definitions: impl IntoIterator<Item = DeviceDefinition>,
1592 affected: impl IntoIterator<Item = DeviceId>,
1593 anonymous_hotline: Option<AnonymousHotlineDefinition>,
1594 ) -> Result<ReconfigureResult, ServerError> {
1595 let mut by_id = HashMap::new();
1596 for definition in definitions {
1597 definition.validate()?;
1598 by_id.insert(definition.id.clone(), definition);
1599 }
1600 let (applied_tx, applied_rx) = oneshot::channel();
1601 self.command_tx
1602 .send(ServerCommand::ReconfigureStationPolicy {
1603 definitions: by_id,
1604 affected: affected.into_iter().collect(),
1605 anonymous_hotline,
1606 applied: applied_tx,
1607 })
1608 .await
1609 .map_err(|_| ServerError::Stopped)?;
1610 applied_rx.await.map_err(|_| ServerError::Stopped)
1611 }
1612
1613 pub async fn reconfigure_anonymous_hotline(
1618 &self,
1619 definition: Option<AnonymousHotlineDefinition>,
1620 ) -> Result<usize, ServerError> {
1621 let (applied_tx, applied_rx) = oneshot::channel();
1622 self.command_tx
1623 .send(ServerCommand::ReconfigureAnonymousHotline {
1624 definition,
1625 applied: applied_tx,
1626 })
1627 .await
1628 .map_err(|_| ServerError::Stopped)?;
1629 applied_rx.await.map_err(|_| ServerError::Stopped)
1630 }
1631}
1632
1633#[derive(Debug)]
1643pub struct Server {
1644 listener: Option<TcpListener>,
1645 accepted_rx: mpsc::Receiver<AcceptedStation>,
1646 config: Arc<ServerConfig>,
1647 anonymous_hotline: Arc<RwLock<Option<AnonymousHotlineDefinition>>>,
1648 definitions: Arc<RwLock<HashMap<DeviceId, DeviceDefinition>>>,
1649 sessions: Sessions,
1650 event_tx: mpsc::Sender<Event>,
1651 command_rx: mpsc::Receiver<ServerCommand>,
1652 next_generation: Arc<AtomicU64>,
1653 next_statistics_generation: Arc<AtomicU64>,
1654 next_call_id: Arc<AtomicU64>,
1655 latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
1656 call_answer_order: Arc<RwLock<CallSelectionOrder>>,
1657}
1658
1659type Sessions = Arc<Mutex<HashMap<DeviceId, SessionSender>>>;
1660type CommandWriteConfirmation = oneshot::Sender<Result<(), String>>;
1661
1662#[derive(Clone, Debug)]
1663struct SessionSender {
1664 generation: SessionGeneration,
1665 anonymous_hotline: bool,
1666 tx: mpsc::Sender<SessionCommand>,
1667}
1668
1669#[derive(Debug)]
1670enum ServerCommand {
1671 Public(Box<Command>),
1672 Confirmed {
1673 command: Box<Command>,
1674 written: CommandWriteConfirmation,
1675 expires_at: Instant,
1676 },
1677 OfferIncoming {
1678 device_id: DeviceId,
1679 line_instance: LineInstance,
1680 call_id: CallId,
1681 info: CallInfo,
1682 ringer: Option<IncomingRing>,
1683 },
1684 Reconfigure {
1685 definitions: HashMap<DeviceId, DeviceDefinition>,
1686 affected: HashSet<DeviceId>,
1687 applied: oneshot::Sender<ReconfigureResult>,
1688 },
1689 ReconfigureStationPolicy {
1690 definitions: HashMap<DeviceId, DeviceDefinition>,
1691 affected: HashSet<DeviceId>,
1692 anonymous_hotline: Option<AnonymousHotlineDefinition>,
1693 applied: oneshot::Sender<ReconfigureResult>,
1694 },
1695 ReconfigureAnonymousHotline {
1696 definition: Option<AnonymousHotlineDefinition>,
1697 applied: oneshot::Sender<usize>,
1698 },
1699 Shutdown,
1700}
1701
1702#[derive(Debug)]
1703enum AnonymousHotlineUpdate {
1704 Preserve,
1705 Replace(Option<AnonymousHotlineDefinition>),
1706}
1707
1708#[derive(Debug)]
1709enum SessionCommand {
1710 Public(Box<Command>),
1711 Confirmed {
1712 command: Box<Command>,
1713 written: CommandWriteConfirmation,
1714 expires_at: Instant,
1715 },
1716 OfferIncoming {
1717 line_instance: LineInstance,
1718 call_id: CallId,
1719 info: Box<CallInfo>,
1720 ringer: Option<IncomingRing>,
1721 },
1722 Disconnect,
1723}
1724
1725#[derive(Clone, Debug)]
1726struct SessionCall {
1727 call_id: CallId,
1728 wire_reference: u32,
1729 line_instance: u32,
1730 media: CallMedia,
1731 video_receive: VideoReceive,
1732 video_transmit: VideoTransmit,
1733 state: CallState,
1734 history_disposition: CallHistoryDisposition,
1735 dialed_number: String,
1736 statistics_directory_number: String,
1737 transfer_role: Option<SessionTransferRole>,
1738}
1739
1740#[derive(Clone, Debug, Default)]
1741struct VideoReceive {
1742 generation: u64,
1743 leg: Option<VideoReceiveLeg>,
1744}
1745
1746#[derive(Clone, Debug)]
1747struct VideoReceiveLeg {
1748 request: MediaRequestIdentity,
1749 conference_id: ConferenceId,
1750 codec: Codec,
1751 requested_address_type: IpAddressType,
1752 state: MediaChannelState,
1753 deadline: Option<Instant>,
1754}
1755
1756#[derive(Debug)]
1757struct ExpiredVideoReceive {
1758 call_id: CallId,
1759 codec: Codec,
1760 passthrough_party_id: PassthroughPartyId,
1761 close: ServerMessage,
1762}
1763
1764#[derive(Clone, Debug, Default)]
1765struct VideoTransmit {
1766 generation: u64,
1767 leg: Option<VideoTransmitLeg>,
1768}
1769
1770#[derive(Clone, Debug)]
1771struct VideoTransmitLeg {
1772 request: MediaRequestIdentity,
1773 conference_id: ConferenceId,
1774 codec: Codec,
1775 address_type: IpAddressType,
1776 state: MediaChannelState,
1777 deadline: Option<Instant>,
1778}
1779
1780#[derive(Debug)]
1781struct ExpiredVideoTransmit {
1782 call_id: CallId,
1783 codec: Codec,
1784 passthrough_party_id: PassthroughPartyId,
1785 stop: ServerMessage,
1786}
1787
1788#[derive(Clone, Debug)]
1789struct CallMedia {
1790 generation: u64,
1791 codec: Codec,
1792 packet_ms: u32,
1793 max_frames_per_packet: u32,
1794 receive: MediaLeg,
1795 transmit: MediaLeg,
1796 coupled_transmit_endpoint: Option<MediaEndpoint>,
1800 requested: bool,
1801}
1802
1803impl CallMedia {
1804 fn new(codec: Codec) -> Self {
1805 Self {
1806 generation: 0,
1807 codec,
1808 packet_ms: DEFAULT_AUDIO_PACKET_MS,
1809 max_frames_per_packet: DEFAULT_AUDIO_MAX_FRAMES_PER_PACKET,
1810 receive: MediaLeg::default(),
1811 transmit: MediaLeg::default(),
1812 coupled_transmit_endpoint: None,
1813 requested: false,
1814 }
1815 }
1816}
1817
1818#[derive(Clone, Debug, Default)]
1819struct MediaLeg {
1820 request: Option<MediaRequestIdentity>,
1821 telephone_event_payload: u8,
1822 peer: Option<MediaEndpoint>,
1823 state: MediaChannelState,
1824 deadline: Option<Instant>,
1825}
1826
1827#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1828enum SessionTransferRole {
1829 Source { consultation_call_id: CallId },
1830 Consultation { source_call_id: CallId },
1831}
1832
1833#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1834enum MediaChannelState {
1835 #[default]
1836 Closed,
1837 Opening,
1838 Open,
1839}
1840
1841impl MediaChannelState {
1842 const fn is_open(self) -> bool {
1843 matches!(self, Self::Open)
1844 }
1845}
1846
1847fn validate_server_config(config: &ServerConfig) -> Result<(), ServerError> {
1848 if digit_character(config.dial_terminator).is_none() {
1849 return Err(ServerError::InvalidConfig(
1850 "dial terminator must be one DTMF character".into(),
1851 ));
1852 }
1853 if !(-840..=840).contains(&config.timezone_offset_minutes) {
1854 return Err(ServerError::InvalidConfig(
1855 "timezone offset must be between -840 and 840 minutes".into(),
1856 ));
1857 }
1858 if config.keepalive_seconds < 5 || config.secondary_keepalive_seconds < 5 {
1859 return Err(ServerError::InvalidConfig(
1860 "primary and secondary keepalive intervals must be at least 5 seconds".into(),
1861 ));
1862 }
1863 if config.advertised_address.is_unspecified()
1864 || config.advertised_address.is_multicast()
1865 || config
1866 .advertised_ipv6_address
1867 .is_some_and(|address| address.is_unspecified() || address.is_multicast())
1868 {
1869 return Err(ServerError::InvalidConfig(
1870 "advertised fallback addresses must be unicast".into(),
1871 ));
1872 }
1873 if !(MIN_REGISTRATION_BACKOFF..=MAX_REGISTRATION_BACKOFF)
1874 .contains(&config.registration_tokens.backoff)
1875 {
1876 return Err(ServerError::InvalidConfig(
1877 "registration-token backoff must be between 30 and 86400 seconds".into(),
1878 ));
1879 }
1880 if config.registration_tokens.server_priority == 0 {
1881 return Err(ServerError::InvalidConfig(
1882 "server priority must be nonzero".into(),
1883 ));
1884 }
1885 if config.signaling_servers.len() > crate::message::MAX_SIGNALING_SERVERS {
1886 return Err(ServerError::InvalidConfig(format!(
1887 "at most {} signaling servers may be advertised",
1888 crate::message::MAX_SIGNALING_SERVERS
1889 )));
1890 }
1891 let mut priorities = HashSet::new();
1892 for server in &config.signaling_servers {
1893 if server.priority == 0 || !priorities.insert(server.priority) {
1894 return Err(ServerError::InvalidConfig(
1895 "signaling server priorities must be nonzero and unique".into(),
1896 ));
1897 }
1898 if server.name.is_empty()
1899 || server.name.len() >= 48
1900 || server.name.chars().any(char::is_control)
1901 || server.address.is_unspecified()
1902 || server.address.is_multicast()
1903 || server.clear_port.is_none() && server.secure_port.is_none()
1904 {
1905 return Err(ServerError::InvalidConfig(
1906 "each signaling server requires a name, unicast address, and at least one port"
1907 .into(),
1908 ));
1909 }
1910 }
1911 if !config.signaling_servers.is_empty()
1912 && !priorities.contains(&config.registration_tokens.server_priority)
1913 {
1914 return Err(ServerError::InvalidConfig(
1915 "the local server priority must occur in the advertised server list".into(),
1916 ));
1917 }
1918 config
1919 .signaling_qos
1920 .validate()
1921 .map_err(|error| ServerError::InvalidConfig(error.to_string()))
1922}
1923
1924impl Server {
1925 pub async fn bind(
1934 config: ServerConfig,
1935 definitions: impl IntoIterator<Item = DeviceDefinition>,
1936 ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>), ServerError> {
1937 validate_server_config(&config)?;
1938 let listener = TcpListener::bind(config.bind)
1939 .await
1940 .map_err(ServerError::Bind)?;
1941 if let Ok(local) = listener.local_addr() {
1942 match SignalingSocket::capture(&listener, local) {
1943 Ok(socket) => report_socket_qos(None, local, socket.apply(config.signaling_qos)),
1944 Err(error) => {
1945 warn!(%local, %error, "unable to retain signaling listener QoS control")
1946 }
1947 }
1948 }
1949 let (server, handle, events, _) = Self::build(config, definitions, Some(listener))?;
1950 Ok((server, handle, events))
1951 }
1952
1953 pub fn with_ingress(
1961 config: ServerConfig,
1962 definitions: impl IntoIterator<Item = DeviceDefinition>,
1963 ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>, ServerIngress), ServerError> {
1964 Self::build(config, definitions, None)
1965 }
1966
1967 fn build(
1968 config: ServerConfig,
1969 definitions: impl IntoIterator<Item = DeviceDefinition>,
1970 listener: Option<TcpListener>,
1971 ) -> Result<(Self, ServerHandle, mpsc::Receiver<Event>, ServerIngress), ServerError> {
1972 validate_server_config(&config)?;
1973 let mut by_id = HashMap::new();
1974 for definition in definitions {
1975 definition.validate()?;
1976 by_id.insert(definition.id.clone(), definition);
1977 }
1978 let (event_tx, event_rx) = mpsc::channel(EVENT_CAPACITY);
1979 let (command_tx, command_rx) = mpsc::channel(COMMAND_CAPACITY);
1980 let (ingress, accepted_rx) =
1981 ServerIngress::channel(SESSION_ACCEPT_CAPACITY, config.signaling_qos);
1982 let next_call_id = Arc::new(AtomicU64::new(1));
1983 let latest_media_statistics = Arc::new(RwLock::new(HashMap::new()));
1984 let call_answer_order = Arc::new(RwLock::new(config.call_answer_order));
1985 let anonymous_hotline = Arc::new(RwLock::new(config.anonymous_hotline.clone()));
1986 let handle = ServerHandle {
1987 command_tx,
1988 next_call_id: Arc::clone(&next_call_id),
1989 latest_media_statistics: Arc::clone(&latest_media_statistics),
1990 call_answer_order: Arc::clone(&call_answer_order),
1991 };
1992 Ok((
1993 Self {
1994 listener,
1995 accepted_rx,
1996 config: Arc::new(config),
1997 anonymous_hotline,
1998 definitions: Arc::new(RwLock::new(by_id)),
1999 sessions: Arc::new(Mutex::new(HashMap::new())),
2000 event_tx,
2001 command_rx,
2002 next_generation: Arc::new(AtomicU64::new(1)),
2003 next_statistics_generation: Arc::new(AtomicU64::new(1)),
2004 next_call_id,
2005 latest_media_statistics,
2006 call_answer_order,
2007 },
2008 handle,
2009 event_rx,
2010 ingress,
2011 ))
2012 }
2013
2014 pub fn local_addr(&self) -> Result<SocketAddr, ServerError> {
2021 self.listener
2022 .as_ref()
2023 .ok_or_else(|| ServerError::InvalidConfig("server has no bound listener".into()))?
2024 .local_addr()
2025 .map_err(ServerError::Io)
2026 }
2027
2028 pub async fn run(mut self) -> Result<(), ServerError> {
2039 if let Some(listener) = &self.listener {
2040 info!(bind = %listener.local_addr()?, "SCCP server listening");
2041 }
2042 loop {
2043 tokio::select! {
2044 accepted = accept_clear(self.listener.as_ref(), self.config.signaling_qos) => {
2045 self.start_session(accepted?);
2046 }
2047 accepted = self.accepted_rx.recv(), if !self.accepted_rx.is_closed() => {
2048 if let Some(accepted) = accepted {
2049 self.start_session(accepted);
2050 }
2051 }
2052 command = self.command_rx.recv() => {
2053 match command {
2054 Some(ServerCommand::Public(command)) => {
2055 if let Err(error) = self.dispatch_public(*command).await {
2056 warn!(%error, "discarding SCCP command for a retired session");
2057 }
2058 }
2059 Some(ServerCommand::Confirmed { command, written, expires_at }) => {
2060 self.dispatch_confirmed(command, written, expires_at).await;
2061 }
2062 Some(ServerCommand::OfferIncoming { device_id, line_instance, call_id, info, ringer }) => {
2063 if let Err(error) = self.dispatch(&device_id, SessionCommand::OfferIncoming { line_instance, call_id, info: Box::new(info), ringer }).await {
2064 warn!(%error, "discarding incoming offer for a retired session");
2065 }
2066 }
2067 Some(ServerCommand::Reconfigure { definitions, affected, applied }) => {
2068 let result = self
2069 .apply_station_policy(
2070 definitions,
2071 affected,
2072 AnonymousHotlineUpdate::Preserve,
2073 )
2074 .await;
2075 let _ = applied.send(result);
2076 }
2077 Some(ServerCommand::ReconfigureStationPolicy {
2078 definitions,
2079 affected,
2080 anonymous_hotline,
2081 applied,
2082 }) => {
2083 let result = self
2084 .apply_station_policy(
2085 definitions,
2086 affected,
2087 AnonymousHotlineUpdate::Replace(anonymous_hotline),
2088 )
2089 .await;
2090 let _ = applied.send(result);
2091 }
2092 Some(ServerCommand::ReconfigureAnonymousHotline { definition, applied }) => {
2093 let sessions = self.sessions.lock().await;
2094 let changed = {
2095 let mut current = self
2096 .anonymous_hotline
2097 .write()
2098 .expect("SCCP anonymous-hotline lock poisoned");
2099 if *current == definition {
2100 false
2101 } else {
2102 *current = definition;
2103 true
2104 }
2105 };
2106 let affected = if changed {
2107 sessions
2108 .values()
2109 .filter(|session| session.anonymous_hotline)
2110 .cloned()
2111 .collect::<Vec<_>>()
2112 } else {
2113 Vec::new()
2114 };
2115 drop(sessions);
2116 let count = affected.len();
2117 for session in affected {
2118 let _ = session.tx.send(SessionCommand::Disconnect).await;
2119 }
2120 let _ = applied.send(count);
2121 }
2122 Some(ServerCommand::Shutdown) | None => {
2123 let sessions: Vec<_> = self.sessions.lock().await.values().cloned().collect();
2124 for session in sessions { let _ = session.tx.send(SessionCommand::Disconnect).await; }
2125 return Ok(());
2126 }
2127 }
2128 }
2129 }
2130 }
2131 }
2132
2133 fn start_session(&self, accepted: AcceptedStation) {
2134 let AcceptedStation {
2135 stream,
2136 peer,
2137 local,
2138 transport,
2139 socket_qos,
2140 } = accepted;
2141 let context = SessionContext {
2142 peer,
2143 local,
2144 transport,
2145 socket_qos,
2146 config: Arc::clone(&self.config),
2147 definitions: Arc::clone(&self.definitions),
2148 anonymous_hotline: Arc::clone(&self.anonymous_hotline),
2149 sessions: Arc::clone(&self.sessions),
2150 event_tx: self.event_tx.clone(),
2151 next_generation: Arc::clone(&self.next_generation),
2152 next_statistics_generation: Arc::clone(&self.next_statistics_generation),
2153 next_call_id: Arc::clone(&self.next_call_id),
2154 latest_media_statistics: Arc::clone(&self.latest_media_statistics),
2155 call_answer_order: Arc::clone(&self.call_answer_order),
2156 };
2157 let error_tx = self.event_tx.clone();
2158 tokio::spawn(async move {
2159 match run_session(stream, context).await {
2160 Ok(()) => debug!(%peer, "SCCP session ended cleanly"),
2161 Err(error) => {
2162 warn!(%peer, %error, "SCCP session ended with an error");
2163 let _ = error_tx
2164 .send(Event::SessionError {
2165 peer,
2166 error: error.to_string(),
2167 })
2168 .await;
2169 }
2170 }
2171 });
2172 }
2173
2174 async fn dispatch_public(&self, command: Command) -> Result<(), ServerError> {
2175 let device_id = command.device_id.clone();
2176 self.dispatch(&device_id, SessionCommand::Public(Box::new(command)))
2177 .await
2178 }
2179
2180 async fn dispatch_confirmed(
2181 &self,
2182 command: Box<Command>,
2183 written: CommandWriteConfirmation,
2184 expires_at: Instant,
2185 ) {
2186 let device_id = command.device_id.clone();
2187 if confirmed_command_expired(&written, expires_at) {
2188 reject_expired_confirmed_command(written);
2189 return;
2190 }
2191 let tx = self
2192 .sessions
2193 .lock()
2194 .await
2195 .get(&device_id)
2196 .map(|session| session.tx.clone());
2197 let Some(tx) = tx else {
2198 let _ = written.send(Err(ServerError::DeviceNotConnected(device_id).to_string()));
2199 return;
2200 };
2201 if let Err(error) = tx
2202 .send(SessionCommand::Confirmed {
2203 command,
2204 written,
2205 expires_at,
2206 })
2207 .await
2208 {
2209 let SessionCommand::Confirmed { written, .. } = error.0 else {
2210 unreachable!("confirmed dispatch returned a different command variant")
2211 };
2212 let _ = written.send(Err(ServerError::DeviceNotConnected(device_id).to_string()));
2213 }
2214 }
2215
2216 async fn dispatch(
2217 &self,
2218 device_id: &DeviceId,
2219 command: SessionCommand,
2220 ) -> Result<(), ServerError> {
2221 let tx = self
2222 .sessions
2223 .lock()
2224 .await
2225 .get(device_id)
2226 .map(|s| s.tx.clone())
2227 .ok_or_else(|| ServerError::DeviceNotConnected(device_id.clone()))?;
2228 tx.send(command)
2229 .await
2230 .map_err(|_| ServerError::DeviceNotConnected(device_id.clone()))
2231 }
2232
2233 async fn apply_station_policy(
2234 &self,
2235 definitions: HashMap<DeviceId, DeviceDefinition>,
2236 affected: HashSet<DeviceId>,
2237 anonymous_hotline: AnonymousHotlineUpdate,
2238 ) -> ReconfigureResult {
2239 let sessions = self.sessions.lock().await;
2243 let result = {
2244 let mut current = self
2245 .definitions
2246 .write()
2247 .expect("SCCP definitions lock poisoned");
2248 let result = reconfigure_result(¤t, &definitions, &affected);
2249 *current = definitions;
2250 result
2251 };
2252 let anonymous_changed = match anonymous_hotline {
2253 AnonymousHotlineUpdate::Preserve => false,
2254 AnonymousHotlineUpdate::Replace(next) => {
2255 let mut current = self
2256 .anonymous_hotline
2257 .write()
2258 .expect("SCCP anonymous-hotline lock poisoned");
2259 if *current == next {
2260 false
2261 } else {
2262 *current = next;
2263 true
2264 }
2265 }
2266 };
2267 let affected_devices = result
2268 .disconnected_devices()
2269 .chain(affected.iter())
2270 .cloned()
2271 .collect::<HashSet<_>>();
2272 let affected_sessions = sessions
2273 .iter()
2274 .filter(|(device, session)| {
2275 affected_devices.contains(*device)
2276 || (anonymous_changed && session.anonymous_hotline)
2277 })
2278 .map(|(_, session)| session.clone())
2279 .collect::<Vec<_>>();
2280 drop(sessions);
2281 for session in affected_sessions {
2282 let _ = session.tx.send(SessionCommand::Disconnect).await;
2283 }
2284 result
2285 }
2286}
2287
2288fn confirmed_command_expired(written: &CommandWriteConfirmation, expires_at: Instant) -> bool {
2289 written.is_closed() || Instant::now() >= expires_at
2290}
2291
2292fn reject_expired_confirmed_command(written: CommandWriteConfirmation) {
2293 let _ = written.send(Err(ServerError::CommandAcknowledgementTimeout.to_string()));
2294}
2295
2296fn prepare_session_command(
2297 command: SessionCommand,
2298) -> Option<(SessionCommand, Option<CommandWriteConfirmation>)> {
2299 match command {
2300 SessionCommand::Confirmed {
2301 command,
2302 written,
2303 expires_at,
2304 } => {
2305 if confirmed_command_expired(&written, expires_at) {
2306 reject_expired_confirmed_command(written);
2307 None
2308 } else {
2309 Some((SessionCommand::Public(command), Some(written)))
2310 }
2311 }
2312 command => Some((command, None)),
2313 }
2314}
2315
2316fn reconfigure_result(
2317 current: &HashMap<DeviceId, DeviceDefinition>,
2318 next: &HashMap<DeviceId, DeviceDefinition>,
2319 affected: &HashSet<DeviceId>,
2320) -> ReconfigureResult {
2321 let mut result = ReconfigureResult::default();
2322 for (device, definition) in next {
2323 match current.get(device) {
2324 None => result.added.push(device.clone()),
2325 Some(previous) if previous != definition => result.changed.push(device.clone()),
2326 Some(_) => {}
2327 }
2328 }
2329 let explicitly_changed: Vec<_> = affected
2330 .iter()
2331 .filter(|device| {
2332 current.contains_key(*device)
2333 && next.contains_key(*device)
2334 && !result.changed.contains(*device)
2335 })
2336 .cloned()
2337 .collect();
2338 result.changed.extend(explicitly_changed);
2339 result.removed.extend(
2340 current
2341 .keys()
2342 .filter(|device| !next.contains_key(*device))
2343 .cloned(),
2344 );
2345 result.added.sort();
2346 result.changed.sort();
2347 result.removed.sort();
2348 result
2349}
2350
2351fn command_call_id(command: &Command) -> Option<CallId> {
2352 match &command.action {
2353 CommandAction::BeginCall { call_id, .. }
2354 | CommandAction::SetCallInfo { call_id, .. }
2355 | CommandAction::CommitOutboundCall { call_id, .. }
2356 | CommandAction::PresentOutboundProceeding { call_id, .. }
2357 | CommandAction::PresentOutboundRinging { call_id, .. }
2358 | CommandAction::SetCallState { call_id, .. }
2359 | CommandAction::SetCallSelected { call_id, .. }
2360 | CommandAction::DisplayPrompt { call_id, .. }
2361 | CommandAction::ClearPrompt { call_id, .. }
2362 | CommandAction::SetRecordingStatus { call_id, .. }
2363 | CommandAction::ShowConferenceParticipantActions { call_id, .. }
2364 | CommandAction::StartTone { call_id, .. }
2365 | CommandAction::StartRinging { call_id, .. }
2366 | CommandAction::StopRinging { call_id, .. }
2367 | CommandAction::OpenReceiveChannel { call_id, .. }
2368 | CommandAction::OpenMultimediaReceiveChannel { call_id, .. }
2369 | CommandAction::CloseMultimediaReceiveChannel { call_id, .. }
2370 | CommandAction::StartMultimediaTransmission { call_id, .. }
2371 | CommandAction::StopMultimediaTransmission { call_id, .. }
2372 | CommandAction::SetMultimediaTransmitBitRate { call_id, .. }
2373 | CommandAction::NotifyMultimediaTransmitBitRate { call_id, .. }
2374 | CommandAction::ControlMultimediaTransmission { call_id, .. }
2375 | CommandAction::OpenOutboundMedia { call_id, .. }
2376 | CommandAction::CloseReceiveChannel { call_id, .. }
2377 | CommandAction::StartMedia { call_id, .. }
2378 | CommandAction::StartMulticastReception { call_id, .. }
2379 | CommandAction::StopMulticastReception { call_id, .. }
2380 | CommandAction::StartMulticastTransmission { call_id, .. }
2381 | CommandAction::StopMulticastTransmission { call_id, .. }
2382 | CommandAction::StopMedia { call_id, .. }
2383 | CommandAction::CloseCall { call_id, .. } => Some(*call_id),
2384 CommandAction::BeginTransfer { source_call_id, .. } => Some(*source_call_id),
2385 CommandAction::SetMwi { .. }
2386 | CommandAction::SetStatusMessage { .. }
2387 | CommandAction::SetMicrophoneMode { .. }
2388 | CommandAction::ResetDevice { .. }
2389 | CommandAction::SetForwardStatus { .. }
2390 | CommandAction::SetFeatureStatus { .. }
2391 | CommandAction::SetDoNotDisturbStatus { .. }
2392 | CommandAction::SetMobilityAppearance { .. }
2393 | CommandAction::SetBlfStatus { .. }
2394 | CommandAction::ShowParkingMenu { .. }
2395 | CommandAction::ShowConferenceList { .. }
2396 | CommandAction::ShowTextService { .. }
2397 | CommandAction::ShowInputService { .. }
2398 | CommandAction::ExecutePhoneActions { .. }
2399 | CommandAction::ShowImageService { .. }
2400 | CommandAction::ShowStatusService { .. }
2401 | CommandAction::SetBackgroundImage { .. }
2402 | CommandAction::PreviewBackgroundImage { .. }
2403 | CommandAction::SetRingtone { .. }
2404 | CommandAction::StartAnnouncement { .. }
2405 | CommandAction::StopAnnouncement { .. }
2406 | CommandAction::AnnouncementFinish { .. }
2407 | CommandAction::DisconnectDevice { .. } => None,
2408 }
2409}
2410
2411async fn accept_clear(
2412 listener: Option<&TcpListener>,
2413 signaling_qos: SignalingQos,
2414) -> Result<AcceptedStation, ServerError> {
2415 let Some(listener) = listener else {
2416 return std::future::pending().await;
2417 };
2418 let (stream, peer) = listener.accept().await?;
2419 stream.set_nodelay(true)?;
2420 let local = stream.local_addr()?;
2421 let socket_qos = match SignalingSocket::capture(&stream, local) {
2422 Ok(socket) => {
2423 report_socket_qos(None, peer, socket.apply(signaling_qos));
2424 Some(Box::new(socket) as Box<dyn StationSocketQos>)
2425 }
2426 Err(error) => {
2427 warn!(%peer, %error, "unable to retain signaling socket QoS control");
2428 None
2429 }
2430 };
2431 Ok(AcceptedStation {
2432 stream: Box::new(stream),
2433 peer,
2434 local,
2435 transport: StationTransport::Clear,
2436 socket_qos,
2437 })
2438}
2439
2440fn report_socket_qos(device_id: Option<&DeviceId>, endpoint: SocketAddr, report: SocketQosReport) {
2441 for failure in report.failures() {
2442 match device_id {
2443 Some(device_id) => {
2444 warn!(%device_id, %endpoint, %failure, "signaling socket marking unavailable")
2445 }
2446 None => warn!(%endpoint, %failure, "signaling socket marking unavailable"),
2447 }
2448 }
2449}
2450
2451fn allocate_session_generation(
2452 next_generation: &AtomicU64,
2453) -> Result<SessionGeneration, ServerError> {
2454 let generation = next_generation
2455 .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
2456 SessionGeneration::new(current).and_then(|_| current.checked_add(1))
2457 })
2458 .map_err(|_| ServerError::SessionGenerationExhausted)?;
2459 SessionGeneration::new(generation).ok_or(ServerError::SessionGenerationExhausted)
2460}
2461
2462const fn transport_allowed(
2463 requirement: StationTransportRequirement,
2464 transport: StationTransport,
2465) -> bool {
2466 matches!(
2467 (requirement, transport),
2468 (StationTransportRequirement::Either, _)
2469 | (StationTransportRequirement::Clear, StationTransport::Clear)
2470 | (
2471 StationTransportRequirement::Secure,
2472 StationTransport::Secure
2473 )
2474 )
2475}
2476
2477#[derive(Debug)]
2478struct SessionContext {
2479 peer: SocketAddr,
2480 local: SocketAddr,
2481 transport: StationTransport,
2482 socket_qos: Option<Box<dyn StationSocketQos>>,
2483 config: Arc<ServerConfig>,
2484 definitions: Arc<RwLock<HashMap<DeviceId, DeviceDefinition>>>,
2485 anonymous_hotline: Arc<RwLock<Option<AnonymousHotlineDefinition>>>,
2486 sessions: Sessions,
2487 event_tx: mpsc::Sender<Event>,
2488 next_generation: Arc<AtomicU64>,
2489 next_statistics_generation: Arc<AtomicU64>,
2490 next_call_id: Arc<AtomicU64>,
2491 latest_media_statistics: Arc<RwLock<HashMap<DeviceId, MediaStatisticsSnapshot>>>,
2492 call_answer_order: Arc<RwLock<CallSelectionOrder>>,
2493}
2494
2495#[derive(Debug)]
2496struct SessionState {
2497 device: DeviceDefinition,
2498 registration: DeviceRegistration,
2499 features: PhoneFeatures,
2500 generation: SessionGeneration,
2501 calls_by_id: HashMap<CallId, SessionCall>,
2502 calls_by_wire: HashMap<u32, CallId>,
2503 media_capabilities: StationMediaCapabilities,
2504 next_media_token: Option<MediaRequestToken>,
2505 next_multicast_generation: u64,
2506 multicast: HashMap<MulticastKey, MulticastSession>,
2507 pending_connection_statistics: HashMap<u32, PendingConnectionStatistics>,
2508 statistics_references: HashSet<u32>,
2509 cancelled_calls: HashSet<CallId>,
2510 last_number_by_line: HashMap<u32, String>,
2511 forwarding_by_line: HashMap<u32, SessionForwarding>,
2512 feature_states: HashMap<u32, SessionFeatureState>,
2513 mwi_by_line: HashMap<u32, bool>,
2514 mobility_appearances: HashMap<u32, LineAppearance>,
2515 active_key_mode: KeyMode,
2516 active_call_id: Option<CallId>,
2517 pending_parking_menu: Option<PendingParkingMenu>,
2518 persistent_status_message: bool,
2519 headset_enabled: bool,
2520 media_path_states:
2521 HashMap<crate::message::values::MediaPathId, crate::message::values::MediaPathEvent>,
2522 pending_media_path_release: Option<PendingMediaPathRelease>,
2523}
2524
2525#[derive(Clone, Copy, Debug)]
2526struct PendingMediaPathRelease {
2527 call_id: CallId,
2528 path: crate::message::values::MediaPathId,
2529 deadline: Instant,
2530}
2531
2532#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2533struct MulticastKey {
2534 conference_id: ConferenceId,
2535 call_id: CallId,
2536}
2537
2538#[derive(Clone, Debug)]
2539struct MulticastSession {
2540 wire_call_reference: u32,
2541 receive: Option<MulticastReceive>,
2542 transmit: Option<MulticastTransmit>,
2543}
2544
2545#[derive(Clone, Debug)]
2546struct MulticastReceive {
2547 request: MediaRequestIdentity,
2548 route: MulticastMediaRoute,
2549 state: MulticastReceiveState,
2550}
2551
2552#[derive(Clone, Debug)]
2553enum MulticastReceiveState {
2554 AwaitingAcknowledgement { deadline: Instant },
2555 Open,
2556}
2557
2558#[derive(Clone, Debug)]
2559struct MulticastTransmit {
2560 request: MediaRequestIdentity,
2561 route: MulticastMediaRoute,
2562}
2563
2564impl SessionState {
2565 fn station_context(&self) -> StationSessionContext {
2566 StationSessionContext::new(self.registration.protocol, self.features)
2567 }
2568}
2569
2570#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2571struct SessionFeatureState {
2572 button_type: ButtonType,
2573 state: u32,
2574}
2575
2576#[derive(Clone, Debug)]
2577struct PendingConnectionStatistics {
2578 session_generation: SessionGeneration,
2579 request_generation: u64,
2580 call_id: CallId,
2581 line_instance: u32,
2582 codec: Codec,
2583 packet_ms: u32,
2584 max_frames_per_packet: u32,
2585 receive_peer: Option<MediaEndpoint>,
2586 transmit_peer: Option<MediaEndpoint>,
2587 directory_number: String,
2588 processing: StatisticsProcessing,
2589 expires_at: Instant,
2590}
2591
2592#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2593struct PendingParkingMenu {
2594 instance: u32,
2595 transaction_id: u32,
2596}
2597
2598#[derive(Clone, Debug, Default)]
2599struct SessionForwarding {
2600 all: Option<String>,
2601 busy: Option<String>,
2602 no_answer: Option<String>,
2603}
2604
2605async fn run_session(
2606 mut stream: Box<dyn StationIo>,
2607 context: SessionContext,
2608) -> Result<(), ServerError> {
2609 let (session_tx, mut session_rx) = mpsc::channel(SESSION_COMMAND_CAPACITY);
2610 let mut decoder = FrameDecoder::new();
2611 let mut read_buffer = [0_u8; 4096];
2612 let mut state: Option<SessionState> = None;
2613 let mut last_keepalive = Instant::now();
2614 let mut session_deadlines = tokio::time::interval(Duration::from_millis(100));
2615 session_deadlines.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
2616 let keepalive_seconds = if context.config.registration_tokens.server_priority == 1 {
2617 context.config.keepalive_seconds
2618 } else {
2619 context.config.secondary_keepalive_seconds
2620 };
2621 let keepalive_timeout = Duration::from_secs(u64::from(keepalive_seconds) * 3);
2622
2623 loop {
2624 tokio::select! {
2625 read = stream.read(&mut read_buffer) => {
2626 let count = read?;
2627 if count == 0 { break; }
2628 for frame in decoder.push(&read_buffer[..count])? {
2629 let decode_protocol = state
2630 .as_ref()
2631 .map_or(ProtocolVersion::V3, |state| state.registration.protocol);
2632 let message_id = frame.message_id;
2633 let message = match ClientMessage::decode_with_version(frame, decode_protocol) {
2634 Ok(message) => message,
2635 Err(error) if message_id != crate::message::id::REGISTER => {
2636 let device_id = state.as_ref().map(|state| state.device.id.clone());
2637 warn!(peer = %context.peer, message_id = format_args!("0x{message_id:04x}"), %error, "ignoring malformed SCCP application message");
2638 let _ = context.event_tx.send(Event::ProtocolWarning {
2639 peer: context.peer,
2640 device_id,
2641 message_id,
2642 error: error.to_string(),
2643 }).await;
2644 continue;
2645 }
2646 Err(error) => return Err(error.into()),
2647 };
2648 if let ClientMessage::Register(registration) = &message {
2649 if state.is_some() {
2650 return Err(ServerError::Protocol(CodecError::InvalidDefinition("duplicate REGISTER on one TCP session".into())));
2651 }
2652 let mut sessions = context.sessions.lock().await;
2653 let configured = context
2654 .definitions
2655 .read()
2656 .expect("SCCP definitions lock poisoned")
2657 .get(®istration.device_id)
2658 .cloned();
2659 let anonymous_hotline = configured.is_none();
2660 let definition = configured.or_else(|| {
2661 context
2662 .anonymous_hotline
2663 .read()
2664 .expect("SCCP anonymous-hotline lock poisoned")
2665 .as_ref()
2666 .map(|hotline| {
2667 hotline.device_definition(registration.device_id.clone())
2668 })
2669 });
2670 let Some(definition) = definition else {
2671 drop(sessions);
2672 send_message(&mut stream, &ServerMessage::RegisterReject { reason: "Device not configured".into() }, ProtocolVersion::V17).await?;
2673 return Ok(());
2674 };
2675 if !transport_allowed(definition.transport, context.transport) {
2676 drop(sessions);
2677 send_message(
2678 &mut stream,
2679 &ServerMessage::RegisterReject {
2680 reason: "Device transport not permitted".into(),
2681 },
2682 ProtocolVersion::V17,
2683 )
2684 .await?;
2685 return Ok(());
2686 }
2687 let protocol = ProtocolVersion::negotiate(registration.advertised_protocol)?;
2688 if canonical_ip_address(context.peer.ip()).is_ipv6()
2689 && protocol < ProtocolVersion::V17
2690 {
2691 drop(sessions);
2692 send_message(
2693 &mut stream,
2694 &ServerMessage::RegisterReject {
2695 reason: "IPv6 requires protocol v17".into(),
2696 },
2697 protocol,
2698 )
2699 .await?;
2700 return Ok(());
2701 }
2702 let features = registration.features;
2703 let generation = allocate_session_generation(&context.next_generation)?;
2704 if let Some(socket_qos) = &context.socket_qos {
2705 let signaling_qos = definition
2706 .signaling_qos
2707 .unwrap_or(context.config.signaling_qos);
2708 report_socket_qos(
2709 Some(®istration.device_id),
2710 context.peer,
2711 socket_qos.apply(signaling_qos),
2712 );
2713 }
2714 let device_registration = DeviceRegistration {
2715 id: registration.device_id.clone(), peer: context.peer,
2716 transport: context.transport,
2717 reported_address: registration.reported_address,
2718 reported_ipv6_address: registration.reported_ipv6_address,
2719 device_type: registration.device_type,
2720 protocol, firmware: registration.firmware.clone(),
2721 };
2722 let previous = sessions.insert(
2723 registration.device_id.clone(), SessionSender {
2724 generation,
2725 anonymous_hotline,
2726 tx: session_tx.clone(),
2727 }
2728 );
2729 drop(sessions);
2730 if let Some(previous) = previous { let _ = previous.tx.send(SessionCommand::Disconnect).await; }
2731 send_message(
2732 &mut stream,
2733 &ServerMessage::RegisterAck {
2734 keepalive_seconds: context.config.keepalive_seconds,
2735 secondary_keepalive_seconds: context.config.secondary_keepalive_seconds,
2736 protocol,
2737 features: PhoneFeatures::empty(),
2738 date_template: context.config.date_template.clone(),
2739 },
2740 protocol,
2741 )
2742 .await?;
2743 send_message(&mut stream, &ServerMessage::CapabilitiesRequest, protocol).await?;
2744 context.event_tx.send(Event::device(
2745 registration.device_id.clone(),
2746 generation,
2747 DeviceEventKind::Registered(device_registration.clone()),
2748 )).await.map_err(|_| ServerError::Stopped)?;
2749 info!(device_id = %registration.device_id, %protocol, peer = %context.peer, "SCCP device registered");
2750 state = Some(SessionState { device: definition, registration: device_registration, features, generation, calls_by_id: HashMap::new(), calls_by_wire: HashMap::new(), media_capabilities: StationMediaCapabilities::default(), next_media_token: MediaRequestToken::new(1), next_multicast_generation: 0, multicast: HashMap::new(), pending_connection_statistics: HashMap::new(), statistics_references: HashSet::new(), cancelled_calls: HashSet::new(), last_number_by_line: HashMap::new(), forwarding_by_line: HashMap::new(), feature_states: HashMap::new(), mwi_by_line: HashMap::new(), mobility_appearances: HashMap::new(), active_key_mode: KeyMode::OnHook, active_call_id: None, pending_parking_menu: None, persistent_status_message: false, headset_enabled: false, media_path_states: HashMap::new(), pending_media_path_release: None });
2751 last_keepalive = Instant::now();
2752 } else if let Some(state) = state.as_mut() {
2753 if matches!(message, ClientMessage::KeepAlive) { last_keepalive = Instant::now(); }
2754 handle_client_message(&mut stream, state, message, &context).await?;
2755 } else {
2756 handle_pre_registration_message(&mut stream, message, &context).await?;
2757 }
2758 }
2759 }
2760 command = session_rx.recv() => {
2761 match command {
2762 Some(command) => {
2763 let Some(state) = state.as_mut() else { continue };
2764 let Some((command, written)) = prepare_session_command(command) else {
2765 continue;
2766 };
2767 match handle_session_command(&mut stream, state, command, &context).await {
2768 Ok(disconnect) => {
2769 if let Some(written) = written {
2770 let _ = written.send(Ok(()));
2771 }
2772 if disconnect { break; }
2773 }
2774 Err(error) => {
2775 if let Some(written) = written {
2776 let _ = written.send(Err(error.to_string()));
2777 }
2778 if error.is_nonfatal_command_rejection() {
2779 warn!(
2780 device_id = %state.device.id,
2781 %error,
2782 "rejected invalid SCCP station command"
2783 );
2784 continue;
2785 }
2786 return Err(error);
2787 }
2788 }
2789 }
2790 None => break,
2791 }
2792 }
2793 _ = session_deadlines.tick(), if state.is_some() => {
2794 if let Some(state) = state.as_mut() {
2795 let now = Instant::now();
2796 let timed_out = expire_handset_acknowledgements(
2797 &mut state.calls_by_id,
2798 now,
2799 );
2800 for (call_id, acknowledgement) in timed_out {
2801 context
2802 .event_tx
2803 .send(Event::device(
2804 state.device.id.clone(),
2805 state.generation,
2806 DeviceEventKind::HandsetAcknowledgementTimedOut {
2807 call_id,
2808 acknowledgement,
2809 }))
2810 .await
2811 .map_err(|_| ServerError::Stopped)?;
2812 }
2813 for (key, stop) in expire_multicast_reception_acknowledgements(state, now) {
2814 send_message(&mut stream, &stop, state.registration.protocol).await?;
2815 context
2816 .event_tx
2817 .send(Event::device(
2818 state.device.id.clone(),
2819 state.generation,
2820 DeviceEventKind::MulticastReceptionTimedOut {
2821 conference_id: key.conference_id,
2822 call_id: key.call_id,
2823 },
2824 ))
2825 .await
2826 .map_err(|_| ServerError::Stopped)?;
2827 }
2828 for expired in expire_multimedia_receive_acknowledgements(state, now) {
2829 send_message(
2830 &mut stream,
2831 &expired.close,
2832 state.registration.protocol,
2833 )
2834 .await?;
2835 context
2836 .event_tx
2837 .send(Event::device(
2838 state.device.id.clone(),
2839 state.generation,
2840 DeviceEventKind::MultimediaReceiveChannelTimedOut {
2841 call_id: expired.call_id,
2842 codec: expired.codec,
2843 passthrough_party_id: expired.passthrough_party_id,
2844 },
2845 ))
2846 .await
2847 .map_err(|_| ServerError::Stopped)?;
2848 }
2849 for expired in expire_multimedia_transmit_acknowledgements(state, now) {
2850 send_message(
2851 &mut stream,
2852 &expired.stop,
2853 state.registration.protocol,
2854 )
2855 .await?;
2856 context
2857 .event_tx
2858 .send(Event::device(
2859 state.device.id.clone(),
2860 state.generation,
2861 DeviceEventKind::MultimediaTransmitTimedOut {
2862 call_id: expired.call_id,
2863 codec: expired.codec,
2864 passthrough_party_id: expired.passthrough_party_id,
2865 },
2866 ))
2867 .await
2868 .map_err(|_| ServerError::Stopped)?;
2869 }
2870 if let Some(pending) = state
2871 .pending_media_path_release
2872 .filter(|pending| pending.deadline <= now)
2873 {
2874 state.pending_media_path_release = None;
2875 let still_released = state.media_path_states.get(&pending.path)
2876 == Some(&crate::message::values::MediaPathEvent::Off)
2877 && !has_active_media_path(state)
2878 && active_media_path_call(state) == Some(pending.call_id);
2879 if still_released
2880 && let Some(call) = state.calls_by_id.get(&pending.call_id).cloned()
2881 {
2882 debug!(
2883 device_id = %state.device.id,
2884 call_id = ?call.call_id,
2885 path = ?pending.path,
2886 "completing unpaired media-path release as OnHook"
2887 );
2888 let line_instance = call.line_instance;
2889 complete_on_hook(
2890 &mut stream,
2891 state,
2892 &context,
2893 call,
2894 line_instance,
2895 )
2896 .await?;
2897 }
2898 }
2899 prune_connection_statistics(
2900 &mut state.pending_connection_statistics,
2901 Instant::now(),
2902 );
2903 }
2904 }
2905 _ = tokio::time::sleep_until(last_keepalive + keepalive_timeout), if state.is_some() => {
2906 warn!(peer = %context.peer, "SCCP keepalive timeout");
2907 break;
2908 }
2909 }
2910 }
2911
2912 if let Some(mut state) = state {
2913 drain_session_media(&mut stream, &mut state).await;
2914 let mut sessions = context.sessions.lock().await;
2915 let was_current = sessions
2916 .get(&state.device.id)
2917 .is_some_and(|entry| entry.generation == state.generation);
2918 if was_current {
2919 sessions.remove(&state.device.id);
2920 }
2921 drop(sessions);
2922 if was_current {
2923 let _ = context
2924 .event_tx
2925 .send(Event::device(
2926 state.device.id,
2927 state.generation,
2928 DeviceEventKind::Disconnected {},
2929 ))
2930 .await;
2931 }
2932 }
2933 Ok(())
2934}
2935
2936fn expire_handset_acknowledgements(
2937 calls_by_id: &mut HashMap<CallId, SessionCall>,
2938 now: Instant,
2939) -> Vec<(CallId, HandsetAcknowledgement)> {
2940 let mut calls = calls_by_id.keys().copied().collect::<Vec<_>>();
2941 calls.sort_unstable_by_key(|call_id| call_id.0);
2942 let mut expired = Vec::new();
2943 for call_id in calls {
2944 let call = calls_by_id
2945 .get_mut(&call_id)
2946 .expect("call identifier came from session state");
2947 if call.media.receive.state == MediaChannelState::Opening
2948 && call
2949 .media
2950 .receive
2951 .deadline
2952 .is_some_and(|deadline| deadline <= now)
2953 {
2954 call.media.receive.state = MediaChannelState::Closed;
2955 call.media.receive.deadline = None;
2956 call.media.receive.peer = None;
2957 if call.media.coupled_transmit_endpoint.take().is_some() {
2958 call.media.transmit.state = MediaChannelState::Closed;
2959 call.media.transmit.deadline = None;
2960 call.media.transmit.peer = None;
2961 }
2962 expired.push((call_id, HandsetAcknowledgement::OpenReceiveChannel));
2963 }
2964 if call.media.transmit.state == MediaChannelState::Opening
2965 && call
2966 .media
2967 .transmit
2968 .deadline
2969 .is_some_and(|deadline| deadline <= now)
2970 {
2971 call.media.transmit.state = MediaChannelState::Closed;
2972 call.media.transmit.deadline = None;
2973 call.media.transmit.peer = None;
2974 expired.push((call_id, HandsetAcknowledgement::StartMediaTransmission));
2975 }
2976 }
2977 expired
2978}
2979
2980async fn handle_pre_registration_message(
2981 stream: &mut dyn StationIo,
2982 message: ClientMessage,
2983 context: &SessionContext,
2984) -> Result<(), ServerError> {
2985 match message {
2986 ClientMessage::KeepAlive => {
2987 send_message(stream, &ServerMessage::KeepAliveAck, ProtocolVersion::V3).await?;
2988 }
2989 ClientMessage::RegisterToken(token) => {
2990 let definition = context
2991 .definitions
2992 .read()
2993 .expect("SCCP definitions lock poisoned")
2994 .get(&token.device_id)
2995 .cloned();
2996 let configured = definition.is_some()
2997 || context
2998 .anonymous_hotline
2999 .read()
3000 .expect("SCCP anonymous-hotline lock poisoned")
3001 .is_some();
3002 let transport_permitted = definition.as_ref().is_none_or(|definition| {
3003 transport_allowed(definition.transport, context.transport)
3004 });
3005 let already_registered = context.sessions.lock().await.contains_key(&token.device_id);
3006 let accept = configured
3007 && transport_permitted
3008 && !already_registered
3009 && context.config.registration_tokens.accepts(&token.device_id);
3010 let response = if accept {
3011 ServerMessage::RegisterTokenAck
3012 } else {
3013 ServerMessage::RegisterTokenReject {
3014 backoff_seconds: u32::try_from(
3015 context.config.registration_tokens.backoff.as_secs(),
3016 )
3017 .unwrap_or(u32::MAX),
3018 }
3019 };
3020 send_message(stream, &response, ProtocolVersion::V17).await?;
3021 }
3022 ClientMessage::Alarm {
3023 severity,
3024 text,
3025 parameters,
3026 } => {
3027 debug!(peer = %context.peer, ?severity, %text, ?parameters, "pre-registration SCCP alarm");
3028 }
3029 ClientMessage::XmlAlarm(message) => match parse_phone_alarm(message.xml_bytes()) {
3030 Ok(telemetry) => {
3031 debug!(
3032 peer = %context.peer,
3033 payload_len = message.xml_bytes().len(),
3034 summary = ?telemetry.summary(),
3035 opaque = telemetry.is_opaque(),
3036 "pre-registration SCCP XML alarm"
3037 );
3038 }
3039 Err(error) => {
3040 warn!(
3041 peer = %context.peer,
3042 payload_len = message.xml_bytes().len(),
3043 %error,
3044 "rejected pre-registration SCCP XML alarm"
3045 );
3046 }
3047 },
3048 ClientMessage::LocationInfo { xml } => match parse_phone_location(xml.as_bytes()) {
3049 Ok(telemetry) => {
3050 debug!(
3051 peer = %context.peer,
3052 payload_len = xml.len(),
3053 summary = ?telemetry.summary(),
3054 opaque = telemetry.is_opaque(),
3055 "pre-registration SCCP location information"
3056 );
3057 }
3058 Err(error) => {
3059 warn!(
3060 peer = %context.peer,
3061 payload_len = xml.len(),
3062 %error,
3063 "rejected pre-registration SCCP location information"
3064 );
3065 }
3066 },
3067 ClientMessage::KnownOpaque(message) => {
3068 debug!(peer = %context.peer, message = ?message, "pre-registration deferred SCCP message");
3069 }
3070 ClientMessage::Unknown(message) => {
3071 warn!(peer = %context.peer, message = ?message, "pre-registration unknown SCCP message");
3072 }
3073 ClientMessage::Register(_)
3074 | ClientMessage::IpPort { .. }
3075 | ClientMessage::KeypadButton { .. }
3076 | ClientMessage::EnblocCall { .. }
3077 | ClientMessage::Stimulus { .. }
3078 | ClientMessage::OffHook { .. }
3079 | ClientMessage::OnHook { .. }
3080 | ClientMessage::OffHookWithCallingParty { .. }
3081 | ClientMessage::LineStatRequest { .. }
3082 | ClientMessage::ConfigStatRequest
3083 | ClientMessage::TimeDateRequest
3084 | ClientMessage::ButtonTemplateRequest
3085 | ClientMessage::VersionRequest
3086 | ClientMessage::CapabilitiesResponse(_)
3087 | ClientMessage::CapabilitiesUpdate(_)
3088 | ClientMessage::OpenMultimediaReceiveChannelAck(_)
3089 | ClientMessage::ServerRequest
3090 | ClientMessage::MulticastMediaReceptionAck { .. }
3091 | ClientMessage::OpenReceiveChannelAck { .. }
3092 | ClientMessage::SoftKeySetRequest
3093 | ClientMessage::SoftKeyTemplateRequest
3094 | ClientMessage::SoftKeyEvent { .. }
3095 | ClientMessage::Unregister { .. }
3096 | ClientMessage::HookFlash { .. }
3097 | ClientMessage::ForwardStatusRequest { .. }
3098 | ClientMessage::SpeedDialStatusRequest { .. }
3099 | ClientMessage::ConnectionStatisticsResponse(_)
3100 | ClientMessage::HeadsetStatus { .. }
3101 | ClientMessage::MediaResourceNotification(_)
3102 | ClientMessage::MediaPathEvent { .. }
3103 | ClientMessage::MediaPathCapability { .. }
3104 | ClientMessage::MediaTransmissionFailure { .. }
3105 | ClientMessage::RegisterAvailableLines { .. }
3106 | ClientMessage::ServiceUrlStatusRequest { .. }
3107 | ClientMessage::FeatureStatusRequest { .. }
3108 | ClientMessage::StartMediaTransmissionAck(_)
3109 | ClientMessage::StartMultimediaTransmissionAck(_)
3110 | ClientMessage::ExtensionDeviceCapabilities(_)
3111 | ClientMessage::DeviceToUserData(_)
3112 | ClientMessage::DeviceToUserDataResponse(_)
3113 | ClientMessage::DeviceToUserDataV1(_)
3114 | ClientMessage::DeviceToUserDataResponseV1(_)
3115 | ClientMessage::PortResponse(_)
3116 | ClientMessage::SubscriptionStatusRequest(_)
3117 | ClientMessage::SubscribeDtmfPayloadResponse(_)
3118 | ClientMessage::UnsubscribeDtmfPayloadResponse(_)
3119 | ClientMessage::CallCountRequest { .. }
3120 | ClientMessage::CreateConferenceResponse(_)
3121 | ClientMessage::DeleteConferenceResponse { .. }
3122 | ClientMessage::ModifyConferenceResponse(_)
3123 | ClientMessage::AuditConferenceResponse(_)
3124 | ClientMessage::AddParticipantResponse(_)
3125 | ClientMessage::AuditParticipantResponse(_) => {
3126 warn!(peer = %context.peer, message = ?message, "ignoring SCCP message before registration");
3127 }
3128 }
3129 Ok(())
3130}
3131
3132async fn handle_client_message(
3133 stream: &mut dyn StationIo,
3134 state: &mut SessionState,
3135 message: ClientMessage,
3136 context: &SessionContext,
3137) -> Result<(), ServerError> {
3138 let protocol = state.registration.protocol;
3139 match message {
3140 ClientMessage::KeepAlive => {
3141 send_message(stream, &ServerMessage::KeepAliveAck, protocol).await?
3142 }
3143 ClientMessage::CapabilitiesResponse(capabilities) => {
3144 let capabilities = StationMediaCapabilities::from(capabilities);
3145 state.media_capabilities.clone_from(&capabilities);
3146 context
3147 .event_tx
3148 .send(Event::device(
3149 state.device.id.clone(),
3150 state.generation,
3151 DeviceEventKind::Capabilities { capabilities },
3152 ))
3153 .await
3154 .map_err(|_| ServerError::Stopped)?;
3155 }
3156 ClientMessage::CapabilitiesUpdate(update) => {
3157 let capabilities = update.into_media_capabilities();
3158 state.media_capabilities.clone_from(&capabilities);
3159 context
3160 .event_tx
3161 .send(Event::device(
3162 state.device.id.clone(),
3163 state.generation,
3164 DeviceEventKind::Capabilities { capabilities },
3165 ))
3166 .await
3167 .map_err(|_| ServerError::Stopped)?;
3168 }
3169 ClientMessage::ConfigStatRequest => {
3170 send_station_ui_message(
3171 stream,
3172 state,
3173 &ServerMessage::ConfigStatus(crate::message::ConfigurationStatus {
3174 device_name: state.device.id.as_str().to_owned(),
3175 station_user_id: 0,
3176 station_instance: 1,
3177 user_name: state.device.description.clone(),
3178 server_name: context.config.server_name.clone(),
3179 line_count: state.device.line_count() as u32,
3180 speed_dial_count: 0,
3181 }),
3182 )
3183 .await?;
3184 }
3185 ClientMessage::LineStatRequest { line_instance } => {
3186 if let Some(message) = line_status(&state.device, line_instance) {
3187 send_station_ui_message(stream, state, &message).await?;
3188 }
3189 }
3190 ClientMessage::ButtonTemplateRequest => {
3191 send_message(
3192 stream,
3193 &ServerMessage::ButtonTemplate {
3194 buttons: button_template(&state.device),
3195 },
3196 protocol,
3197 )
3198 .await?;
3199 }
3200 ClientMessage::VersionRequest => {
3201 send_message(
3202 stream,
3203 &ServerMessage::Version {
3204 firmware: context.config.firmware_version.clone(),
3205 },
3206 protocol,
3207 )
3208 .await?;
3209 }
3210 ClientMessage::ServerRequest => {
3211 send_message(
3212 stream,
3213 &ServerMessage::ServerResponse {
3214 servers: server_response_endpoints(context, protocol)?,
3215 },
3216 protocol,
3217 )
3218 .await?;
3219 }
3220 ClientMessage::TimeDateRequest => {
3221 send_message(
3222 stream,
3223 &time_date_message(context.config.timezone_offset_minutes),
3224 protocol,
3225 )
3226 .await?
3227 }
3228 ClientMessage::SoftKeyTemplateRequest => {
3229 send_message(
3230 stream,
3231 &ServerMessage::SoftKeyTemplate {
3232 actions: state.device.soft_keys.template_actions(),
3233 },
3234 protocol,
3235 )
3236 .await?
3237 }
3238 ClientMessage::SoftKeySetRequest => {
3239 send_message(
3240 stream,
3241 &ServerMessage::SoftKeySet {
3242 profile: state.device.soft_keys.clone(),
3243 },
3244 protocol,
3245 )
3246 .await?
3247 }
3248 ClientMessage::ForwardStatusRequest { line_instance } => {
3249 let forwarding = state
3250 .forwarding_by_line
3251 .get(&line_instance)
3252 .cloned()
3253 .unwrap_or_default();
3254 send_message(
3255 stream,
3256 &ServerMessage::ForwardStatus {
3257 line_instance,
3258 forward_all: forwarding.all,
3259 forward_busy: forwarding.busy,
3260 forward_no_answer: forwarding.no_answer,
3261 },
3262 protocol,
3263 )
3264 .await?;
3265 }
3266 ClientMessage::SpeedDialStatusRequest {
3267 speed_dial_instance,
3268 } => {
3269 send_station_ui_message(
3270 stream,
3271 state,
3272 &speed_dial_status(&state.device, speed_dial_instance),
3273 )
3274 .await?;
3275 }
3276 ClientMessage::FeatureStatusRequest {
3277 index,
3278 capabilities,
3279 } => {
3280 if let Some(mut message) = feature_status(&state.device, index, capabilities) {
3281 if let ServerMessage::FeatureStatus {
3282 button_type,
3283 state: feature_state,
3284 ..
3285 } = &mut message
3286 && let Some(cached) = state.feature_states.get(&index)
3287 {
3288 *button_type = cached.button_type;
3289 *feature_state = cached.state;
3290 }
3291 send_station_ui_message(stream, state, &message).await?;
3292 }
3293 }
3294 ClientMessage::ServiceUrlStatusRequest { index } => {
3295 if let Some(message) = service_url_status(&state.device, index) {
3296 send_station_ui_message(stream, state, &message).await?;
3297 }
3298 }
3299 ClientMessage::SubscriptionStatusRequest(request) => {
3300 send_message(
3301 stream,
3302 &ServerMessage::SubscriptionStatus {
3303 transaction_id: request.transaction_id,
3304 feature_id: request.feature_id,
3305 timer_seconds: 0,
3306 cause: SubscriptionCause::RouteFailure,
3307 },
3308 protocol,
3309 )
3310 .await?;
3311 }
3312 ClientMessage::RegisterAvailableLines { .. } => {
3313 debug!(device_id = %state.device.id, "phone finished registering available lines");
3314 }
3315 ClientMessage::OffHook {
3316 line_instance,
3317 call_reference,
3318 } => {
3319 if let Some(active_call) = find_call(state, call_reference)
3320 && !matches!(
3321 active_call.state,
3322 CallState::RingIn | CallState::CallWaiting | CallState::OnHook
3323 )
3324 {
3325 debug!(
3326 device_id = %state.device.id,
3327 call_id = ?active_call.call_id,
3328 call_state = ?active_call.state,
3329 line_instance,
3330 call_reference,
3331 "ignoring duplicate OffHook while a call is already active"
3332 );
3333 return Ok(());
3334 }
3335 let line = normalize_line(state, line_instance);
3336 let answer = find_answer_call(
3337 state,
3338 call_reference,
3339 line_instance,
3340 *context
3341 .call_answer_order
3342 .read()
3343 .expect("SCCP call-answer-order lock poisoned"),
3344 )
3345 .cloned();
3346 let answering = answer.is_some();
3347 let call = answer.unwrap_or_else(|| {
3348 ensure_phone_call(state, call_reference, line, &context.next_call_id)
3349 });
3350 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3351 stored.state = CallState::OffHook;
3352 }
3353 state.active_call_id = Some(call.call_id);
3354 if answering {
3355 begin_answer_ui(stream, &call, protocol).await?;
3356 } else {
3357 state.active_key_mode = KeyMode::OffHook;
3358 begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3359 }
3360 context
3361 .event_tx
3362 .send(Event::device(
3363 state.device.id.clone(),
3364 state.generation,
3365 DeviceEventKind::OffHook {
3366 call_id: call.call_id,
3367 line_instance: LineInstance::new(line),
3368 },
3369 ))
3370 .await
3371 .map_err(|_| ServerError::Stopped)?;
3372 }
3373 ClientMessage::OnHook {
3374 line_instance,
3375 call_reference,
3376 } => {
3377 state.pending_media_path_release = None;
3378 if let Some(call) = find_call(state, call_reference).cloned() {
3379 let line = if line_instance == 0 {
3380 call.line_instance
3381 } else {
3382 line_instance
3383 };
3384 complete_on_hook(stream, state, context, call, line).await?;
3385 }
3386 }
3387 ClientMessage::HookFlash {
3388 line_instance,
3389 call_reference,
3390 } => {
3391 let line_instance = normalize_line(state, line_instance);
3392 let call_id = find_call(state, call_reference).map(|call| call.call_id);
3393 context
3394 .event_tx
3395 .send(Event::device(
3396 state.device.id.clone(),
3397 state.generation,
3398 DeviceEventKind::HookFlash {
3399 call_id,
3400 line_instance: LineInstance::new(line_instance),
3401 },
3402 ))
3403 .await
3404 .map_err(|_| ServerError::Stopped)?;
3405 }
3406 ClientMessage::KeypadButton {
3407 button,
3408 call_reference,
3409 ..
3410 } => {
3411 if let Some(call) = find_call(state, call_reference) {
3412 if matches!(button, Digit::Unknown(_)) {
3413 return Ok(());
3414 }
3415 let call = call.clone();
3416 if matches!(
3417 call.state,
3418 CallState::Connected
3419 | CallState::Hold
3420 | CallState::HoldYellow
3421 | CallState::HoldRed
3422 ) && call.media.transmit.state.is_open()
3423 && call.media.transmit.telephone_event_payload != 0
3424 {
3425 return Ok(());
3429 }
3430 let collecting = matches!(call.state, CallState::OffHook | CallState::Transfer);
3431 if collecting && state.active_key_mode != KeyMode::DigitsFollowing {
3432 state.active_key_mode = KeyMode::DigitsFollowing;
3433 send_message(
3434 stream,
3435 &ServerMessage::StopTone {
3436 line_instance: call.line_instance,
3437 call_reference: call.wire_reference,
3438 },
3439 protocol,
3440 )
3441 .await?;
3442 send_message(
3443 stream,
3444 &ServerMessage::SelectSoftKeys {
3445 line_instance: call.line_instance,
3446 call_reference: call.wire_reference,
3447 set: KeyMode::DigitsFollowing,
3448 valid_mask: state.device.soft_keys.valid_mask(KeyMode::DigitsFollowing),
3449 },
3450 protocol,
3451 )
3452 .await?;
3453 }
3454 if collecting && let Some(character) = digit_character(button) {
3455 let number = if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3456 stored.dialed_number.push(character);
3457 stored.dialed_number.clone()
3458 } else {
3459 String::new()
3460 };
3461 if button == context.config.dial_terminator {
3462 remember_last_number(state, call.line_instance, &number, &context.config);
3463 }
3464 }
3465 context
3466 .event_tx
3467 .send(Event::device(
3468 state.device.id.clone(),
3469 state.generation,
3470 DeviceEventKind::Digit {
3471 call_id: call.call_id,
3472 digit: button,
3473 },
3474 ))
3475 .await
3476 .map_err(|_| ServerError::Stopped)?;
3477 }
3478 }
3479 ClientMessage::EnblocCall {
3480 called_party,
3481 line_instance,
3482 ..
3483 } => {
3484 let line = normalize_line(state, line_instance);
3485 let existing = state
3486 .calls_by_id
3487 .values()
3488 .find(|call| call.line_instance == line && call.state != CallState::OnHook)
3489 .cloned();
3490 let created = existing.is_none();
3491 let call = existing
3492 .unwrap_or_else(|| ensure_phone_call(state, 0, line, &context.next_call_id));
3493 if created {
3494 state.active_key_mode = KeyMode::OffHook;
3495 begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3496 context
3497 .event_tx
3498 .send(Event::device(
3499 state.device.id.clone(),
3500 state.generation,
3501 DeviceEventKind::OffHook {
3502 call_id: call.call_id,
3503 line_instance: LineInstance::new(line),
3504 },
3505 ))
3506 .await
3507 .map_err(|_| ServerError::Stopped)?;
3508 }
3509 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
3510 stored.dialed_number.clone_from(&called_party);
3511 }
3512 remember_last_number(state, call.line_instance, &called_party, &context.config);
3513 context
3514 .event_tx
3515 .send(Event::device(
3516 state.device.id.clone(),
3517 state.generation,
3518 DeviceEventKind::EnblocCall {
3519 call_id: call.call_id,
3520 line_instance: LineInstance::new(line),
3521 number: called_party,
3522 },
3523 ))
3524 .await
3525 .map_err(|_| ServerError::Stopped)?;
3526 }
3527 ClientMessage::SoftKeyEvent {
3528 event,
3529 line_instance,
3530 call_reference,
3531 } => {
3532 let received_soft_key = SoftKey::from(event);
3533 if !state
3534 .device
3535 .soft_keys
3536 .allows(state.active_key_mode, received_soft_key)
3537 {
3538 debug!(
3539 device_id = %state.device.id,
3540 mode = state.active_key_mode.wire_value(),
3541 event,
3542 "ignoring unavailable soft-key event"
3543 );
3544 return Ok(());
3545 }
3546 let line = normalize_line(state, line_instance);
3547 let mut soft_key = received_soft_key;
3548 let ringing_call = find_answer_call(
3549 state,
3550 call_reference,
3551 line_instance,
3552 *context
3553 .call_answer_order
3554 .read()
3555 .expect("SCCP call-answer-order lock poisoned"),
3556 );
3557 let mut call_id = if matches!(soft_key, SoftKey::Answer | SoftKey::NewCall)
3558 && let Some(call) = ringing_call
3559 {
3560 soft_key = SoftKey::Answer;
3561 Some(call.call_id)
3562 } else {
3563 find_call(state, call_reference).map(|call| call.call_id)
3564 };
3565 if soft_key == SoftKey::MeetMe
3566 && call_id.is_some_and(|call_id| {
3567 state
3568 .calls_by_id
3569 .get(&call_id)
3570 .is_some_and(|call| call.state != CallState::OffHook)
3571 })
3572 {
3573 call_id = None;
3574 }
3575 if matches!(
3576 soft_key,
3577 SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3578 ) && call_id.is_some_and(|call_id| {
3579 state
3580 .calls_by_id
3581 .get(&call_id)
3582 .is_some_and(|call| call.state == CallState::OnHook)
3583 }) {
3584 call_id = None;
3585 }
3586 if soft_key == SoftKey::Redial {
3587 begin_redial(stream, state, context, line, call_id).await?;
3588 return Ok(());
3589 }
3590 if call_id.is_none()
3591 && matches!(
3592 soft_key,
3593 SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3594 )
3595 {
3596 let call = if soft_key == SoftKey::MeetMe {
3597 reserve_phone_call(state, line, &context.next_call_id)
3598 } else {
3599 ensure_phone_call(state, 0, line, &context.next_call_id)
3600 };
3601 state.active_call_id = Some(call.call_id);
3602 state.active_key_mode = KeyMode::OffHook;
3603 begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
3604 context
3605 .event_tx
3606 .send(Event::device(
3607 state.device.id.clone(),
3608 state.generation,
3609 DeviceEventKind::OffHook {
3610 call_id: call.call_id,
3611 line_instance: LineInstance::new(line),
3612 },
3613 ))
3614 .await
3615 .map_err(|_| ServerError::Stopped)?;
3616 call_id = Some(call.call_id);
3617 }
3618 if soft_key == SoftKey::Backspace
3619 && let Some(call_id) = call_id
3620 && let Some(call) = state.calls_by_id.get_mut(&call_id)
3621 {
3622 call.dialed_number.pop();
3623 let call = call.clone();
3624 send_message(
3625 stream,
3626 &ServerMessage::BackspaceResponse {
3627 line_instance: call.line_instance,
3628 call_reference: call.wire_reference,
3629 },
3630 protocol,
3631 )
3632 .await?;
3633 }
3634 if soft_key == SoftKey::Dial
3635 && let Some(call) = call_id.and_then(|call_id| state.calls_by_id.get(&call_id))
3636 {
3637 let line_instance = call.line_instance;
3638 let number = call.dialed_number.clone();
3639 remember_last_number(state, line_instance, &number, &context.config);
3640 }
3641 if soft_key == SoftKey::Answer
3642 && let Some(call) = call_id.and_then(|call_id| state.calls_by_id.get_mut(&call_id))
3643 {
3644 call.state = CallState::OffHook;
3645 let call = call.clone();
3646 state.active_call_id = Some(call.call_id);
3647 begin_answer_ui(stream, &call, protocol).await?;
3648 }
3649 context
3650 .event_tx
3651 .send(Event::device(
3652 state.device.id.clone(),
3653 state.generation,
3654 DeviceEventKind::SoftKey {
3655 call_id,
3656 line_instance: LineInstance::new(line),
3657 soft_key,
3658 },
3659 ))
3660 .await
3661 .map_err(|_| ServerError::Stopped)?;
3662 }
3663 ClientMessage::Stimulus {
3664 stimulus,
3665 instance,
3666 call_reference,
3667 ..
3668 } => {
3669 let mut call_id = find_call(state, call_reference).map(|call| call.call_id);
3670 if stimulus == Stimulus::MeetMeConference
3671 && call_id.is_some_and(|call_id| {
3672 state
3673 .calls_by_id
3674 .get(&call_id)
3675 .is_some_and(|call| call.state != CallState::OffHook)
3676 })
3677 {
3678 call_id = None;
3679 }
3680 if matches!(
3681 stimulus,
3682 Stimulus::Line
3683 | Stimulus::NewCall
3684 | Stimulus::CallPickup
3685 | Stimulus::GroupCallPickup
3686 ) && call_id.is_some_and(|call_id| {
3687 state
3688 .calls_by_id
3689 .get(&call_id)
3690 .is_some_and(|call| call.state == CallState::OnHook)
3691 }) {
3692 call_id = None;
3693 }
3694 if stimulus == Stimulus::Line {
3695 let line = normalize_line(state, instance);
3696 if call_id.is_none() {
3697 let call = ensure_phone_call(state, 0, line, &context.next_call_id);
3698 state.active_key_mode = KeyMode::OffHook;
3699 begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3700 .await?;
3701 context
3702 .event_tx
3703 .send(Event::device(
3704 state.device.id.clone(),
3705 state.generation,
3706 DeviceEventKind::OffHook {
3707 call_id: call.call_id,
3708 line_instance: LineInstance::new(line),
3709 },
3710 ))
3711 .await
3712 .map_err(|_| ServerError::Stopped)?;
3713 } else {
3714 context
3715 .event_tx
3716 .send(Event::device(
3717 state.device.id.clone(),
3718 state.generation,
3719 DeviceEventKind::LineButton {
3720 line_instance: LineInstance::new(line),
3721 call_id,
3722 },
3723 ))
3724 .await
3725 .map_err(|_| ServerError::Stopped)?;
3726 }
3727 } else if stimulus == Stimulus::ParkingLot {
3728 let configured = state.device.buttons.iter().any(|button| {
3729 matches!(
3730 button,
3731 ButtonDefinition::Feature(feature)
3732 if feature.instance == instance
3733 && feature.feature == ButtonType::ParkingLot
3734 )
3735 });
3736 if !configured {
3737 debug!(
3738 device_id = %state.device.id,
3739 instance,
3740 "ignoring unconfigured parking-lot button stimulus"
3741 );
3742 return Ok(());
3743 }
3744 let line_instance = call_id
3745 .and_then(|call_id| state.calls_by_id.get(&call_id))
3746 .map_or_else(|| normalize_line(state, 0), |call| call.line_instance);
3747 context
3748 .event_tx
3749 .send(Event::device(
3750 state.device.id.clone(),
3751 state.generation,
3752 DeviceEventKind::ParkingLotButton {
3753 instance: LineInstance::new(instance),
3754 call_id,
3755 line_instance: LineInstance::new(line_instance),
3756 },
3757 ))
3758 .await
3759 .map_err(|_| ServerError::Stopped)?;
3760 } else if stimulus == Stimulus::Privacy {
3761 let configured = state.device.buttons.iter().any(|button| {
3762 matches!(
3763 button,
3764 ButtonDefinition::Feature(feature)
3765 if feature.instance == instance
3766 && feature.feature == ButtonType::Feature
3767 )
3768 });
3769 if !configured {
3770 debug!(
3771 device_id = %state.device.id,
3772 instance,
3773 "ignoring unconfigured generic feature-button stimulus"
3774 );
3775 return Ok(());
3776 }
3777 context
3778 .event_tx
3779 .send(Event::device(
3780 state.device.id.clone(),
3781 state.generation,
3782 DeviceEventKind::FeatureButton {
3783 instance: LineInstance::new(instance),
3784 },
3785 ))
3786 .await
3787 .map_err(|_| ServerError::Stopped)?;
3788 } else if stimulus == Stimulus::DoNotDisturb {
3789 let configured = state.device.buttons.iter().any(|button| {
3790 matches!(
3791 button,
3792 ButtonDefinition::Feature(feature)
3793 if feature.instance == instance
3794 && feature.feature == ButtonType::DoNotDisturb
3795 )
3796 });
3797 if !configured {
3798 debug!(
3799 device_id = %state.device.id,
3800 instance,
3801 "ignoring unconfigured do-not-disturb button stimulus"
3802 );
3803 return Ok(());
3804 }
3805 context
3806 .event_tx
3807 .send(Event::device(
3808 state.device.id.clone(),
3809 state.generation,
3810 DeviceEventKind::DoNotDisturbButton {
3811 instance: LineInstance::new(instance),
3812 },
3813 ))
3814 .await
3815 .map_err(|_| ServerError::Stopped)?;
3816 } else if stimulus == Stimulus::Mobility {
3817 let configured = state.device.buttons.iter().any(|button| {
3818 matches!(
3819 button,
3820 ButtonDefinition::Feature(feature)
3821 if feature.instance == instance
3822 && feature.feature == ButtonType::Mobility
3823 )
3824 });
3825 if !configured {
3826 debug!(
3827 device_id = %state.device.id,
3828 instance,
3829 "ignoring unconfigured mobility button stimulus"
3830 );
3831 return Ok(());
3832 }
3833 context
3834 .event_tx
3835 .send(Event::device(
3836 state.device.id.clone(),
3837 state.generation,
3838 DeviceEventKind::MobilityButton {
3839 instance: LineInstance::new(instance),
3840 },
3841 ))
3842 .await
3843 .map_err(|_| ServerError::Stopped)?;
3844 } else if stimulus == Stimulus::Voicemail {
3845 let configured = state.device.buttons.iter().any(|button| {
3846 matches!(
3847 button,
3848 ButtonDefinition::Feature(feature)
3849 if feature.instance == instance
3850 && feature.feature == ButtonType::Voicemail
3851 )
3852 });
3853 if !configured {
3854 debug!(
3855 device_id = %state.device.id,
3856 instance,
3857 "ignoring unconfigured voicemail button stimulus"
3858 );
3859 return Ok(());
3860 }
3861 let line = call_id
3862 .and_then(|call_id| state.calls_by_id.get(&call_id))
3863 .map_or_else(
3864 || normalize_line(state, instance),
3865 |call| call.line_instance,
3866 );
3867 let call = call_id
3868 .and_then(|call_id| state.calls_by_id.get(&call_id).cloned())
3869 .unwrap_or_else(|| ensure_phone_call(state, 0, line, &context.next_call_id));
3870 if call_id.is_none() {
3871 state.active_call_id = Some(call.call_id);
3872 state.active_key_mode = KeyMode::OffHook;
3873 begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3874 .await?;
3875 context
3876 .event_tx
3877 .send(Event::device(
3878 state.device.id.clone(),
3879 state.generation,
3880 DeviceEventKind::OffHook {
3881 call_id: call.call_id,
3882 line_instance: LineInstance::new(line),
3883 },
3884 ))
3885 .await
3886 .map_err(|_| ServerError::Stopped)?;
3887 }
3888 context
3889 .event_tx
3890 .send(Event::device(
3891 state.device.id.clone(),
3892 state.generation,
3893 DeviceEventKind::VoicemailButton {
3894 call_id: call.call_id,
3895 line_instance: LineInstance::new(line),
3896 },
3897 ))
3898 .await
3899 .map_err(|_| ServerError::Stopped)?;
3900 } else {
3901 let line = normalize_line(state, instance);
3902 let Some(soft_key) = stimulus_soft_key(stimulus) else {
3903 debug!(
3904 device_id = %state.device.id,
3905 stimulus = stimulus.wire_value(),
3906 "ignoring stimulus without a soft-key action mapping"
3907 );
3908 return Ok(());
3909 };
3910 if !state
3911 .device
3912 .soft_keys
3913 .allows(state.active_key_mode, soft_key)
3914 {
3915 debug!(
3916 device_id = %state.device.id,
3917 mode = state.active_key_mode.wire_value(),
3918 stimulus = stimulus.wire_value(),
3919 "ignoring unavailable soft-key stimulus"
3920 );
3921 return Ok(());
3922 }
3923 if soft_key == SoftKey::Redial {
3924 begin_redial(stream, state, context, line, call_id).await?;
3925 return Ok(());
3926 }
3927 if matches!(
3928 soft_key,
3929 SoftKey::NewCall | SoftKey::Pickup | SoftKey::GroupPickup | SoftKey::MeetMe
3930 ) && call_id.is_none()
3931 {
3932 let call = if soft_key == SoftKey::MeetMe {
3933 reserve_phone_call(state, line, &context.next_call_id)
3934 } else {
3935 ensure_phone_call(state, 0, line, &context.next_call_id)
3936 };
3937 state.active_call_id = Some(call.call_id);
3938 state.active_key_mode = KeyMode::OffHook;
3939 begin_phone_call_ui(stream, &call, &state.device, state.station_context())
3940 .await?;
3941 context
3942 .event_tx
3943 .send(Event::device(
3944 state.device.id.clone(),
3945 state.generation,
3946 DeviceEventKind::OffHook {
3947 call_id: call.call_id,
3948 line_instance: LineInstance::new(line),
3949 },
3950 ))
3951 .await
3952 .map_err(|_| ServerError::Stopped)?;
3953 call_id = Some(call.call_id);
3954 }
3955 context
3956 .event_tx
3957 .send(Event::device(
3958 state.device.id.clone(),
3959 state.generation,
3960 DeviceEventKind::SoftKey {
3961 call_id,
3962 line_instance: LineInstance::new(line),
3963 soft_key,
3964 },
3965 ))
3966 .await
3967 .map_err(|_| ServerError::Stopped)?;
3968 }
3969 }
3970 ClientMessage::MulticastMediaReceptionAck {
3971 status,
3972 passthrough_party_id,
3973 call_reference,
3974 } => {
3975 let Some(key) =
3976 find_multicast_receive_key(state, call_reference.get(), passthrough_party_id.get())
3977 else {
3978 debug!(
3979 device_id = %state.device.id,
3980 "ignored stale or mismatched multicast reception acknowledgement"
3981 );
3982 return Ok(());
3983 };
3984 if status == MediaStatus::Ok {
3985 let route = {
3986 let receive = state
3987 .multicast
3988 .get_mut(&key)
3989 .and_then(|session| session.receive.as_mut())
3990 .expect("multicast key came from current receive state");
3991 receive.state = MulticastReceiveState::Open;
3992 receive.route
3993 };
3994 context
3995 .event_tx
3996 .send(Event::device(
3997 state.device.id.clone(),
3998 state.generation,
3999 DeviceEventKind::MulticastReceptionStarted {
4000 conference_id: key.conference_id,
4001 call_id: key.call_id,
4002 route,
4003 },
4004 ))
4005 .await
4006 .map_err(|_| ServerError::Stopped)?;
4007 } else {
4008 if let Some(stop) = take_multicast_stop(state, key, true) {
4009 send_message(stream, &stop, protocol).await?;
4010 }
4011 context
4012 .event_tx
4013 .send(Event::device(
4014 state.device.id.clone(),
4015 state.generation,
4016 DeviceEventKind::MulticastReceptionFailed {
4017 conference_id: key.conference_id,
4018 call_id: key.call_id,
4019 status,
4020 },
4021 ))
4022 .await
4023 .map_err(|_| ServerError::Stopped)?;
4024 }
4025 }
4026 ClientMessage::OpenReceiveChannelAck {
4027 status,
4028 address,
4029 port,
4030 call_reference,
4031 passthrough_party_id,
4032 } => {
4033 if let Some(call_id) =
4034 find_receive_media_call_id(state, call_reference, passthrough_party_id)
4035 {
4036 let call = state
4037 .calls_by_id
4038 .get(&call_id)
4039 .expect("media call identifier came from session state")
4040 .clone();
4041 if call.media.receive.state != MediaChannelState::Opening {
4042 debug!(
4043 device_id = %state.device.id,
4044 call_id = ?call.call_id,
4045 state = ?call.media.receive.state,
4046 "ignored stale receive-channel acknowledgement"
4047 );
4048 return Ok(());
4049 }
4050 let endpoint = MediaEndpoint {
4051 address,
4052 rtp_port: port,
4053 rtcp_port: port.saturating_add(1),
4054 codec: call.media.codec,
4055 packet_ms: call.media.packet_ms,
4056 max_frames_per_packet: call.media.max_frames_per_packet,
4057 telephone_event_payload: call.media.receive.telephone_event_payload,
4058 };
4059 let stored = state
4060 .calls_by_id
4061 .get_mut(&call_id)
4062 .expect("media call identifier came from session state");
4063 let implied_transmit = if status == MediaStatus::Ok {
4064 stored.media.receive.state = MediaChannelState::Open;
4065 stored.media.receive.peer = Some(endpoint);
4066 if let Some(endpoint) = stored.media.coupled_transmit_endpoint.take() {
4067 stored.media.transmit.state = MediaChannelState::Open;
4068 stored.media.transmit.peer = Some(endpoint);
4069 stored.media.transmit.deadline = None;
4070 Some(endpoint)
4071 } else {
4072 None
4073 }
4074 } else {
4075 stored.media.receive.state = MediaChannelState::Closed;
4076 stored.media.receive.peer = None;
4077 if stored.media.coupled_transmit_endpoint.take().is_some() {
4078 stored.media.transmit.state = MediaChannelState::Closed;
4079 stored.media.transmit.peer = None;
4080 stored.media.transmit.deadline = None;
4081 }
4082 None
4083 };
4084 stored.media.receive.deadline = None;
4085 context
4086 .event_tx
4087 .send(Event::device(
4088 state.device.id.clone(),
4089 state.generation,
4090 DeviceEventKind::ReceiveChannelOpened {
4091 call_id: call.call_id,
4092 status,
4093 endpoint,
4094 },
4095 ))
4096 .await
4097 .map_err(|_| ServerError::Stopped)?;
4098 if let Some(endpoint) = implied_transmit {
4099 context
4100 .event_tx
4101 .send(Event::device(
4102 state.device.id.clone(),
4103 state.generation,
4104 DeviceEventKind::TransmitChannelImplied {
4105 call_id: call.call_id,
4106 endpoint,
4107 },
4108 ))
4109 .await
4110 .map_err(|_| ServerError::Stopped)?;
4111 }
4112 }
4113 }
4114 ClientMessage::StartMediaTransmissionAck(ack) => {
4115 if let Some(call_id) = find_transmit_media_call_id(
4116 state,
4117 ack.conference_id,
4118 ack.call_reference,
4119 ack.passthrough_party_id,
4120 ) {
4121 let call = state
4122 .calls_by_id
4123 .get(&call_id)
4124 .expect("media call identifier came from session state")
4125 .clone();
4126 if call.media.transmit.state != MediaChannelState::Opening {
4127 debug!(
4128 device_id = %state.device.id,
4129 call_id = ?call.call_id,
4130 state = ?call.media.transmit.state,
4131 "ignored stale transmit-channel acknowledgement"
4132 );
4133 return Ok(());
4134 }
4135 let endpoint = MediaEndpoint {
4136 address: ack.address,
4137 rtp_port: ack.port,
4138 rtcp_port: ack.port.saturating_add(1),
4139 codec: call.media.codec,
4140 packet_ms: call.media.packet_ms,
4141 max_frames_per_packet: call.media.max_frames_per_packet,
4142 telephone_event_payload: call.media.transmit.telephone_event_payload,
4143 };
4144 let stored = state
4145 .calls_by_id
4146 .get_mut(&call_id)
4147 .expect("media call identifier came from session state");
4148 let coupled = stored.media.coupled_transmit_endpoint.take().is_some();
4149 if ack.status == MediaStatus::Ok {
4150 stored.media.transmit.state = MediaChannelState::Open;
4151 stored.media.transmit.peer = Some(endpoint);
4152 } else {
4153 stored.media.transmit.state = MediaChannelState::Closed;
4154 stored.media.transmit.peer = None;
4155 if coupled {
4156 stored.media.receive.state = MediaChannelState::Closed;
4157 stored.media.receive.deadline = None;
4158 stored.media.receive.peer = None;
4159 }
4160 }
4161 stored.media.transmit.deadline = None;
4162 context
4163 .event_tx
4164 .send(Event::device(
4165 state.device.id.clone(),
4166 state.generation,
4167 DeviceEventKind::TransmitChannelStarted {
4168 call_id: call.call_id,
4169 status: ack.status,
4170 endpoint,
4171 },
4172 ))
4173 .await
4174 .map_err(|_| ServerError::Stopped)?;
4175 }
4176 }
4177 ClientMessage::Alarm {
4178 severity,
4179 text,
4180 parameters,
4181 } => {
4182 context
4183 .event_tx
4184 .send(Event::device(
4185 state.device.id.clone(),
4186 state.generation,
4187 DeviceEventKind::Alarm {
4188 severity,
4189 text,
4190 parameters,
4191 },
4192 ))
4193 .await
4194 .map_err(|_| ServerError::Stopped)?;
4195 }
4196 ClientMessage::XmlAlarm(message) => match parse_phone_alarm(message.xml_bytes()) {
4197 Ok(telemetry) => {
4198 context
4199 .event_tx
4200 .send(Event::device(
4201 state.device.id.clone(),
4202 state.generation,
4203 DeviceEventKind::XmlAlarm { telemetry },
4204 ))
4205 .await
4206 .map_err(|_| ServerError::Stopped)?;
4207 }
4208 Err(error) => {
4209 warn!(
4210 device_id = %state.device.id,
4211 payload_len = message.xml_bytes().len(),
4212 %error,
4213 "rejected SCCP XML alarm"
4214 );
4215 }
4216 },
4217 ClientMessage::LocationInfo { xml } => match parse_phone_location(xml.as_bytes()) {
4218 Ok(telemetry) => {
4219 context
4220 .event_tx
4221 .send(Event::device(
4222 state.device.id.clone(),
4223 state.generation,
4224 DeviceEventKind::LocationInformation { telemetry },
4225 ))
4226 .await
4227 .map_err(|_| ServerError::Stopped)?;
4228 }
4229 Err(error) => {
4230 warn!(
4231 device_id = %state.device.id,
4232 payload_len = xml.len(),
4233 %error,
4234 "rejected SCCP location information"
4235 );
4236 }
4237 },
4238 ClientMessage::Unregister { .. } => {
4239 send_message(stream, &ServerMessage::UnregisterAck, protocol).await?;
4240 }
4241 ClientMessage::CallCountRequest { .. } => {
4242 send_message(stream, &ServerMessage::CallCountResponse, protocol).await?;
4243 }
4244 ClientMessage::ConnectionStatisticsResponse(statistics) => {
4245 collect_connection_statistics(state, statistics, context).await?;
4246 }
4247 ClientMessage::MediaTransmissionFailure {
4248 conference_id,
4249 passthrough_party_id,
4250 address,
4251 port,
4252 call_reference,
4253 status,
4254 } => {
4255 if let Some(key) = find_multicast_transmit_key(
4256 state,
4257 conference_id,
4258 call_reference,
4259 passthrough_party_id,
4260 address,
4261 port,
4262 ) {
4263 if let Some(stop) = take_multicast_stop(state, key, false) {
4264 send_message(stream, &stop, protocol).await?;
4265 }
4266 context
4267 .event_tx
4268 .send(Event::device(
4269 state.device.id.clone(),
4270 state.generation,
4271 DeviceEventKind::MulticastTransmissionFailed {
4272 conference_id: key.conference_id,
4273 call_id: key.call_id,
4274 status,
4275 address,
4276 port,
4277 },
4278 ))
4279 .await
4280 .map_err(|_| ServerError::Stopped)?;
4281 return Ok(());
4282 }
4283 let Some(call_id) = find_transmit_media_call_id(
4284 state,
4285 conference_id,
4286 call_reference,
4287 passthrough_party_id,
4288 ) else {
4289 return Ok(());
4290 };
4291 let call = state
4292 .calls_by_id
4293 .get(&call_id)
4294 .expect("media call identifier came from session state")
4295 .clone();
4296 let Some(endpoint) = call.media.transmit.peer else {
4297 return Ok(());
4298 };
4299 if call.media.transmit.state != MediaChannelState::Open
4300 || (conference_id != 0 && conference_id != call.wire_reference)
4301 || endpoint.address != address
4302 || endpoint.rtp_port != port
4303 {
4304 debug!(
4305 device_id = %state.device.id,
4306 call_id = ?call.call_id,
4307 "ignored stale or mismatched media-transmission failure"
4308 );
4309 return Ok(());
4310 }
4311 let stored = state
4312 .calls_by_id
4313 .get_mut(&call_id)
4314 .expect("media call identifier came from session state");
4315 stored.media.transmit.state = MediaChannelState::Closed;
4316 stored.media.transmit.peer = None;
4317 context
4318 .event_tx
4319 .send(Event::device(
4320 state.device.id.clone(),
4321 state.generation,
4322 DeviceEventKind::MediaTransmissionFailed {
4323 call_id,
4324 status,
4325 endpoint,
4326 },
4327 ))
4328 .await
4329 .map_err(|_| ServerError::Stopped)?;
4330 }
4331 ClientMessage::HeadsetStatus { enabled } => {
4332 if state.headset_enabled != enabled {
4333 state.headset_enabled = enabled;
4334 context
4335 .event_tx
4336 .send(Event::device(
4337 state.device.id.clone(),
4338 state.generation,
4339 DeviceEventKind::HeadsetStatusChanged { enabled },
4340 ))
4341 .await
4342 .map_err(|_| ServerError::Stopped)?;
4343 }
4344 }
4345 ClientMessage::MediaPathEvent {
4346 path,
4347 event: media_path_event,
4348 } => {
4349 if state.media_path_states.get(&path) != Some(&media_path_event) {
4350 state.media_path_states.insert(path, media_path_event);
4351 if media_path_event == crate::message::values::MediaPathEvent::On {
4352 state.pending_media_path_release = None;
4353 } else if media_path_event == crate::message::values::MediaPathEvent::Off
4354 && is_local_audio_path(path)
4355 && !has_active_media_path(state)
4356 && let Some(call_id) = active_media_path_call(state)
4357 {
4358 state.pending_media_path_release = Some(PendingMediaPathRelease {
4359 call_id,
4360 path,
4361 deadline: Instant::now() + MEDIA_PATH_RELEASE_GRACE,
4362 });
4363 }
4364 context
4365 .event_tx
4366 .send(Event::device(
4367 state.device.id.clone(),
4368 state.generation,
4369 DeviceEventKind::MediaPathChanged {
4370 path,
4371 event: media_path_event,
4372 },
4373 ))
4374 .await
4375 .map_err(|_| ServerError::Stopped)?;
4376 }
4377 }
4378 ClientMessage::MediaPathCapability { .. } => {}
4379 message @ (ClientMessage::IpPort { .. }
4380 | ClientMessage::OffHookWithCallingParty { .. }
4381 | ClientMessage::MediaResourceNotification(_)
4382 | ClientMessage::SubscribeDtmfPayloadResponse(_)
4383 | ClientMessage::UnsubscribeDtmfPayloadResponse(_)
4384 | ClientMessage::PortResponse(_)) => {
4385 debug!(device_id = %state.device.id, message = ?message, "consumed SCCP telemetry");
4386 }
4387 ClientMessage::DeviceToUserData(message) => {
4388 handle_phone_service_message(
4389 state,
4390 context,
4391 crate::message::id::DEVICE_TO_USER_DATA,
4392 PhoneServiceMessageKind::Data,
4393 PhoneServiceRouting {
4394 application_id: ApplicationId::new(message.application_id),
4395 line_instance: LineInstance::new(message.line_instance),
4396 call_reference: CallReference::new(message.call_reference),
4397 transaction_id: TransactionId::new(message.transaction_id),
4398 },
4399 None,
4400 &message.data,
4401 )
4402 .await?;
4403 }
4404 ClientMessage::DeviceToUserDataResponse(message) => {
4405 handle_phone_service_message(
4406 state,
4407 context,
4408 crate::message::id::DEVICE_TO_USER_DATA_RESPONSE,
4409 PhoneServiceMessageKind::Response,
4410 PhoneServiceRouting {
4411 application_id: ApplicationId::new(message.application_id),
4412 line_instance: LineInstance::new(message.line_instance),
4413 call_reference: CallReference::new(message.call_reference),
4414 transaction_id: TransactionId::new(message.transaction_id),
4415 },
4416 None,
4417 &message.data,
4418 )
4419 .await?;
4420 }
4421 ClientMessage::DeviceToUserDataV1(message) => {
4422 handle_phone_service_message(
4423 state,
4424 context,
4425 crate::message::id::DEVICE_TO_USER_DATA_V1,
4426 PhoneServiceMessageKind::Data,
4427 PhoneServiceRouting {
4428 application_id: ApplicationId::new(message.application_id),
4429 line_instance: LineInstance::new(message.line_instance),
4430 call_reference: CallReference::new(message.call_reference),
4431 transaction_id: TransactionId::new(message.transaction_id),
4432 },
4433 Some(PhoneServiceExtendedRouting {
4434 sequence_flag: message.sequence_flag,
4435 display_priority: message.display_priority,
4436 conference_id: message.conference_id,
4437 application_instance_id: message.application_instance_id,
4438 routing: message.routing,
4439 }),
4440 &message.data,
4441 )
4442 .await?;
4443 }
4444 ClientMessage::DeviceToUserDataResponseV1(message) => {
4445 handle_phone_service_message(
4446 state,
4447 context,
4448 crate::message::id::DEVICE_TO_USER_DATA_RESPONSE_V1,
4449 PhoneServiceMessageKind::Response,
4450 PhoneServiceRouting {
4451 application_id: ApplicationId::new(message.application_id),
4452 line_instance: LineInstance::new(message.line_instance),
4453 call_reference: CallReference::new(message.call_reference),
4454 transaction_id: TransactionId::new(message.transaction_id),
4455 },
4456 Some(PhoneServiceExtendedRouting {
4457 sequence_flag: message.sequence_flag,
4458 display_priority: message.display_priority,
4459 conference_id: message.conference_id,
4460 application_instance_id: message.application_instance_id,
4461 routing: message.routing,
4462 }),
4463 &message.data,
4464 )
4465 .await?;
4466 }
4467 ClientMessage::OpenMultimediaReceiveChannelAck(ack) => {
4468 let Some(call_id) = state.calls_by_wire.get(&ack.call_reference.get()).copied() else {
4469 debug!(device_id = %state.device.id, "ignored video receive acknowledgement for an unknown call");
4470 return Ok(());
4471 };
4472 let Some((request, codec, requested_address_type)) =
4473 state.calls_by_id.get(&call_id).and_then(|call| {
4474 call.video_receive.leg.as_ref().and_then(|leg| {
4475 (leg.state == MediaChannelState::Opening
4476 && leg.request.token().get() == ack.passthrough_party_id.get())
4477 .then_some((leg.request, leg.codec, leg.requested_address_type))
4478 })
4479 })
4480 else {
4481 debug!(device_id = %state.device.id, ?call_id, "ignored stale video receive acknowledgement");
4482 return Ok(());
4483 };
4484
4485 let event = if ack.status == MediaStatus::Ok {
4486 if !endpoint_is_usable(ack.endpoint)
4487 || !address_matches_type(ack.endpoint.address, requested_address_type)
4488 {
4489 debug!(device_id = %state.device.id, ?call_id, "ignored unusable video receive endpoint");
4490 return Ok(());
4491 }
4492 let leg = state
4493 .calls_by_id
4494 .get_mut(&call_id)
4495 .and_then(|call| call.video_receive.leg.as_mut())
4496 .expect("correlated video receive leg remains present");
4497 debug_assert_eq!(leg.request, request);
4498 leg.state = MediaChannelState::Open;
4499 leg.deadline = None;
4500 DeviceEventKind::MultimediaReceiveChannelOpened {
4501 call_id,
4502 codec,
4503 endpoint: ack.endpoint,
4504 passthrough_party_id: ack.passthrough_party_id,
4505 }
4506 } else {
4507 let close = take_multimedia_receive_close(state, call_id)
4508 .expect("correlated video receive leg remains present");
4509 send_message(stream, &close, protocol).await?;
4510 DeviceEventKind::MultimediaReceiveChannelFailed {
4511 call_id,
4512 codec,
4513 status: ack.status,
4514 endpoint: ack.endpoint,
4515 passthrough_party_id: ack.passthrough_party_id,
4516 }
4517 };
4518 context
4519 .event_tx
4520 .send(Event::device(
4521 state.device.id.clone(),
4522 state.generation,
4523 event,
4524 ))
4525 .await
4526 .map_err(|_| ServerError::Stopped)?;
4527 }
4528 ClientMessage::StartMultimediaTransmissionAck(ack) => {
4529 let Some(call_id) = state.calls_by_wire.get(&ack.call_reference.get()).copied() else {
4530 debug!(device_id = %state.device.id, "ignored video transmit acknowledgement for an unknown call");
4531 return Ok(());
4532 };
4533 let Some((request, codec, address_type)) =
4534 state.calls_by_id.get(&call_id).and_then(|call| {
4535 call.video_transmit.leg.as_ref().and_then(|leg| {
4536 (leg.state == MediaChannelState::Opening
4537 && leg.request.token().get() == ack.passthrough_party_id.get()
4538 && leg.conference_id == ack.conference_id)
4539 .then_some((leg.request, leg.codec, leg.address_type))
4540 })
4541 })
4542 else {
4543 debug!(device_id = %state.device.id, ?call_id, "ignored stale video transmit acknowledgement");
4544 return Ok(());
4545 };
4546
4547 let event = if ack.status == MediaStatus::Ok {
4548 if !endpoint_is_usable(ack.endpoint)
4549 || !address_matches_type(ack.endpoint.address, address_type)
4550 {
4551 debug!(device_id = %state.device.id, ?call_id, "ignored unusable video transmit endpoint");
4552 return Ok(());
4553 }
4554 let leg = state
4555 .calls_by_id
4556 .get_mut(&call_id)
4557 .and_then(|call| call.video_transmit.leg.as_mut())
4558 .expect("correlated video transmit leg remains present");
4559 debug_assert_eq!(leg.request, request);
4560 leg.state = MediaChannelState::Open;
4561 leg.deadline = None;
4562 DeviceEventKind::MultimediaTransmitStarted {
4563 call_id,
4564 codec,
4565 endpoint: ack.endpoint,
4566 passthrough_party_id: ack.passthrough_party_id,
4567 }
4568 } else {
4569 let stop = take_multimedia_transmit_stop(state, call_id)
4570 .expect("correlated video transmit leg remains present");
4571 send_message(stream, &stop, protocol).await?;
4572 DeviceEventKind::MultimediaTransmitFailed {
4573 call_id,
4574 codec,
4575 status: ack.status,
4576 endpoint: ack.endpoint,
4577 passthrough_party_id: ack.passthrough_party_id,
4578 }
4579 };
4580 context
4581 .event_tx
4582 .send(Event::device(
4583 state.device.id.clone(),
4584 state.generation,
4585 event,
4586 ))
4587 .await
4588 .map_err(|_| ServerError::Stopped)?;
4589 }
4590 message @ (ClientMessage::ExtensionDeviceCapabilities(_)
4591 | ClientMessage::CreateConferenceResponse(_)
4592 | ClientMessage::DeleteConferenceResponse { .. }
4593 | ClientMessage::ModifyConferenceResponse(_)
4594 | ClientMessage::AuditConferenceResponse(_)
4595 | ClientMessage::AddParticipantResponse(_)
4596 | ClientMessage::AuditParticipantResponse(_)) => {
4597 debug!(device_id = %state.device.id, message = ?message, "deferred SCCP application message");
4598 context
4599 .event_tx
4600 .send(Event::device(
4601 state.device.id.clone(),
4602 state.generation,
4603 DeviceEventKind::UnhandledMessage { message },
4604 ))
4605 .await
4606 .map_err(|_| ServerError::Stopped)?;
4607 }
4608 ClientMessage::KnownOpaque(message) => {
4609 let message = ClientMessage::KnownOpaque(message);
4610 debug!(device_id = %state.device.id, message = ?message, "unhandled SCCP message");
4611 context
4612 .event_tx
4613 .send(Event::device(
4614 state.device.id.clone(),
4615 state.generation,
4616 DeviceEventKind::UnhandledMessage { message },
4617 ))
4618 .await
4619 .map_err(|_| ServerError::Stopped)?;
4620 }
4621 ClientMessage::Unknown(message) => {
4622 let message = ClientMessage::Unknown(message);
4623 warn!(device_id = %state.device.id, message = ?message, "unknown SCCP message");
4624 context
4625 .event_tx
4626 .send(Event::device(
4627 state.device.id.clone(),
4628 state.generation,
4629 DeviceEventKind::UnhandledMessage { message },
4630 ))
4631 .await
4632 .map_err(|_| ServerError::Stopped)?;
4633 }
4634 ClientMessage::Register(_) | ClientMessage::RegisterToken(_) => {
4635 warn!(device_id = %state.device.id, "ignoring registration message on registered session");
4636 }
4637 }
4638 Ok(())
4639}
4640
4641const fn is_local_audio_path(path: crate::message::values::MediaPathId) -> bool {
4642 matches!(
4643 path,
4644 crate::message::values::MediaPathId::Headset
4645 | crate::message::values::MediaPathId::Handset
4646 | crate::message::values::MediaPathId::Speaker
4647 )
4648}
4649
4650fn has_active_media_path(state: &SessionState) -> bool {
4651 state.media_path_states.iter().any(|(path, event)| {
4652 is_local_audio_path(*path) && *event == crate::message::values::MediaPathEvent::On
4653 })
4654}
4655
4656fn active_media_path_call(state: &SessionState) -> Option<CallId> {
4657 state.active_call_id.filter(|call_id| {
4658 state.calls_by_id.get(call_id).is_some_and(|call| {
4659 !matches!(
4660 call.state,
4661 CallState::OnHook
4662 | CallState::RingIn
4663 | CallState::CallWaiting
4664 | CallState::Hold
4665 | CallState::HoldYellow
4666 | CallState::HoldRed
4667 )
4668 })
4669 })
4670}
4671
4672async fn complete_on_hook(
4673 stream: &mut dyn StationIo,
4674 state: &mut SessionState,
4675 context: &SessionContext,
4676 call: SessionCall,
4677 line_instance: u32,
4678) -> Result<(), ServerError> {
4679 state.pending_media_path_release = None;
4680 context
4681 .event_tx
4682 .send(Event::device(
4683 state.device.id.clone(),
4684 state.generation,
4685 DeviceEventKind::OnHook {
4686 call_id: call.call_id,
4687 line_instance: LineInstance::new(line_instance),
4688 },
4689 ))
4690 .await
4691 .map_err(|_| ServerError::Stopped)?;
4692 state.active_key_mode = KeyMode::OnHook;
4693 stop_call_multicast(stream, state, call.call_id, state.registration.protocol).await?;
4694 close_call_media_messages(stream, &call, state.registration.protocol).await?;
4695 close_call_messages(
4696 stream,
4697 &call,
4698 &state.device.soft_keys,
4699 state.registration.protocol,
4700 context.config.timezone_offset_minutes,
4701 )
4702 .await?;
4703 request_connection_statistics(stream, state, &call, context).await?;
4704 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
4705 stored.state = CallState::OnHook;
4706 stored.media.receive.state = MediaChannelState::Closed;
4707 stored.media.receive.deadline = None;
4708 stored.media.transmit.state = MediaChannelState::Closed;
4709 stored.media.transmit.deadline = None;
4710 stored.media.coupled_transmit_endpoint = None;
4711 stored.video_receive.leg = None;
4712 stored.video_transmit.leg = None;
4713 }
4714 if state.active_call_id == Some(call.call_id) {
4715 state.active_call_id = None;
4716 }
4717 Ok(())
4718}
4719
4720fn button_template(device: &DeviceDefinition) -> Vec<ButtonTemplateEntry> {
4721 let mut buttons = Vec::with_capacity(56);
4722 let mut addon_buttons_remaining = None;
4723 for button in &device.buttons {
4724 if let ButtonDefinition::AddonModule(addon) = button {
4725 buttons.extend(std::iter::repeat_n(
4726 ButtonTemplateEntry {
4727 instance: 0,
4728 button_type: ButtonType::Unused,
4729 },
4730 addon_buttons_remaining.take().unwrap_or_default(),
4731 ));
4732 addon_buttons_remaining = addon.button_capacity();
4733 continue;
4734 }
4735 buttons.push(match button {
4736 ButtonDefinition::Line(appearance) => ButtonTemplateEntry {
4737 instance: appearance.instance,
4738 button_type: ButtonType::Line,
4739 },
4740 ButtonDefinition::SpeedDial(speed_dial) => ButtonTemplateEntry {
4741 instance: speed_dial.instance,
4742 button_type: ButtonType::SpeedDial,
4743 },
4744 ButtonDefinition::BlfSpeedDial(speed_dial) => ButtonTemplateEntry {
4745 instance: speed_dial.instance,
4746 button_type: ButtonType::BlfSpeedDial,
4747 },
4748 ButtonDefinition::Feature(feature) => ButtonTemplateEntry {
4749 instance: feature.instance,
4750 button_type: ButtonType::from(feature.feature.wire_value()),
4751 },
4752 ButtonDefinition::Service(service) => ButtonTemplateEntry {
4753 instance: service.instance,
4754 button_type: ButtonType::ServiceUrl,
4755 },
4756 ButtonDefinition::Unused => ButtonTemplateEntry {
4757 instance: 0,
4758 button_type: ButtonType::Unused,
4759 },
4760 ButtonDefinition::AddonModule(_) => unreachable!("addon marker handled above"),
4761 });
4762 if let Some(remaining) = &mut addon_buttons_remaining {
4763 *remaining = remaining.saturating_sub(1);
4764 }
4765 }
4766 buttons.extend(std::iter::repeat_n(
4767 ButtonTemplateEntry {
4768 instance: 0,
4769 button_type: ButtonType::Unused,
4770 },
4771 addon_buttons_remaining.unwrap_or_default(),
4772 ));
4773 buttons
4774}
4775
4776fn line_status(device: &DeviceDefinition, instance: u32) -> Option<ServerMessage> {
4777 device
4778 .line(instance)
4779 .map(|appearance| ServerMessage::LineStatus {
4780 instance: appearance.instance,
4781 number: appearance.line.number.clone(),
4782 display_name: appearance.display_label().to_owned(),
4783 })
4784}
4785
4786fn mobility_device_candidate(
4787 current: &DeviceDefinition,
4788 current_appearances: &HashMap<u32, LineAppearance>,
4789 next_appearances: &HashMap<u32, LineAppearance>,
4790) -> Result<DeviceDefinition, CodecError> {
4791 let mut candidate = current.clone();
4792 candidate.buttons.retain(|button| {
4793 !matches!(
4794 button,
4795 ButtonDefinition::Line(line)
4796 if current_appearances.values().any(|appearance| appearance == line)
4797 )
4798 });
4799 let mut index = 0;
4800 while index < candidate.buttons.len() {
4801 let mobility_instance = match &candidate.buttons[index] {
4802 ButtonDefinition::Feature(feature) if feature.feature == ButtonType::Mobility => {
4803 Some(feature.instance)
4804 }
4805 _ => None,
4806 };
4807 if let Some(appearance) = mobility_instance
4808 .and_then(|instance| next_appearances.get(&instance))
4809 .cloned()
4810 {
4811 candidate
4812 .buttons
4813 .insert(index + 1, ButtonDefinition::Line(appearance));
4814 index += 2;
4815 } else {
4816 index += 1;
4817 }
4818 }
4819 candidate.validate()?;
4820 Ok(candidate)
4821}
4822
4823fn speed_dial_status(device: &DeviceDefinition, instance: u32) -> ServerMessage {
4824 let speed_dial = device.buttons.iter().find_map(|button| match button {
4825 ButtonDefinition::SpeedDial(speed_dial) if speed_dial.instance == instance => {
4826 Some((&speed_dial.number, &speed_dial.display_name))
4827 }
4828 ButtonDefinition::BlfSpeedDial(speed_dial) if speed_dial.instance == instance => {
4829 Some((&speed_dial.number, &speed_dial.display_name))
4830 }
4831 _ => None,
4832 });
4833 ServerMessage::SpeedDialStatus {
4834 instance,
4835 number: speed_dial.map_or_else(String::new, |(number, _)| number.clone()),
4836 display_name: speed_dial.map_or_else(String::new, |(_, display_name)| display_name.clone()),
4837 }
4838}
4839
4840fn feature_status(
4841 device: &DeviceDefinition,
4842 instance: u32,
4843 capabilities: u32,
4844) -> Option<ServerMessage> {
4845 device.buttons.iter().find_map(|button| match button {
4846 ButtonDefinition::BlfSpeedDial(speed_dial)
4847 if capabilities == 1 && speed_dial.instance == instance =>
4848 {
4849 Some(ServerMessage::FeatureStatus {
4850 instance,
4851 button_type: ButtonType::BlfSpeedDial,
4852 label: speed_dial.display_name.clone(),
4853 state: BusyLampFieldState::UnknownState.wire_value(),
4854 })
4855 }
4856 ButtonDefinition::Feature(feature) if feature.instance == instance => {
4857 Some(ServerMessage::FeatureStatus {
4858 instance,
4859 button_type: ButtonType::from(feature.feature.wire_value()),
4860 label: feature.label.clone(),
4861 state: 0,
4862 })
4863 }
4864 _ => None,
4865 })
4866}
4867
4868fn feature_state_messages(
4869 device: &DeviceDefinition,
4870 instance: u32,
4871 enabled: bool,
4872) -> Option<[ServerMessage; 2]> {
4873 let feature = device.buttons.iter().find_map(|button| match button {
4874 ButtonDefinition::Feature(feature) if feature.instance == instance => Some(feature),
4875 _ => None,
4876 })?;
4877 Some([
4878 ServerMessage::FeatureStatus {
4879 instance,
4880 button_type: ButtonType::from(feature.feature.wire_value()),
4881 label: feature.label.clone(),
4882 state: u32::from(enabled),
4883 },
4884 ServerMessage::SetLamp {
4885 stimulus: feature.feature,
4886 instance,
4887 mode: if enabled { LampMode::On } else { LampMode::Off },
4888 },
4889 ])
4890}
4891
4892fn do_not_disturb_state_messages(
4893 device: &DeviceDefinition,
4894 instance: u32,
4895 mode: DoNotDisturbMode,
4896 button_mode: DoNotDisturbButtonMode,
4897 protocol: ProtocolVersion,
4898) -> Option<[ServerMessage; 2]> {
4899 let feature = device.buttons.iter().find_map(|button| match button {
4900 ButtonDefinition::Feature(feature)
4901 if feature.instance == instance && feature.feature == ButtonType::DoNotDisturb =>
4902 {
4903 Some(feature)
4904 }
4905 _ => None,
4906 })?;
4907 let exact_enabled = match button_mode {
4908 DoNotDisturbButtonMode::Cycle => mode != DoNotDisturbMode::Off,
4909 DoNotDisturbButtonMode::Silent => mode == DoNotDisturbMode::Silent,
4910 DoNotDisturbButtonMode::Reject => mode == DoNotDisturbMode::Reject,
4911 };
4912 let multi_state =
4913 button_mode == DoNotDisturbButtonMode::Cycle && protocol > ProtocolVersion::V15;
4914 let (button_type, state) = if multi_state {
4915 (
4916 ButtonType::MultiblinkFeature,
4917 match mode {
4918 DoNotDisturbMode::Off => 0x010000,
4919 DoNotDisturbMode::Reject => 0x020202,
4920 DoNotDisturbMode::Silent => 0x030302,
4921 },
4922 )
4923 } else {
4924 (ButtonType::DoNotDisturb, u32::from(exact_enabled))
4925 };
4926 let lamp = match (exact_enabled, mode) {
4927 (false, _) | (_, DoNotDisturbMode::Off) => LampMode::Off,
4928 (true, DoNotDisturbMode::Silent) => LampMode::Blink,
4929 (true, DoNotDisturbMode::Reject) => LampMode::On,
4930 };
4931 Some([
4932 ServerMessage::FeatureStatus {
4933 instance,
4934 button_type,
4935 label: feature.label.clone(),
4936 state,
4937 },
4938 ServerMessage::SetLamp {
4939 stimulus: feature.feature,
4940 instance,
4941 mode: lamp,
4942 },
4943 ])
4944}
4945
4946fn blf_status_messages(
4947 instance: u32,
4948 number: &str,
4949 label: &str,
4950 state: BlfState,
4951 caller: Option<&BlfCallerInfo>,
4952) -> [ServerMessage; 3] {
4953 let caller = caller.map(BlfCallerInfo::display).unwrap_or_default();
4954 let dynamic_label = if caller.is_empty() {
4955 label.to_owned()
4956 } else {
4957 format!("{label}: {caller}")
4958 };
4959 let icon = match state {
4960 BlfState::Idle => BusyLampFieldState::Idle,
4961 BlfState::Ringing => BusyLampFieldState::Alerting,
4962 BlfState::Busy | BlfState::Held => BusyLampFieldState::InUse,
4963 BlfState::Unavailable | BlfState::Unknown => BusyLampFieldState::UnknownState,
4964 };
4965 let lamp = match state {
4966 BlfState::Idle => LampMode::Off,
4967 BlfState::Ringing => LampMode::Blink,
4968 BlfState::Busy => LampMode::On,
4969 BlfState::Held => LampMode::Hold,
4970 BlfState::Unavailable => LampMode::Flash,
4971 BlfState::Unknown => LampMode::Wink,
4972 };
4973 [
4974 ServerMessage::SpeedDialStatus {
4975 instance,
4976 number: truncate_utf8(number, 23),
4977 display_name: truncate_utf8(&dynamic_label, 39),
4978 },
4979 ServerMessage::FeatureStatus {
4980 instance,
4981 button_type: ButtonType::BlfSpeedDial,
4982 label: truncate_utf8(&dynamic_label, 39),
4983 state: icon.wire_value(),
4984 },
4985 ServerMessage::SetLamp {
4986 stimulus: ButtonType::BlfSpeedDial,
4987 instance,
4988 mode: lamp,
4989 },
4990 ]
4991}
4992
4993fn hinted_ringing_notification(
4994 device: &DeviceDefinition,
4995 label: &str,
4996 caller: Option<&BlfCallerInfo>,
4997 state: BlfState,
4998) -> Option<HandsetStatusMessage> {
4999 if !device.ui.hinted_ringing_notification || state != BlfState::Ringing {
5000 return None;
5001 }
5002 let caller = caller.map(BlfCallerInfo::display).unwrap_or_default();
5003 let text = if caller.is_empty() {
5004 format!("{label} is ringing")
5005 } else {
5006 format!("{label} is ringing: {caller}")
5007 };
5008 Some(HandsetStatusMessage::Display {
5009 text: truncate_utf8(&text, 79),
5010 timeout_seconds: 5,
5011 priority: None,
5012 })
5013}
5014
5015fn truncate_utf8(value: &str, maximum_bytes: usize) -> String {
5016 if value.len() <= maximum_bytes {
5017 return value.to_owned();
5018 }
5019 let end = value
5020 .char_indices()
5021 .map(|(index, _)| index)
5022 .take_while(|index| *index <= maximum_bytes)
5023 .last()
5024 .unwrap_or(0);
5025 value[..end].to_owned()
5026}
5027
5028fn service_url_status(device: &DeviceDefinition, index: u32) -> Option<ServerMessage> {
5029 device.buttons.iter().find_map(|button| match button {
5030 ButtonDefinition::Service(service) if service.instance == index => {
5031 Some(ServerMessage::ServiceUrlStatus {
5032 index,
5033 url: service.url.clone(),
5034 label: service.label.clone(),
5035 extension_text: String::new(),
5036 })
5037 }
5038 _ => None,
5039 })
5040}
5041
5042const fn key_mode_for_call_state(state: CallState) -> KeyMode {
5043 match state {
5044 CallState::Connected => KeyMode::Connected,
5045 CallState::Hold | CallState::HoldYellow | CallState::HoldRed => KeyMode::OnHold,
5046 CallState::RingIn | CallState::CallWaiting => KeyMode::RingIn,
5047 CallState::OffHook
5048 | CallState::Busy
5049 | CallState::Congestion
5050 | CallState::InvalidNumber
5051 | CallState::IntercomOneWay => KeyMode::OffHook,
5052 CallState::Transfer => KeyMode::ConnectedTransfer,
5053 CallState::RingOut | CallState::Proceed => KeyMode::RingOut,
5054 CallState::RemoteMultiline => KeyMode::OnHookStealable,
5055 CallState::OnHook | CallState::Park | CallState::Unknown(_) => KeyMode::OnHook,
5056 }
5057}
5058
5059fn transfer_key_mode(call: &SessionCall, state: CallState) -> KeyMode {
5060 if matches!(
5061 call.transfer_role,
5062 Some(SessionTransferRole::Consultation { .. })
5063 ) && matches!(state, CallState::RingOut | CallState::Connected)
5064 {
5065 KeyMode::ConnectedTransfer
5066 } else {
5067 key_mode_for_call_state(state)
5068 }
5069}
5070
5071fn stimulus_soft_key(stimulus: Stimulus) -> Option<SoftKey> {
5072 Some(match stimulus {
5073 Stimulus::LastNumberRedial => SoftKey::Redial,
5074 Stimulus::Hold => SoftKey::Hold,
5075 Stimulus::Transfer => SoftKey::Transfer,
5076 Stimulus::ForwardAll => SoftKey::ForwardAll,
5077 Stimulus::ForwardBusy => SoftKey::ForwardBusy,
5078 Stimulus::ForwardNoAnswer => SoftKey::ForwardNoAnswer,
5079 Stimulus::Conference => SoftKey::Conference,
5080 Stimulus::MeetMeConference => SoftKey::MeetMe,
5081 Stimulus::CallPark => SoftKey::Park,
5082 Stimulus::CallPickup => SoftKey::Pickup,
5083 Stimulus::GroupCallPickup => SoftKey::GroupPickup,
5084 Stimulus::DoNotDisturb => SoftKey::DoNotDisturb,
5085 Stimulus::ConferenceList => SoftKey::ConferenceList,
5086 Stimulus::NewCall => SoftKey::NewCall,
5087 Stimulus::EndCall => SoftKey::EndCall,
5088 _ => return None,
5089 })
5090}
5091
5092fn parking_menu_xml(
5093 instance: u32,
5094 transaction_id: u32,
5095 lot: &str,
5096 calls: &[ParkingMenuEntry],
5097) -> Result<String, ServerError> {
5098 if calls.len() > PARKING_MENU_MAX_ITEMS {
5099 return Err(PhoneXmlError::LimitExceeded {
5100 kind: "parking menu",
5101 actual: calls.len(),
5102 maximum: PARKING_MENU_MAX_ITEMS,
5103 }
5104 .into());
5105 }
5106 let items = calls
5107 .iter()
5108 .map(|call| {
5109 let party = if !call.caller_name.trim().is_empty() {
5110 call.caller_name.trim()
5111 } else if !call.caller_number.trim().is_empty() {
5112 call.caller_number.trim()
5113 } else {
5114 "Unknown caller"
5115 };
5116 let connected = if !call.connected_name.trim().is_empty() {
5117 format!(" to {}", call.connected_name.trim())
5118 } else if !call.connected_number.trim().is_empty() {
5119 format!(" to {}", call.connected_number.trim())
5120 } else {
5121 String::new()
5122 };
5123 CiscoIpPhoneMenuItem {
5124 name: Some(format!("{}: {}{}", call.slot, party, connected)),
5125 url: Some(format!(
5126 "UserCallData:{}:{instance}:0:{transaction_id}:retrieve/{}/{}",
5127 PARKING_APPLICATION_ID,
5128 utf8_percent_encode(lot, NON_ALPHANUMERIC),
5129 call.slot,
5130 )),
5131 }
5132 })
5133 .collect();
5134 CiscoIpPhoneMenu::new(
5135 format!("Parked calls - {lot}"),
5136 if calls.is_empty() {
5137 "No parked calls"
5138 } else {
5139 "Select a call"
5140 },
5141 items,
5142 )?
5143 .to_xml_with_limit(2_000)
5144 .map_err(ServerError::from)
5145}
5146
5147fn text_service_messages(
5148 line_instance: LineInstance,
5149 call_reference: CallReference,
5150 transaction_id: TransactionId,
5151 priority: PhoneServicePriority,
5152 document: &CiscoIpPhoneText,
5153 protocol: ProtocolVersion,
5154) -> Result<Vec<ServerMessage>, ServerError> {
5155 if protocol <= ProtocolVersion::V17
5156 && document
5157 .text
5158 .as_deref()
5159 .is_some_and(|text| text.chars().count() > PHONE_TEXT_LEGACY_MAX_CHARS)
5160 {
5161 return Err(PhoneXmlError::InvalidField {
5162 field: "legacy phone text body",
5163 expected: "at most 1024 characters",
5164 }
5165 .into());
5166 }
5167 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5168 2_000
5169 } else {
5170 crate::phone::xml::PHONE_TEXT_MAX_BYTES
5171 };
5172 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5173 Ok(phone_service_document_messages(
5174 line_instance,
5175 call_reference,
5176 ApplicationId::new(PHONE_TEXT_APPLICATION_ID),
5177 transaction_id,
5178 priority,
5179 &xml,
5180 ))
5181}
5182
5183fn input_service_messages(
5184 line_instance: LineInstance,
5185 call_reference: CallReference,
5186 application_id: ApplicationId,
5187 transaction_id: TransactionId,
5188 priority: PhoneServicePriority,
5189 document: &CiscoIpPhoneInput,
5190 protocol: ProtocolVersion,
5191) -> Result<Vec<ServerMessage>, ServerError> {
5192 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5193 2_000
5194 } else {
5195 PHONE_INPUT_MAX_BYTES
5196 };
5197 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5198 Ok(phone_service_document_messages(
5199 line_instance,
5200 call_reference,
5201 application_id,
5202 transaction_id,
5203 priority,
5204 &xml,
5205 ))
5206}
5207
5208fn execute_phone_action_messages(
5209 line_instance: LineInstance,
5210 call_reference: CallReference,
5211 application_id: ApplicationId,
5212 transaction_id: TransactionId,
5213 priority: PhoneServicePriority,
5214 document: &CiscoIpPhoneExecute,
5215 protocol: ProtocolVersion,
5216) -> Result<Vec<ServerMessage>, ServerError> {
5217 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5218 2_000
5219 } else {
5220 PHONE_EXECUTE_MAX_BYTES
5221 };
5222 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5223 Ok(phone_service_document_messages(
5224 line_instance,
5225 call_reference,
5226 application_id,
5227 transaction_id,
5228 priority,
5229 &xml,
5230 ))
5231}
5232
5233fn image_service_messages(
5234 line_instance: LineInstance,
5235 call_reference: CallReference,
5236 application_id: ApplicationId,
5237 transaction_id: TransactionId,
5238 priority: PhoneServicePriority,
5239 document: &PhoneImageDocument,
5240 protocol: ProtocolVersion,
5241) -> Result<Vec<ServerMessage>, ServerError> {
5242 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5243 2_000
5244 } else {
5245 PHONE_IMAGE_MAX_BYTES
5246 };
5247 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5248 Ok(phone_service_document_messages(
5249 line_instance,
5250 call_reference,
5251 application_id,
5252 transaction_id,
5253 priority,
5254 &xml,
5255 ))
5256}
5257
5258fn status_service_messages(
5259 line_instance: LineInstance,
5260 call_reference: CallReference,
5261 application_id: ApplicationId,
5262 transaction_id: TransactionId,
5263 priority: PhoneServicePriority,
5264 document: &PhoneStatusDocument,
5265 protocol: ProtocolVersion,
5266) -> Result<Vec<ServerMessage>, ServerError> {
5267 let maximum_bytes = if protocol <= ProtocolVersion::V17 {
5268 2_000
5269 } else {
5270 PHONE_STATUS_MAX_BYTES
5271 };
5272 let xml = document.to_xml_with_limit(maximum_bytes)?.into_bytes();
5273 Ok(phone_service_document_messages(
5274 line_instance,
5275 call_reference,
5276 application_id,
5277 transaction_id,
5278 priority,
5279 &xml,
5280 ))
5281}
5282
5283fn background_control_message(
5284 transaction_id: TransactionId,
5285 document: &PhoneBackgroundControlDocument,
5286) -> Result<ServerMessage, ServerError> {
5287 let xml = document.to_xml()?.into_bytes();
5288 let [message] = phone_service_document_messages(
5289 LineInstance::new(0),
5290 CallReference::new(0),
5291 ApplicationId::new(PHONE_BACKGROUND_APPLICATION_ID),
5292 transaction_id,
5293 PhoneServicePriority::LOW,
5294 &xml,
5295 )
5296 .try_into()
5297 .map_err(|_| PhoneXmlError::InvalidField {
5298 field: "background control document",
5299 expected: "a single application-data frame",
5300 })?;
5301 Ok(message)
5302}
5303
5304fn ringtone_control_message(
5305 transaction_id: TransactionId,
5306 document: &CiscoIpPhoneSetRingTone,
5307) -> Result<ServerMessage, ServerError> {
5308 let xml = document.to_xml()?.into_bytes();
5309 let [message] = phone_service_document_messages(
5310 LineInstance::new(0),
5311 CallReference::new(0),
5312 ApplicationId::new(PHONE_RINGTONE_APPLICATION_ID),
5313 transaction_id,
5314 PhoneServicePriority::LOW,
5315 &xml,
5316 )
5317 .try_into()
5318 .map_err(|_| PhoneXmlError::InvalidField {
5319 field: "ringtone control document",
5320 expected: "a single application-data frame",
5321 })?;
5322 Ok(message)
5323}
5324
5325#[cfg(test)]
5326fn start_announcement_message(
5327 conference_id: ConferenceId,
5328 announcements: Vec<AnnouncementEntry>,
5329 end_of_ack: bool,
5330 participant_ids: Vec<ParticipantId>,
5331 hearing_participant_mask: u32,
5332 play_mode: u32,
5333) -> ServerMessage {
5334 ServerMessage::StartAnnouncement {
5335 announcements,
5336 end_of_ack: u32::from(end_of_ack),
5337 conference_id: conference_id.get(),
5338 matrix_conference_party_ids: participant_ids
5339 .into_iter()
5340 .map(ParticipantId::get)
5341 .collect(),
5342 hearing_conference_party_mask: hearing_participant_mask,
5343 play_mode,
5344 }
5345}
5346
5347fn phone_service_document_messages(
5348 line_instance: LineInstance,
5349 call_reference: CallReference,
5350 application_id: ApplicationId,
5351 transaction_id: TransactionId,
5352 priority: PhoneServicePriority,
5353 xml: &[u8],
5354) -> Vec<ServerMessage> {
5355 let chunks = xml.chunks(2_000);
5356 let chunk_count = chunks.len();
5357 chunks
5358 .enumerate()
5359 .map(|(index, data)| {
5360 let sequence_flag = if chunk_count == 1 || index + 1 == chunk_count {
5361 2
5362 } else if index == 0 {
5363 0
5364 } else {
5365 1
5366 };
5367 ServerMessage::UserToDeviceDataV1(UserDataV1Message {
5368 application_id: application_id.get(),
5369 line_instance: line_instance.get(),
5370 call_reference: call_reference.get(),
5371 transaction_id: transaction_id.get(),
5372 sequence_flag,
5373 display_priority: priority.wire(),
5374 conference_id: call_reference.get(),
5375 application_instance_id: application_id.get(),
5376 routing: 1,
5377 data: data.to_vec(),
5378 })
5379 })
5380 .collect()
5381}
5382
5383async fn handle_phone_service_message(
5384 state: &mut SessionState,
5385 context: &SessionContext,
5386 message_id: u32,
5387 kind: PhoneServiceMessageKind,
5388 routing: PhoneServiceRouting,
5389 extended: Option<PhoneServiceExtendedRouting>,
5390 data: &[u8],
5391) -> Result<(), ServerError> {
5392 let payload = match parse_phone_service_payload(data, kind) {
5393 Ok(payload) => payload,
5394 Err(error) => {
5395 warn!(
5396 device_id = %state.device.id,
5397 message_id = format_args!("0x{message_id:04x}"),
5398 %error,
5399 "ignoring malformed phone-service response"
5400 );
5401 context
5402 .event_tx
5403 .send(Event::ProtocolWarning {
5404 peer: context.peer,
5405 device_id: Some(state.device.id.clone()),
5406 message_id,
5407 error: error.to_string(),
5408 })
5409 .await
5410 .map_err(|_| ServerError::Stopped)?;
5411 return Ok(());
5412 }
5413 };
5414 let response = PhoneServiceEvent {
5415 kind,
5416 routing,
5417 extended,
5418 payload,
5419 };
5420
5421 if let Some((lot, slot)) = parking_menu_selection(state.pending_parking_menu, &response) {
5422 state.pending_parking_menu = None;
5423 context
5424 .event_tx
5425 .send(Event::device(
5426 state.device.id.clone(),
5427 state.generation,
5428 DeviceEventKind::ParkingMenuSelection { lot, slot },
5429 ))
5430 .await
5431 .map_err(|_| ServerError::Stopped)?;
5432 }
5433 if response.kind == PhoneServiceMessageKind::Data
5434 && response.routing.application_id.get() == ConferenceListAction::APPLICATION_ID
5435 && let PhoneServicePayload::Submission(submission) = &response.payload
5436 && let Some(action) = ConferenceListAction::from_route(&submission.route)
5437 {
5438 context
5439 .event_tx
5440 .send(Event::device(
5441 state.device.id.clone(),
5442 state.generation,
5443 DeviceEventKind::ConferenceListAction { action },
5444 ))
5445 .await
5446 .map_err(|_| ServerError::Stopped)?;
5447 }
5448 context
5449 .event_tx
5450 .send(Event::device(
5451 state.device.id.clone(),
5452 state.generation,
5453 DeviceEventKind::PhoneServiceResponse { response },
5454 ))
5455 .await
5456 .map_err(|_| ServerError::Stopped)
5457}
5458
5459fn parking_menu_selection(
5460 pending: Option<PendingParkingMenu>,
5461 response: &PhoneServiceEvent,
5462) -> Option<(String, u32)> {
5463 let pending = pending?;
5464 if response.kind != PhoneServiceMessageKind::Data
5465 || response.routing.application_id.get() != PARKING_APPLICATION_ID
5466 || response.routing.line_instance.get() != pending.instance
5467 || response.routing.call_reference.get() != 0
5468 || response.routing.transaction_id.get() != pending.transaction_id
5469 || response
5470 .extended
5471 .is_some_and(|extended| extended.application_instance_id != pending.instance)
5472 {
5473 return None;
5474 }
5475 let PhoneServicePayload::Submission(submission) = &response.payload else {
5476 return None;
5477 };
5478 let [action, lot, slot] = submission.route.as_slice() else {
5479 return None;
5480 };
5481 if action != "retrieve" || lot.is_empty() || !submission.values.is_empty() {
5482 return None;
5483 }
5484 let slot = slot.parse().ok()?;
5485 (slot != 0).then(|| (lot.clone(), slot))
5486}
5487
5488fn digit_character(digit: Digit) -> Option<char> {
5489 match digit {
5490 Digit::Number(number @ 0..=9) => Some(char::from(b'0' + number)),
5491 Digit::Star => Some('*'),
5492 Digit::Pound => Some('#'),
5493 Digit::A => Some('A'),
5494 Digit::B => Some('B'),
5495 Digit::C => Some('C'),
5496 Digit::D => Some('D'),
5497 Digit::Number(_) | Digit::Unknown(_) => None,
5498 }
5499}
5500
5501fn normalized_last_number(number: &str, config: &ServerConfig) -> Option<String> {
5502 let number = number.trim();
5503 let number = if config.record_dial_terminator {
5504 number
5505 } else {
5506 digit_character(config.dial_terminator)
5507 .map_or(number, |terminator| number.trim_end_matches(terminator))
5508 };
5509 (!number.is_empty()).then(|| number.to_owned())
5510}
5511
5512fn remember_last_number(
5513 state: &mut SessionState,
5514 line_instance: u32,
5515 number: &str,
5516 config: &ServerConfig,
5517) {
5518 if let Some(number) = normalized_last_number(number, config) {
5519 state.last_number_by_line.insert(line_instance, number);
5520 }
5521}
5522
5523async fn begin_redial(
5524 stream: &mut dyn StationIo,
5525 state: &mut SessionState,
5526 context: &SessionContext,
5527 line_instance: u32,
5528 existing_call_id: Option<CallId>,
5529) -> Result<(), ServerError> {
5530 if state.device.ui.placed_calls_redial_menu
5531 && placed_calls_menu_supported(state.registration.protocol)
5532 {
5533 let document = CiscoIpPhoneExecute::new(vec![CiscoIpPhoneExecuteItem::new(
5534 "Application:PlacedCalls",
5535 )?])?;
5536 for message in execute_phone_action_messages(
5537 LineInstance::new(line_instance),
5538 CallReference::new(0),
5539 ApplicationId::new(0),
5540 TransactionId::new(0),
5541 PhoneServicePriority::NORMAL,
5542 &document,
5543 state.registration.protocol,
5544 )? {
5545 send_message(stream, &message, state.registration.protocol).await?;
5546 }
5547 return Ok(());
5548 }
5549
5550 let Some(number) = state.last_number_by_line.get(&line_instance).cloned() else {
5551 return Ok(());
5552 };
5553 let existing = existing_call_id.and_then(|call_id| {
5554 state
5555 .calls_by_id
5556 .get(&call_id)
5557 .filter(|call| call.line_instance == line_instance && call.state != CallState::OnHook)
5558 .cloned()
5559 });
5560 let (call, created) = existing.map_or_else(
5561 || {
5562 (
5563 ensure_phone_call(state, 0, line_instance, &context.next_call_id),
5564 true,
5565 )
5566 },
5567 |call| (call, false),
5568 );
5569
5570 if created {
5571 state.active_key_mode = KeyMode::OffHook;
5572 begin_phone_call_ui(stream, &call, &state.device, state.station_context()).await?;
5573 context
5574 .event_tx
5575 .send(Event::device(
5576 state.device.id.clone(),
5577 state.generation,
5578 DeviceEventKind::OffHook {
5579 call_id: call.call_id,
5580 line_instance: LineInstance::new(line_instance),
5581 },
5582 ))
5583 .await
5584 .map_err(|_| ServerError::Stopped)?;
5585 }
5586 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
5587 stored.dialed_number.clone_from(&number);
5588 }
5589 send_message(
5590 stream,
5591 &ServerMessage::DialedNumber {
5592 number: number.clone(),
5593 line_instance,
5594 call_reference: call.wire_reference,
5595 },
5596 state.registration.protocol,
5597 )
5598 .await?;
5599 context
5600 .event_tx
5601 .send(Event::device(
5602 state.device.id.clone(),
5603 state.generation,
5604 DeviceEventKind::EnblocCall {
5605 call_id: call.call_id,
5606 line_instance: LineInstance::new(line_instance),
5607 number,
5608 },
5609 ))
5610 .await
5611 .map_err(|_| ServerError::Stopped)?;
5612 Ok(())
5613}
5614
5615fn placed_calls_menu_supported(protocol: ProtocolVersion) -> bool {
5616 protocol >= ProtocolVersion::V8
5617}
5618
5619async fn handle_session_command(
5620 stream: &mut dyn StationIo,
5621 state: &mut SessionState,
5622 command: SessionCommand,
5623 context: &SessionContext,
5624) -> Result<bool, ServerError> {
5625 let config = &context.config;
5626 let protocol = state.registration.protocol;
5627 match command {
5628 SessionCommand::Confirmed { .. } => {
5629 unreachable!("confirmed commands are unwrapped by the session loop")
5630 }
5631 SessionCommand::Disconnect => {
5632 drain_session_media(stream, state).await;
5633 return Ok(true);
5634 }
5635 SessionCommand::OfferIncoming {
5636 line_instance,
5637 call_id,
5638 info,
5639 ringer,
5640 } => {
5641 if state.cancelled_calls.remove(&call_id) {
5642 debug!(device_id = %state.device.id, ?call_id, "discarding incoming call cancelled before it was offered");
5643 return Ok(false);
5644 }
5645 let line_instance = normalize_line(state, line_instance.get());
5646 let statistics_directory_number = statistics_directory_for_call_info(&info).to_owned();
5647 let caller = match (
5648 info.calling_name.trim().is_empty(),
5649 info.calling_number.trim().is_empty(),
5650 ) {
5651 (false, false) => format!("{} ({})", info.calling_name, info.calling_number),
5652 (false, true) => info.calling_name.clone(),
5653 (true, false) => info.calling_number.clone(),
5654 (true, true) => "Unknown number".to_owned(),
5655 };
5656 let incoming_state = if state.calls_by_id.values().any(|call| {
5657 matches!(
5658 call.state,
5659 CallState::Connected
5660 | CallState::Hold
5661 | CallState::HoldYellow
5662 | CallState::HoldRed
5663 )
5664 }) {
5665 CallState::CallWaiting
5666 } else {
5667 CallState::RingIn
5668 };
5669 let call = insert_call(state, call_id, line_instance, Codec::Pcmu, incoming_state);
5670 if incoming_state == CallState::RingIn && state.active_call_id.is_none() {
5671 state.active_call_id = Some(call.call_id);
5672 }
5673 if let Some(stored) = state.calls_by_id.get_mut(&call.call_id) {
5674 stored.statistics_directory_number = statistics_directory_number;
5675 }
5676 send_message(
5677 stream,
5678 &ServerMessage::ClearPrompt {
5679 line_instance,
5680 call_reference: call.wire_reference,
5681 },
5682 protocol,
5683 )
5684 .await?;
5685 send_message(
5686 stream,
5687 &ServerMessage::CallState {
5688 state: incoming_state,
5689 line_instance,
5690 call_reference: call.wire_reference,
5691 },
5692 protocol,
5693 )
5694 .await?;
5695 send_station_ui_message(
5696 stream,
5697 state,
5698 &ServerMessage::CallInfo {
5699 info: *info,
5700 line_instance,
5701 call_reference: call.wire_reference,
5702 },
5703 )
5704 .await?;
5705 send_message(
5706 stream,
5707 &ServerMessage::SetLamp {
5708 stimulus: ButtonType::Line,
5709 instance: line_instance,
5710 mode: LampMode::Blink,
5711 },
5712 protocol,
5713 )
5714 .await?;
5715 if let Some(ringer) = incoming_ringer(ringer, incoming_state) {
5716 send_message(
5717 stream,
5718 &ServerMessage::SetRinger {
5719 mode: ringer.mode,
5720 duration: ringer.duration,
5721 line_instance,
5722 call_reference: call.wire_reference,
5723 },
5724 protocol,
5725 )
5726 .await?;
5727 }
5728 state.active_key_mode = KeyMode::RingIn;
5729 send_message(
5730 stream,
5731 &ServerMessage::SelectSoftKeys {
5732 line_instance,
5733 call_reference: call.wire_reference,
5734 set: KeyMode::RingIn,
5735 valid_mask: state.device.soft_keys.valid_mask(KeyMode::RingIn),
5736 },
5737 protocol,
5738 )
5739 .await?;
5740 send_station_ui_message(
5741 stream,
5742 state,
5743 &ServerMessage::DisplayPrompt {
5744 timeout_seconds: 0,
5745 text: format!("From {caller}"),
5746 line_instance,
5747 call_reference: call.wire_reference,
5748 },
5749 )
5750 .await?;
5751 }
5752 SessionCommand::Public(command) => {
5753 let command = *command;
5754 if let Some(call_id) = command_call_id(&command)
5755 && !matches!(
5756 &command.action,
5757 CommandAction::BeginCall { .. } | CommandAction::CloseCall { .. }
5758 )
5759 && !state.calls_by_id.contains_key(&call_id)
5760 {
5761 debug!(device_id = %state.device.id, ?call_id, command = ?command, "ignoring stale SCCP call command");
5762 return Ok(false);
5763 }
5764 let action = command.action;
5765 match action {
5766 CommandAction::DisconnectDevice { .. } => {
5767 drain_session_media(stream, state).await;
5768 return Ok(true);
5769 }
5770 CommandAction::BeginCall {
5771 line_instance,
5772 call_id,
5773 codec,
5774 } => {
5775 if state.calls_by_id.contains_key(&call_id) {
5776 return Ok(false);
5777 }
5778 let line_instance = normalize_line(state, line_instance.get());
5779 let call =
5780 insert_call(state, call_id, line_instance, codec, CallState::OffHook);
5781 state.active_call_id = Some(call.call_id);
5782 state.active_key_mode = KeyMode::OffHook;
5783 begin_phone_call_ui(stream, &call, &state.device, state.station_context())
5784 .await?;
5785 }
5786 CommandAction::BeginTransfer {
5787 source_call_id,
5788 consultation_line_instance,
5789 consultation_call_id,
5790 codec,
5791 } => {
5792 let consultation_line_instance = consultation_line_instance.get();
5793 if state.calls_by_id.contains_key(&consultation_call_id) {
5794 return Ok(false);
5795 }
5796 let source = require_call_mut(state, source_call_id)?;
5797 if !matches!(
5798 source.state,
5799 CallState::Hold | CallState::HoldYellow | CallState::HoldRed
5800 ) {
5801 return Err(ServerError::InvalidCallTransaction {
5802 call_id: source_call_id,
5803 operation: "begin transfer",
5804 state: source.state,
5805 });
5806 }
5807 source.state = CallState::Transfer;
5808 source.transfer_role = Some(SessionTransferRole::Source {
5809 consultation_call_id,
5810 });
5811 let source = source.clone();
5812 send_message(
5813 stream,
5814 &ServerMessage::CallState {
5815 state: CallState::Transfer,
5816 line_instance: source.line_instance,
5817 call_reference: source.wire_reference,
5818 },
5819 protocol,
5820 )
5821 .await?;
5822 send_station_ui_message(
5823 stream,
5824 state,
5825 &ServerMessage::DisplayPrompt {
5826 timeout_seconds: 0,
5827 text: "Call Transfer".into(),
5828 line_instance: source.line_instance,
5829 call_reference: source.wire_reference,
5830 },
5831 )
5832 .await?;
5833
5834 let line_instance = normalize_line(state, consultation_line_instance);
5835 let mut consultation = insert_call(
5836 state,
5837 consultation_call_id,
5838 line_instance,
5839 codec,
5840 CallState::OffHook,
5841 );
5842 consultation.transfer_role =
5843 Some(SessionTransferRole::Consultation { source_call_id });
5844 state
5845 .calls_by_id
5846 .insert(consultation_call_id, consultation.clone());
5847 state.active_call_id = Some(consultation.call_id);
5848 state.active_key_mode = KeyMode::OffHookFeature;
5849 begin_phone_call_ui_with_key_mode(
5850 stream,
5851 &consultation,
5852 &state.device,
5853 KeyMode::OffHookFeature,
5854 state.station_context(),
5855 )
5856 .await?;
5857 send_message(
5858 stream,
5859 &ServerMessage::SetLamp {
5860 stimulus: ButtonType::Transfer,
5861 instance: source.line_instance,
5862 mode: LampMode::Flash,
5863 },
5864 protocol,
5865 )
5866 .await?;
5867 }
5868 CommandAction::SetCallSelected {
5869 call_id, selected, ..
5870 } => {
5871 let call = require_call(state, call_id)?.clone();
5872 send_message(
5873 stream,
5874 &ServerMessage::CallSelectStatus {
5875 status: u32::from(selected),
5876 call_reference: call.wire_reference,
5877 line_instance: call.line_instance,
5878 },
5879 protocol,
5880 )
5881 .await?;
5882 }
5883 CommandAction::SetMwi {
5884 line_instance,
5885 enabled,
5886 ..
5887 } => {
5888 let line_instance = line_instance.get();
5889 state.mwi_by_line.insert(line_instance, enabled);
5890 send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
5891 }
5892 CommandAction::SetForwardStatus {
5893 line_instance,
5894 forward_all,
5895 forward_busy,
5896 forward_no_answer,
5897 ..
5898 } => {
5899 let line_instance = line_instance.get();
5900 state.forwarding_by_line.insert(
5901 line_instance,
5902 SessionForwarding {
5903 all: forward_all.clone(),
5904 busy: forward_busy.clone(),
5905 no_answer: forward_no_answer.clone(),
5906 },
5907 );
5908 send_message(
5909 stream,
5910 &ServerMessage::ForwardStatus {
5911 line_instance,
5912 forward_all,
5913 forward_busy,
5914 forward_no_answer,
5915 },
5916 protocol,
5917 )
5918 .await?;
5919 }
5920 CommandAction::SetFeatureStatus {
5921 instance, enabled, ..
5922 } => {
5923 let instance = instance.get();
5924 if let Some(messages) = feature_state_messages(&state.device, instance, enabled)
5925 {
5926 let ServerMessage::FeatureStatus {
5927 button_type,
5928 state: feature_state,
5929 ..
5930 } = &messages[0]
5931 else {
5932 unreachable!("feature status starts with a feature-state message")
5933 };
5934 state.feature_states.insert(
5935 instance,
5936 SessionFeatureState {
5937 button_type: *button_type,
5938 state: *feature_state,
5939 },
5940 );
5941 for message in messages {
5942 send_station_ui_message(stream, state, &message).await?;
5943 }
5944 }
5945 }
5946 CommandAction::SetDoNotDisturbStatus {
5947 instance,
5948 mode,
5949 button_mode,
5950 ..
5951 } => {
5952 let instance = instance.get();
5953 if let Some(messages) = do_not_disturb_state_messages(
5954 &state.device,
5955 instance,
5956 mode,
5957 button_mode,
5958 protocol,
5959 ) {
5960 let ServerMessage::FeatureStatus {
5961 button_type,
5962 state: feature_state,
5963 ..
5964 } = &messages[0]
5965 else {
5966 unreachable!("DND status starts with a feature-state message")
5967 };
5968 state.feature_states.insert(
5969 instance,
5970 SessionFeatureState {
5971 button_type: *button_type,
5972 state: *feature_state,
5973 },
5974 );
5975 for message in messages {
5976 send_station_ui_message(stream, state, &message).await?;
5977 }
5978 }
5979 }
5980 CommandAction::SetMobilityAppearance {
5981 mobility_instance,
5982 appearance,
5983 ..
5984 } => {
5985 let mobility_instance = mobility_instance.get();
5986 let configured = state.device.buttons.iter().any(|button| {
5987 matches!(
5988 button,
5989 ButtonDefinition::Feature(feature)
5990 if feature.instance == mobility_instance
5991 && feature.feature == ButtonType::Mobility
5992 )
5993 });
5994 if !configured {
5995 return Err(CodecError::InvalidDefinition(format!(
5996 "device {} has no mobility button instance {mobility_instance}",
5997 state.device.id
5998 ))
5999 .into());
6000 }
6001 let previous = state.mobility_appearances.get(&mobility_instance).cloned();
6002 let mut next_appearances = state.mobility_appearances.clone();
6003 match &appearance {
6004 Some(appearance) => {
6005 next_appearances.insert(mobility_instance, appearance.clone());
6006 }
6007 None => {
6008 next_appearances.remove(&mobility_instance);
6009 }
6010 }
6011 let candidate = mobility_device_candidate(
6012 &state.device,
6013 &state.mobility_appearances,
6014 &next_appearances,
6015 )?;
6016
6017 send_message(
6018 stream,
6019 &ServerMessage::ButtonTemplate {
6020 buttons: button_template(&candidate),
6021 },
6022 protocol,
6023 )
6024 .await?;
6025 if let Some(appearance) = &appearance {
6026 if let Some(message) = line_status(&candidate, appearance.instance) {
6027 send_station_ui_message(stream, state, &message).await?;
6028 }
6029 } else if let Some(previous) = &previous {
6030 send_station_ui_message(
6031 stream,
6032 state,
6033 &ServerMessage::LineStatus {
6034 instance: previous.instance,
6035 number: String::new(),
6036 display_name: String::new(),
6037 },
6038 )
6039 .await?;
6040 }
6041 state.device = candidate;
6042 state.mobility_appearances = next_appearances;
6043 }
6044 CommandAction::SetBlfStatus {
6045 instance,
6046 number,
6047 label,
6048 state: blf_state,
6049 caller,
6050 ..
6051 } => {
6052 let instance = instance.get();
6053 for message in
6054 blf_status_messages(instance, &number, &label, blf_state, caller.as_ref())
6055 {
6056 send_station_ui_message(stream, state, &message).await?;
6057 }
6058 if let Some(notification) = hinted_ringing_notification(
6059 &state.device,
6060 &label,
6061 caller.as_ref(),
6062 blf_state,
6063 ) {
6064 for message in status_message_frames(
6065 notification,
6066 state.registration.device_type,
6067 &mut state.persistent_status_message,
6068 ) {
6069 send_station_ui_message(stream, state, &message).await?;
6070 }
6071 }
6072 }
6073 CommandAction::ShowParkingMenu {
6074 instance,
6075 transaction_id,
6076 lot,
6077 calls,
6078 ..
6079 } => {
6080 let instance = instance.get();
6081 let transaction_id = transaction_id.get();
6082 send_message(
6083 stream,
6084 &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6085 application_id: PARKING_APPLICATION_ID,
6086 line_instance: instance,
6087 call_reference: 0,
6088 transaction_id,
6089 sequence_flag: 0,
6090 display_priority: 2,
6091 conference_id: 0,
6092 application_instance_id: instance,
6093 routing: 0,
6094 data: parking_menu_xml(instance, transaction_id, &lot, &calls)?
6095 .into_bytes(),
6096 }),
6097 protocol,
6098 )
6099 .await?;
6100 state.pending_parking_menu = Some(PendingParkingMenu {
6101 instance,
6102 transaction_id,
6103 });
6104 }
6105 CommandAction::ShowConferenceList {
6106 call_id,
6107 conference_id,
6108 participants,
6109 ..
6110 } => {
6111 let call = require_call(state, call_id)?.clone();
6112 let family = if protocol >= ProtocolVersion::V8 {
6113 ConferenceMenuFamily::IconMenu
6114 } else {
6115 ConferenceMenuFamily::Menu
6116 };
6117 let data = ConferenceListDocument::new(conference_id, &participants, family)?
6118 .to_xml()?
6119 .into_bytes();
6120 send_message(
6121 stream,
6122 &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6123 application_id: ConferenceListAction::APPLICATION_ID,
6124 line_instance: call.line_instance,
6125 call_reference: call.wire_reference,
6126 transaction_id: conference_id.get(),
6127 sequence_flag: 0,
6128 display_priority: 2,
6129 conference_id: conference_id.get(),
6130 application_instance_id: call.line_instance,
6131 routing: 0,
6132 data,
6133 }),
6134 protocol,
6135 )
6136 .await?;
6137 }
6138 CommandAction::ShowConferenceParticipantActions {
6139 call_id,
6140 conference_id,
6141 participant,
6142 removable,
6143 demotable,
6144 ..
6145 } => {
6146 let call = require_call(state, call_id)?.clone();
6147 let family = if protocol >= ProtocolVersion::V8 {
6148 ConferenceMenuFamily::IconMenu
6149 } else {
6150 ConferenceMenuFamily::Menu
6151 };
6152 let data = ConferenceParticipantActionsDocument::new(
6153 conference_id,
6154 &participant,
6155 removable,
6156 demotable,
6157 family,
6158 )?
6159 .to_xml()?
6160 .into_bytes();
6161 send_message(
6162 stream,
6163 &ServerMessage::UserToDeviceDataV1(UserDataV1Message {
6164 application_id: ConferenceListAction::APPLICATION_ID,
6165 line_instance: call.line_instance,
6166 call_reference: call.wire_reference,
6167 transaction_id: conference_id.get(),
6168 sequence_flag: 0,
6169 display_priority: 2,
6170 conference_id: conference_id.get(),
6171 application_instance_id: call.line_instance,
6172 routing: 0,
6173 data,
6174 }),
6175 protocol,
6176 )
6177 .await?;
6178 }
6179 CommandAction::ShowTextService {
6180 line_instance,
6181 call_reference,
6182 transaction_id,
6183 priority,
6184 document,
6185 ..
6186 } => {
6187 for message in text_service_messages(
6188 line_instance,
6189 call_reference,
6190 transaction_id,
6191 priority,
6192 &document,
6193 protocol,
6194 )? {
6195 send_message(stream, &message, protocol).await?;
6196 }
6197 }
6198 CommandAction::ShowInputService {
6199 line_instance,
6200 call_reference,
6201 application_id,
6202 transaction_id,
6203 priority,
6204 document,
6205 ..
6206 } => {
6207 for message in input_service_messages(
6208 line_instance,
6209 call_reference,
6210 application_id,
6211 transaction_id,
6212 priority,
6213 &document,
6214 protocol,
6215 )? {
6216 send_message(stream, &message, protocol).await?;
6217 }
6218 }
6219 CommandAction::ExecutePhoneActions {
6220 line_instance,
6221 call_reference,
6222 application_id,
6223 transaction_id,
6224 priority,
6225 document,
6226 ..
6227 } => {
6228 for message in execute_phone_action_messages(
6229 line_instance,
6230 call_reference,
6231 application_id,
6232 transaction_id,
6233 priority,
6234 &document,
6235 protocol,
6236 )? {
6237 send_message(stream, &message, protocol).await?;
6238 }
6239 }
6240 CommandAction::ShowImageService {
6241 line_instance,
6242 call_reference,
6243 application_id,
6244 transaction_id,
6245 priority,
6246 document,
6247 ..
6248 } => {
6249 for message in image_service_messages(
6250 line_instance,
6251 call_reference,
6252 application_id,
6253 transaction_id,
6254 priority,
6255 &document,
6256 protocol,
6257 )? {
6258 send_message(stream, &message, protocol).await?;
6259 }
6260 }
6261 CommandAction::ShowStatusService {
6262 line_instance,
6263 call_reference,
6264 application_id,
6265 transaction_id,
6266 priority,
6267 document,
6268 ..
6269 } => {
6270 for message in status_service_messages(
6271 line_instance,
6272 call_reference,
6273 application_id,
6274 transaction_id,
6275 priority,
6276 &document,
6277 protocol,
6278 )? {
6279 send_message(stream, &message, protocol).await?;
6280 }
6281 }
6282 CommandAction::SetBackgroundImage {
6283 transaction_id,
6284 document,
6285 ..
6286 } => {
6287 let message = background_control_message(
6288 transaction_id,
6289 &PhoneBackgroundControlDocument::Set(document),
6290 )?;
6291 send_message(stream, &message, protocol).await?;
6292 }
6293 CommandAction::PreviewBackgroundImage {
6294 transaction_id,
6295 document,
6296 ..
6297 } => {
6298 let message = background_control_message(
6299 transaction_id,
6300 &PhoneBackgroundControlDocument::Preview(document),
6301 )?;
6302 send_message(stream, &message, protocol).await?;
6303 }
6304 CommandAction::SetRingtone {
6305 transaction_id,
6306 document,
6307 ..
6308 } => {
6309 let message = ringtone_control_message(transaction_id, &document)?;
6310 send_message(stream, &message, protocol).await?;
6311 }
6312 CommandAction::StartTone { call_id, tone, .. } => {
6313 let call = require_call(state, call_id)?.clone();
6314 let message = if tone == Tone::Silence {
6315 ServerMessage::StopTone {
6316 line_instance: call.line_instance,
6317 call_reference: call.wire_reference,
6318 }
6319 } else {
6320 ServerMessage::StartTone {
6321 tone,
6322 direction: ToneDirection::User,
6323 line_instance: call.line_instance,
6324 call_reference: call.wire_reference,
6325 }
6326 };
6327 send_message(stream, &message, protocol).await?;
6328 }
6329 CommandAction::StartAnnouncement {
6330 conference_id,
6331 announcements,
6332 end_of_ack,
6333 participant_ids,
6334 hearing_participant_mask,
6335 play_mode,
6336 ..
6337 } => {
6338 let _ = (
6339 conference_id,
6340 announcements,
6341 end_of_ack,
6342 participant_ids,
6343 hearing_participant_mask,
6344 play_mode,
6345 );
6346 return Err(ServerError::InvalidStationCommand {
6347 message: "StartAnnouncement",
6348 });
6349 }
6350 CommandAction::StopAnnouncement { conference_id, .. } => {
6351 let _ = conference_id;
6352 return Err(ServerError::InvalidStationCommand {
6353 message: "StopAnnouncement",
6354 });
6355 }
6356 CommandAction::AnnouncementFinish {
6357 conference_id,
6358 play_status,
6359 ..
6360 } => {
6361 let _ = (conference_id, play_status);
6362 return Err(ServerError::InvalidStationCommand {
6363 message: "AnnouncementFinish",
6364 });
6365 }
6366 CommandAction::SetCallInfo { call_id, info, .. } => {
6367 let statistics_directory_number =
6368 statistics_directory_for_call_info(&info).to_owned();
6369 if let Some(stored) = state.calls_by_id.get_mut(&call_id) {
6370 stored.statistics_directory_number = statistics_directory_number;
6371 }
6372 let call = require_call(state, call_id)?.clone();
6373 send_station_ui_message(
6374 stream,
6375 state,
6376 &ServerMessage::CallInfo {
6377 info,
6378 line_instance: call.line_instance,
6379 call_reference: call.wire_reference,
6380 },
6381 )
6382 .await?;
6383 }
6384 CommandAction::CommitOutboundCall { call_id, info, .. } => {
6385 let statistics_directory_number =
6386 statistics_directory_for_call_info(&info).to_owned();
6387 let call = require_call_mut(state, call_id)?;
6388 call.state = CallState::Proceed;
6389 call.history_disposition =
6390 updated_history_disposition(call.history_disposition, CallState::Proceed);
6391 call.statistics_directory_number = statistics_directory_number;
6392 let call = call.clone();
6393 let number = digit_character(config.dial_terminator)
6394 .and_then(|terminator| call.dialed_number.strip_suffix(terminator))
6395 .unwrap_or(&call.dialed_number)
6396 .to_owned();
6397 remember_last_number(state, call.line_instance, &number, config);
6398 state.active_call_id = Some(call.call_id);
6399 refresh_mwi_lamps(stream, state, protocol).await?;
6400 for message in [
6401 ServerMessage::StopTone {
6402 line_instance: call.line_instance,
6403 call_reference: call.wire_reference,
6404 },
6405 ServerMessage::SetLamp {
6406 stimulus: ButtonType::Line,
6407 instance: call.line_instance,
6408 mode: LampMode::Blink,
6409 },
6410 ServerMessage::CallInfo {
6411 info,
6412 line_instance: call.line_instance,
6413 call_reference: call.wire_reference,
6414 },
6415 ServerMessage::DialedNumber {
6416 number,
6417 line_instance: call.line_instance,
6418 call_reference: call.wire_reference,
6419 },
6420 ServerMessage::CallState {
6421 state: CallState::Proceed,
6422 line_instance: call.line_instance,
6423 call_reference: call.wire_reference,
6424 },
6425 ] {
6426 send_station_ui_message(stream, state, &message).await?;
6427 }
6428 }
6429 CommandAction::PresentOutboundProceeding { call_id, info, .. } => {
6430 let statistics_directory_number =
6431 statistics_directory_for_call_info(&info).to_owned();
6432 let call = require_call_mut(state, call_id)?;
6433 call.state = CallState::Proceed;
6434 call.history_disposition =
6435 updated_history_disposition(call.history_disposition, CallState::Proceed);
6436 call.statistics_directory_number = statistics_directory_number;
6437 let call = call.clone();
6438 state.active_call_id = Some(call.call_id);
6439 refresh_mwi_lamps(stream, state, protocol).await?;
6440 for message in [
6441 ServerMessage::StopTone {
6442 line_instance: call.line_instance,
6443 call_reference: call.wire_reference,
6444 },
6445 ServerMessage::CallState {
6446 state: CallState::Proceed,
6447 line_instance: call.line_instance,
6448 call_reference: call.wire_reference,
6449 },
6450 ServerMessage::CallInfo {
6451 info,
6452 line_instance: call.line_instance,
6453 call_reference: call.wire_reference,
6454 },
6455 ServerMessage::DisplayPrompt {
6456 timeout_seconds: 0,
6457 text: "Call Proceed".into(),
6458 line_instance: call.line_instance,
6459 call_reference: call.wire_reference,
6460 },
6461 ] {
6462 send_station_ui_message(stream, state, &message).await?;
6463 }
6464 }
6465 CommandAction::PresentOutboundRinging { call_id, info, .. } => {
6466 let statistics_directory_number =
6467 statistics_directory_for_call_info(&info).to_owned();
6468 let call = require_call_mut(state, call_id)?;
6469 call.state = CallState::Proceed;
6470 call.history_disposition =
6471 updated_history_disposition(call.history_disposition, CallState::Proceed);
6472 call.statistics_directory_number = statistics_directory_number;
6473 let call = call.clone();
6474 state.active_call_id = Some(call.call_id);
6475 let key_mode = transfer_key_mode(&call, CallState::RingOut);
6476 state.active_key_mode = key_mode;
6477 refresh_mwi_lamps(stream, state, protocol).await?;
6478 for message in [
6479 ServerMessage::CallState {
6480 state: CallState::Proceed,
6481 line_instance: call.line_instance,
6482 call_reference: call.wire_reference,
6483 },
6484 ServerMessage::DisplayPrompt {
6485 timeout_seconds: 0,
6486 text: "Ring out".into(),
6487 line_instance: call.line_instance,
6488 call_reference: call.wire_reference,
6489 },
6490 ServerMessage::StartTone {
6491 tone: Tone::Alerting,
6492 direction: ToneDirection::User,
6493 line_instance: call.line_instance,
6494 call_reference: call.wire_reference,
6495 },
6496 ServerMessage::SelectSoftKeys {
6497 line_instance: call.line_instance,
6498 call_reference: call.wire_reference,
6499 set: key_mode,
6500 valid_mask: state.device.soft_keys.valid_mask(key_mode),
6501 },
6502 ServerMessage::CallInfo {
6503 info,
6504 line_instance: call.line_instance,
6505 call_reference: call.wire_reference,
6506 },
6507 ] {
6508 send_station_ui_message(stream, state, &message).await?;
6509 }
6510 }
6511 CommandAction::SetCallState {
6512 call_id,
6513 state: call_state,
6514 ..
6515 } => {
6516 let transfer_source_to_clear =
6517 state
6518 .calls_by_id
6519 .get(&call_id)
6520 .and_then(|call| match call.transfer_role {
6521 Some(SessionTransferRole::Source {
6522 consultation_call_id,
6523 }) if call_state != CallState::Transfer => {
6524 Some((consultation_call_id, call.line_instance))
6525 }
6526 _ => None,
6527 });
6528 let call = require_call_mut(state, call_id)?;
6529 call.state = call_state;
6530 call.history_disposition =
6531 updated_history_disposition(call.history_disposition, call_state);
6532 let call = call.clone();
6533 if matches!(
6534 call_state,
6535 CallState::Proceed | CallState::RingOut | CallState::Connected
6536 ) {
6537 remember_last_number(
6538 state,
6539 call.line_instance,
6540 &call.dialed_number,
6541 config,
6542 );
6543 }
6544 prepare_call_state_ui(stream, &call, call_state, protocol).await?;
6545 send_message(
6546 stream,
6547 &ServerMessage::CallState {
6548 state: call_state,
6549 line_instance: call.line_instance,
6550 call_reference: call.wire_reference,
6551 },
6552 protocol,
6553 )
6554 .await?;
6555 finish_call_state_ui(stream, &call, call_state, state.station_context())
6556 .await?;
6557 let set = transfer_key_mode(&call, call_state);
6558 state.active_key_mode = set;
6559 match call_state {
6560 CallState::Connected
6561 | CallState::OffHook
6562 | CallState::Transfer
6563 | CallState::RingOut
6564 | CallState::Proceed
6565 | CallState::IntercomOneWay => {
6566 state.active_call_id = Some(call.call_id);
6567 }
6568 CallState::OnHook
6569 | CallState::Hold
6570 | CallState::HoldYellow
6571 | CallState::HoldRed
6572 if state.active_call_id == Some(call.call_id) =>
6573 {
6574 state.active_call_id = None;
6575 }
6576 _ => {}
6577 }
6578 refresh_mwi_lamps(stream, state, protocol).await?;
6579 send_message(
6580 stream,
6581 &ServerMessage::SelectSoftKeys {
6582 line_instance: call.line_instance,
6583 call_reference: call.wire_reference,
6584 set,
6585 valid_mask: state.device.soft_keys.valid_mask(set),
6586 },
6587 protocol,
6588 )
6589 .await?;
6590 if let Some((consultation_call_id, line_instance)) = transfer_source_to_clear {
6591 if let Some(source) = state.calls_by_id.get_mut(&call_id) {
6592 source.transfer_role = None;
6593 }
6594 if let Some(consultation) = state.calls_by_id.get_mut(&consultation_call_id)
6595 {
6596 consultation.transfer_role = None;
6597 }
6598 send_message(
6599 stream,
6600 &ServerMessage::SetLamp {
6601 stimulus: ButtonType::Transfer,
6602 instance: line_instance,
6603 mode: LampMode::Off,
6604 },
6605 protocol,
6606 )
6607 .await?;
6608 }
6609 }
6610 CommandAction::DisplayPrompt {
6611 call_id,
6612 timeout_seconds,
6613 text,
6614 ..
6615 } => {
6616 let call = require_call(state, call_id)?.clone();
6617 send_station_ui_message(
6618 stream,
6619 state,
6620 &ServerMessage::DisplayPrompt {
6621 timeout_seconds,
6622 text,
6623 line_instance: call.line_instance,
6624 call_reference: call.wire_reference,
6625 },
6626 )
6627 .await?;
6628 }
6629 CommandAction::ClearPrompt { call_id, .. } => {
6630 let call = require_call(state, call_id)?.clone();
6631 send_message(
6632 stream,
6633 &ServerMessage::ClearPrompt {
6634 line_instance: call.line_instance,
6635 call_reference: call.wire_reference,
6636 },
6637 protocol,
6638 )
6639 .await?;
6640 }
6641 CommandAction::SetStatusMessage { message, beep, .. } => {
6642 let frames = status_message_frames(
6643 message,
6644 state.registration.device_type,
6645 &mut state.persistent_status_message,
6646 );
6647 for frame in frames {
6648 send_station_ui_message(stream, state, &frame).await?;
6649 }
6650 if beep {
6651 send_message(
6652 stream,
6653 &ServerMessage::StartTone {
6654 tone: Tone::ZipZip,
6655 direction: ToneDirection::User,
6656 line_instance: 0,
6657 call_reference: 0,
6658 },
6659 protocol,
6660 )
6661 .await?;
6662 }
6663 }
6664 CommandAction::SetMicrophoneMode { enabled, .. } => {
6665 send_message(
6666 stream,
6667 &ServerMessage::SetMicrophoneMode(if enabled {
6668 MicrophoneMode::On
6669 } else {
6670 MicrophoneMode::Off
6671 }),
6672 protocol,
6673 )
6674 .await?;
6675 }
6676 CommandAction::SetRecordingStatus {
6677 call_id, active, ..
6678 } => {
6679 let call = require_call(state, call_id)?.clone();
6680 send_message(
6681 stream,
6682 &ServerMessage::RecordingStatus {
6683 call_reference: call.wire_reference,
6684 active,
6685 },
6686 protocol,
6687 )
6688 .await?;
6689 }
6690 CommandAction::ResetDevice { reset_type, .. } => {
6691 send_message(stream, &ServerMessage::Reset(reset_type), protocol).await?;
6692 }
6693 ringing @ (CommandAction::StartRinging { call_id }
6694 | CommandAction::StopRinging { call_id }) => {
6695 let enabled = matches!(ringing, CommandAction::StartRinging { .. });
6696 let call = require_call(state, call_id)?.clone();
6697 send_message(
6698 stream,
6699 &ServerMessage::SetRinger {
6700 mode: if enabled {
6701 RingerMode::Inside
6702 } else {
6703 RingerMode::Off
6704 },
6705 duration: RingDuration::Normal,
6706 line_instance: call.line_instance,
6707 call_reference: call.wire_reference,
6708 },
6709 protocol,
6710 )
6711 .await?;
6712 }
6713 CommandAction::OpenReceiveChannel {
6714 call_id,
6715 source,
6716 codec,
6717 packet_ms,
6718 max_frames_per_packet,
6719 dtmf_mode,
6720 audio_processing,
6721 ..
6722 } => {
6723 let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
6724 let request = allocate_media_request_identity(state, call_id)?;
6725 let call = require_call_mut(state, call_id)?;
6726 call.media.requested = true;
6727 call.media.codec = codec;
6728 call.media.packet_ms = packet_ms;
6729 call.media.max_frames_per_packet = max_frames_per_packet;
6730 call.media.receive.telephone_event_payload = telephone_event_payload;
6731 call.media.receive.peer = None;
6732 call.media.receive.state = MediaChannelState::Opening;
6733 call.media.receive.deadline =
6734 Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6735 call.media.receive.request = Some(request);
6736 if call.media.transmit.state == MediaChannelState::Closed {
6737 call.media.transmit.request = None;
6738 }
6739 call.media.coupled_transmit_endpoint = None;
6740 let call = call.clone();
6741 send_message(
6742 stream,
6743 &ServerMessage::OpenReceiveChannel {
6744 call_reference: call.wire_reference,
6745 passthrough_party_id: request.token().get(),
6746 packet_ms,
6747 codec,
6748 echo_cancellation: audio_processing.echo_cancellation,
6749 telephone_event_payload,
6750 source_address: source
6751 .map(|endpoint| endpoint.address)
6752 .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
6753 source_port: source.map_or(0, |endpoint| endpoint.rtp_port),
6754 encryption: None,
6755 wire: None,
6756 },
6757 protocol,
6758 )
6759 .await?;
6760 }
6761 CommandAction::OpenMultimediaReceiveChannel {
6762 call_id,
6763 descriptor,
6764 } => {
6765 let call_state = require_call(state, call_id)?.state;
6766 if call_state != CallState::Connected {
6767 return Err(ServerError::InvalidCallTransaction {
6768 call_id,
6769 operation: "open video receive media",
6770 state: call_state,
6771 });
6772 }
6773 validate_multimedia_receive(state, &descriptor)?;
6774 let request = allocate_video_receive_identity(state, call_id)?;
6775 let replacement_close = take_multimedia_receive_close(state, call_id);
6776 let call = require_call_mut(state, call_id)?;
6777 let line_instance = call.line_instance;
6778 let call_reference = CallReference::new(call.wire_reference);
6779 call.video_receive.leg = Some(VideoReceiveLeg {
6780 request,
6781 conference_id: descriptor.conference_id,
6782 codec: descriptor.payload.codec(),
6783 requested_address_type: descriptor.requested_address_type,
6784 state: MediaChannelState::Opening,
6785 deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
6786 });
6787
6788 if let Some(close) = replacement_close {
6789 send_message(stream, &close, protocol).await?;
6790 }
6791 send_message(
6792 stream,
6793 &ServerMessage::OpenMultimediaChannel(OpenMultimediaChannel {
6794 conference_id: descriptor.conference_id,
6795 passthrough_party_id: request.token().get().into(),
6796 line_instance,
6797 call_reference,
6798 payload: descriptor.payload,
6799 conference_creator: descriptor.conference_creator,
6800 encryption: descriptor.encryption,
6801 stream_passthrough_id: descriptor.stream_passthrough_id,
6802 associated_stream_id: descriptor.associated_stream_id,
6803 source: descriptor.source,
6804 requested_address_type: descriptor.requested_address_type,
6805 }),
6806 protocol,
6807 )
6808 .await?;
6809 }
6810 CommandAction::CloseMultimediaReceiveChannel { call_id } => {
6811 if let Some(close) = take_multimedia_receive_close(state, call_id) {
6812 send_message(stream, &close, protocol).await?;
6813 }
6814 }
6815 CommandAction::StartMultimediaTransmission {
6816 call_id,
6817 descriptor,
6818 } => {
6819 let call_state = require_call(state, call_id)?.state;
6820 if call_state != CallState::Connected {
6821 return Err(ServerError::InvalidCallTransaction {
6822 call_id,
6823 operation: "start video transmit media",
6824 state: call_state,
6825 });
6826 }
6827 validate_multimedia_transmit(state, &descriptor)?;
6828 let request = allocate_video_transmit_identity(state, call_id)?;
6829 let replacement_stop = take_multimedia_transmit_stop(state, call_id);
6830 let call_reference = {
6831 let call = require_call_mut(state, call_id)?;
6832 let call_reference = CallReference::new(call.wire_reference);
6833 call.video_transmit.leg = Some(VideoTransmitLeg {
6834 request,
6835 conference_id: descriptor.conference_id,
6836 codec: descriptor.payload.codec(),
6837 address_type: address_type(descriptor.endpoint.address),
6838 state: MediaChannelState::Opening,
6839 deadline: Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT),
6840 });
6841 call_reference
6842 };
6843
6844 if let Some(stop) = replacement_stop {
6845 send_message(stream, &stop, protocol).await?;
6846 }
6847 send_message(
6848 stream,
6849 &ServerMessage::StartMultimediaTransmission(MultimediaTransmissionStart {
6850 conference_id: descriptor.conference_id,
6851 passthrough_party_id: request.token().get().into(),
6852 endpoint: descriptor.endpoint,
6853 call_reference,
6854 payload: descriptor.payload,
6855 traffic_class: descriptor.traffic_class,
6856 encryption: descriptor.encryption,
6857 stream_passthrough_id: descriptor.stream_passthrough_id,
6858 associated_stream_id: descriptor.associated_stream_id,
6859 }),
6860 protocol,
6861 )
6862 .await?;
6863 }
6864 CommandAction::StopMultimediaTransmission { call_id } => {
6865 if let Some(stop) = take_multimedia_transmit_stop(state, call_id) {
6866 send_message(stream, &stop, protocol).await?;
6867 }
6868 }
6869 flow_action @ (CommandAction::SetMultimediaTransmitBitRate {
6870 call_id,
6871 passthrough_party_id,
6872 maximum_bit_rate,
6873 }
6874 | CommandAction::NotifyMultimediaTransmitBitRate {
6875 call_id,
6876 passthrough_party_id,
6877 maximum_bit_rate,
6878 }) => {
6879 if maximum_bit_rate == 0 {
6880 return Err(ServerError::InvalidMultimediaTransmitControl(
6881 "maximum bit rate must be nonzero",
6882 ));
6883 }
6884 let (conference_id, call_reference) =
6885 multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
6886 let flow = VideoFlowControl {
6887 conference_id,
6888 passthrough_party_id,
6889 call_reference,
6890 maximum_bit_rate,
6891 };
6892 let message = if matches!(
6893 flow_action,
6894 CommandAction::SetMultimediaTransmitBitRate { .. }
6895 ) {
6896 ServerMessage::FlowControlCommand(flow)
6897 } else {
6898 ServerMessage::FlowControlNotify(flow)
6899 };
6900 send_message(stream, &message, protocol).await?;
6901 }
6902 CommandAction::ControlMultimediaTransmission {
6903 call_id,
6904 passthrough_party_id,
6905 control,
6906 } => {
6907 let (conference_id, call_reference) =
6908 multimedia_transmit_control_identity(state, call_id, passthrough_party_id)?;
6909 let (command, data) = encode_multimedia_transmit_control(control)?;
6910 send_message(
6911 stream,
6912 &ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
6913 conference_id,
6914 passthrough_party_id,
6915 call_reference,
6916 command,
6917 data,
6918 }),
6919 protocol,
6920 )
6921 .await?;
6922 }
6923 CommandAction::OpenOutboundMedia {
6924 call_id,
6925 source,
6926 mut endpoint,
6927 codec,
6928 packet_ms,
6929 max_frames_per_packet,
6930 dtmf_mode,
6931 audio_processing,
6932 traffic_class,
6933 } => {
6934 let call_state = require_call(state, call_id)?.state;
6935 if !matches!(call_state, CallState::Proceed | CallState::RingOut) {
6936 return Err(ServerError::InvalidCallTransaction {
6937 call_id,
6938 operation: "open coupled outbound media",
6939 state: call_state,
6940 });
6941 }
6942 let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
6943 let source_address = source
6944 .map(|source| source.address)
6945 .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
6946 let source_port = source.map_or(0, |source| source.rtp_port);
6947 let request = allocate_media_request_identity(state, call_id)?;
6948 let call = require_call_mut(state, call_id)?;
6949 call.media.requested = true;
6950 call.media.codec = codec;
6951 call.media.packet_ms = packet_ms;
6952 call.media.max_frames_per_packet = max_frames_per_packet;
6953 call.media.receive.telephone_event_payload = telephone_event_payload;
6954 call.media.receive.peer = None;
6955 call.media.receive.state = MediaChannelState::Opening;
6956 call.media.receive.deadline =
6957 Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6958 call.media.receive.request = Some(request);
6959 call.media.transmit.telephone_event_payload = telephone_event_payload;
6960 call.media.transmit.peer = None;
6961 call.media.transmit.state = MediaChannelState::Opening;
6962 call.media.transmit.deadline =
6963 Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
6964 call.media.transmit.request = Some(request);
6965 endpoint.telephone_event_payload = telephone_event_payload;
6966 call.media.coupled_transmit_endpoint = Some(endpoint);
6967 let call = call.clone();
6968 send_message(
6969 stream,
6970 &ServerMessage::OpenReceiveChannel {
6971 call_reference: call.wire_reference,
6972 passthrough_party_id: request.token().get(),
6973 packet_ms,
6974 codec,
6975 echo_cancellation: audio_processing.echo_cancellation,
6976 telephone_event_payload,
6977 source_address,
6978 source_port,
6979 encryption: None,
6980 wire: None,
6981 },
6982 protocol,
6983 )
6984 .await?;
6985 send_message(
6986 stream,
6987 &ServerMessage::StartMediaTransmission {
6988 call_reference: call.wire_reference,
6989 passthrough_party_id: request.token().get(),
6990 endpoint,
6991 silence_suppression: audio_processing.silence_suppression,
6992 traffic_class,
6993 encryption: None,
6994 wire: None,
6995 },
6996 protocol,
6997 )
6998 .await?;
6999 }
7000 CommandAction::CloseReceiveChannel { call_id, .. } => {
7001 let call = require_call_mut(state, call_id)?;
7002 call.media.coupled_transmit_endpoint = None;
7003 if call.media.receive.state != MediaChannelState::Closed {
7004 call.media.receive.state = MediaChannelState::Closed;
7005 call.media.receive.deadline = None;
7006 let call = call.clone();
7007 send_message(
7008 stream,
7009 &ServerMessage::CloseReceiveChannel(AudioStreamControl {
7010 conference_id: ConferenceId::new(call.wire_reference),
7011 call_reference: CallReference::new(call.wire_reference),
7012 passthrough_party_id: media_request_party_id(
7013 call.media.receive.request,
7014 call.wire_reference,
7015 )
7016 .into(),
7017 port_handling_flag: 0,
7018 }),
7019 protocol,
7020 )
7021 .await?;
7022 }
7023 }
7024 CommandAction::StartMedia {
7025 call_id,
7026 mut endpoint,
7027 dtmf_mode,
7028 audio_processing,
7029 traffic_class,
7030 } => {
7031 let telephone_event_payload = dtmf_mode.telephone_event_payload(state.features);
7032 let request = {
7033 let call = require_call(state, call_id)?;
7034 if call.media.transmit.request.is_none() {
7035 call.media.receive.request
7036 } else {
7037 None
7038 }
7039 };
7040 let request = match request {
7041 Some(request) => request,
7042 None => allocate_media_request_identity(state, call_id)?,
7043 };
7044 let call = require_call_mut(state, call_id)?;
7045 call.media.requested = true;
7046 call.media.transmit.telephone_event_payload = telephone_event_payload;
7047 call.media.transmit.peer = None;
7048 call.media.transmit.state = MediaChannelState::Opening;
7049 call.media.transmit.deadline =
7050 Some(Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT);
7051 call.media.transmit.request = Some(request);
7052 call.media.coupled_transmit_endpoint = None;
7053 let call = call.clone();
7054 endpoint.telephone_event_payload = telephone_event_payload;
7055 send_message(
7056 stream,
7057 &ServerMessage::StartMediaTransmission {
7058 call_reference: call.wire_reference,
7059 passthrough_party_id: request.token().get(),
7060 endpoint,
7061 silence_suppression: audio_processing.silence_suppression,
7062 traffic_class,
7063 encryption: None,
7064 wire: None,
7065 },
7066 protocol,
7067 )
7068 .await?;
7069 }
7070 CommandAction::StartMulticastReception {
7071 conference_id,
7072 call_id,
7073 route,
7074 echo_cancellation,
7075 g723_bitrate,
7076 } => {
7077 validate_multicast_route(state, route, None)?;
7078 let wire_call_reference = require_call(state, call_id)?.wire_reference;
7079 let request = allocate_multicast_request_identity(state)?;
7080 let key = MulticastKey {
7081 conference_id,
7082 call_id,
7083 };
7084 if let Some(stop) = take_multicast_stop(state, key, true) {
7085 send_message(stream, &stop, protocol).await?;
7086 }
7087 send_message(
7088 stream,
7089 &ServerMessage::StartMulticastMediaReception(MulticastMediaReception {
7090 conference_id,
7091 passthrough_party_id: request.token().get().into(),
7092 call_reference: CallReference::new(wire_call_reference),
7093 address: route.address,
7094 port: route.port,
7095 packet_millis: route.packet_millis,
7096 codec: route.codec,
7097 echo_cancellation,
7098 g723_bitrate,
7099 }),
7100 protocol,
7101 )
7102 .await?;
7103 state
7104 .multicast
7105 .entry(key)
7106 .or_insert_with(|| MulticastSession {
7107 wire_call_reference,
7108 receive: None,
7109 transmit: None,
7110 })
7111 .receive = Some(MulticastReceive {
7112 request,
7113 route,
7114 state: MulticastReceiveState::AwaitingAcknowledgement {
7115 deadline: Instant::now() + HANDSET_ACKNOWLEDGEMENT_TIMEOUT,
7116 },
7117 });
7118 }
7119 CommandAction::StopMulticastReception {
7120 conference_id,
7121 call_id,
7122 } => {
7123 let key = MulticastKey {
7124 conference_id,
7125 call_id,
7126 };
7127 if let Some(stop) = take_multicast_stop(state, key, true) {
7128 send_message(stream, &stop, protocol).await?;
7129 }
7130 }
7131 CommandAction::StartMulticastTransmission {
7132 conference_id,
7133 call_id,
7134 route,
7135 precedence,
7136 silence_suppression,
7137 max_frames_per_packet,
7138 g723_bitrate,
7139 } => {
7140 validate_multicast_route(state, route, Some(max_frames_per_packet))?;
7141 let wire_call_reference = require_call(state, call_id)?.wire_reference;
7142 let request = allocate_multicast_request_identity(state)?;
7143 let key = MulticastKey {
7144 conference_id,
7145 call_id,
7146 };
7147 if let Some(stop) = take_multicast_stop(state, key, false) {
7148 send_message(stream, &stop, protocol).await?;
7149 }
7150 send_message(
7151 stream,
7152 &ServerMessage::StartMulticastMediaTransmission(
7153 MulticastMediaTransmission {
7154 conference_id,
7155 passthrough_party_id: request.token().get().into(),
7156 call_reference: CallReference::new(wire_call_reference),
7157 address: route.address,
7158 port: route.port,
7159 packet_millis: route.packet_millis,
7160 codec: route.codec,
7161 precedence,
7162 silence_suppression: silence_suppression.wire_value(),
7163 max_frames_per_packet,
7164 g723_bitrate,
7165 },
7166 ),
7167 protocol,
7168 )
7169 .await?;
7170 state
7171 .multicast
7172 .entry(key)
7173 .or_insert_with(|| MulticastSession {
7174 wire_call_reference,
7175 receive: None,
7176 transmit: None,
7177 })
7178 .transmit = Some(MulticastTransmit { request, route });
7179 context
7180 .event_tx
7181 .send(Event::device(
7182 state.device.id.clone(),
7183 state.generation,
7184 DeviceEventKind::MulticastTransmissionStarted {
7185 conference_id,
7186 call_id,
7187 route,
7188 },
7189 ))
7190 .await
7191 .map_err(|_| ServerError::Stopped)?;
7192 }
7193 CommandAction::StopMulticastTransmission {
7194 conference_id,
7195 call_id,
7196 } => {
7197 let key = MulticastKey {
7198 conference_id,
7199 call_id,
7200 };
7201 if let Some(stop) = take_multicast_stop(state, key, false) {
7202 send_message(stream, &stop, protocol).await?;
7203 }
7204 }
7205 CommandAction::StopMedia { call_id, .. } => {
7206 if let Some(call) = state
7207 .calls_by_id
7208 .get_mut(&call_id)
7209 .filter(|call| call.media.transmit.state != MediaChannelState::Closed)
7210 {
7211 call.media.transmit.state = MediaChannelState::Closed;
7212 call.media.transmit.deadline = None;
7213 call.media.coupled_transmit_endpoint = None;
7214 let call = call.clone();
7215 send_message(
7216 stream,
7217 &ServerMessage::StopMediaTransmission(AudioStreamControl {
7218 conference_id: ConferenceId::new(call.wire_reference),
7219 call_reference: CallReference::new(call.wire_reference),
7220 passthrough_party_id: media_request_party_id(
7221 call.media.transmit.request,
7222 call.wire_reference,
7223 )
7224 .into(),
7225 port_handling_flag: 0,
7226 }),
7227 protocol,
7228 )
7229 .await?;
7230 }
7231 }
7232 CommandAction::CloseCall { call_id, .. } => {
7233 if let Some(call) = state.calls_by_id.get(&call_id).cloned() {
7234 state.active_key_mode = KeyMode::OnHook;
7235 stop_call_multicast(stream, state, call_id, protocol).await?;
7236 if call.state != CallState::OnHook {
7237 close_call_media_messages(stream, &call, protocol).await?;
7238 close_call_messages(
7239 stream,
7240 &call,
7241 &state.device.soft_keys,
7242 protocol,
7243 context.config.timezone_offset_minutes,
7244 )
7245 .await?;
7246 request_connection_statistics(stream, state, &call, context).await?;
7247 }
7248 remove_call(state, call_id);
7249 refresh_mwi_lamps(stream, state, protocol).await?;
7250 } else {
7251 state.cancelled_calls.insert(call_id);
7252 }
7253 }
7254 }
7255 }
7256 }
7257 Ok(false)
7258}
7259
7260async fn send_mwi_lamp(
7261 stream: &mut dyn StationIo,
7262 state: &SessionState,
7263 line_instance: u32,
7264 enabled: bool,
7265 protocol: ProtocolVersion,
7266) -> Result<(), ServerError> {
7267 let mode = projected_mwi_lamp(state.device.ui, state.active_call_id.is_some(), enabled);
7268 send_message(
7269 stream,
7270 &ServerMessage::SetLamp {
7271 stimulus: ButtonType::Voicemail,
7272 instance: line_instance,
7273 mode,
7274 },
7275 protocol,
7276 )
7277 .await
7278}
7279
7280fn projected_mwi_lamp(ui: crate::types::StationUiPolicy, on_call: bool, enabled: bool) -> LampMode {
7281 if enabled && (ui.mwi_on_call || !on_call) {
7282 ui.mwi_lamp_mode
7283 } else {
7284 LampMode::Off
7285 }
7286}
7287
7288fn updated_history_disposition(
7289 current: CallHistoryDisposition,
7290 state: CallState,
7291) -> CallHistoryDisposition {
7292 if current != CallHistoryDisposition::Missed {
7293 return current;
7294 }
7295 match state {
7296 CallState::Connected => CallHistoryDisposition::Received,
7297 CallState::RemoteMultiline => CallHistoryDisposition::Ignore,
7298 _ => current,
7299 }
7300}
7301
7302async fn refresh_mwi_lamps(
7303 stream: &mut dyn StationIo,
7304 state: &SessionState,
7305 protocol: ProtocolVersion,
7306) -> Result<(), ServerError> {
7307 for (&line_instance, &enabled) in &state.mwi_by_line {
7308 send_mwi_lamp(stream, state, line_instance, enabled, protocol).await?;
7309 }
7310 Ok(())
7311}
7312
7313fn incoming_ringer(
7314 ringer: Option<IncomingRing>,
7315 incoming_state: CallState,
7316) -> Option<IncomingRing> {
7317 ringer.map(|mut ringer| {
7318 if incoming_state == CallState::CallWaiting {
7319 ringer.duration = RingDuration::Single;
7320 if ringer.mode != RingerMode::Urgent {
7321 ringer.mode = RingerMode::Silent;
7322 }
7323 }
7324 ringer
7325 })
7326}
7327
7328async fn request_connection_statistics(
7329 stream: &mut dyn StationIo,
7330 state: &mut SessionState,
7331 call: &SessionCall,
7332 context: &SessionContext,
7333) -> Result<(), ServerError> {
7334 prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
7335 if !call.media.requested
7336 || state.pending_connection_statistics.len() >= MAX_PENDING_CONNECTION_STATISTICS
7337 || state.statistics_references.len() >= MAX_STATISTICS_REFERENCES_PER_SESSION
7338 {
7339 return Ok(());
7340 }
7341 let directory_number = if call.statistics_directory_number.is_empty() {
7342 call.dialed_number.trim()
7343 } else {
7344 call.statistics_directory_number.trim()
7345 };
7346 let maximum = if state.registration.protocol >= ProtocolVersion::V19 {
7347 24
7348 } else {
7349 23
7350 };
7351 if directory_number.is_empty()
7352 || directory_number.len() > maximum
7353 || directory_number.contains(['\0', '\r', '\n'])
7354 {
7355 warn!(
7356 device_id = %state.device.id,
7357 ?call.call_id,
7358 byte_count = directory_number.len(),
7359 "skipping connection-statistics request with unusable directory number"
7360 );
7361 return Ok(());
7362 }
7363 if !state.statistics_references.insert(call.wire_reference) {
7364 warn!(
7365 device_id = %state.device.id,
7366 ?call.call_id,
7367 call_reference = call.wire_reference,
7368 "skipping connection-statistics request for a reused call reference"
7369 );
7370 return Ok(());
7371 }
7372 let request_generation = context
7373 .next_statistics_generation
7374 .fetch_add(1, Ordering::Relaxed);
7375 let processing = StatisticsProcessing::Clear;
7376 state.pending_connection_statistics.insert(
7377 call.wire_reference,
7378 PendingConnectionStatistics {
7379 session_generation: state.generation,
7380 request_generation,
7381 call_id: call.call_id,
7382 line_instance: call.line_instance,
7383 codec: call.media.codec,
7384 packet_ms: call.media.packet_ms,
7385 max_frames_per_packet: call.media.max_frames_per_packet,
7386 receive_peer: call.media.receive.peer,
7387 transmit_peer: call.media.transmit.peer,
7388 directory_number: directory_number.to_owned(),
7389 processing,
7390 expires_at: Instant::now() + CONNECTION_STATISTICS_TIMEOUT,
7391 },
7392 );
7393 send_message(
7394 stream,
7395 &ServerMessage::ConnectionStatisticsRequest {
7396 directory_number: directory_number.to_owned(),
7397 call_reference: call.wire_reference,
7398 processing,
7399 },
7400 state.registration.protocol,
7401 )
7402 .await
7403}
7404
7405fn statistics_directory_for_call_info(info: &CallInfo) -> &str {
7406 match info.direction {
7407 crate::types::CallDirection::Inbound => &info.calling_number,
7408 crate::types::CallDirection::Outbound => &info.called_number,
7409 }
7410}
7411
7412fn prune_connection_statistics(
7413 pending_statistics: &mut HashMap<u32, PendingConnectionStatistics>,
7414 now: Instant,
7415) {
7416 pending_statistics.retain(|_, pending| pending.expires_at > now);
7417}
7418
7419async fn collect_connection_statistics(
7420 state: &mut SessionState,
7421 statistics: ConnectionStatistics,
7422 context: &SessionContext,
7423) -> Result<(), ServerError> {
7424 prune_connection_statistics(&mut state.pending_connection_statistics, Instant::now());
7425 let Some(pending) = state
7426 .pending_connection_statistics
7427 .get(&statistics.call_reference)
7428 .cloned()
7429 else {
7430 warn!(
7431 device_id = %state.device.id,
7432 call_reference = statistics.call_reference,
7433 "ignoring unsolicited or expired connection-statistics response"
7434 );
7435 return Ok(());
7436 };
7437 let current_session = context
7438 .sessions
7439 .lock()
7440 .await
7441 .get(&state.device.id)
7442 .is_some_and(|session| session.generation == pending.session_generation);
7443 if !current_session
7444 || pending.session_generation != state.generation
7445 || statistics.processing != pending.processing
7446 || statistics.directory_number != pending.directory_number
7447 {
7448 warn!(
7449 device_id = %state.device.id,
7450 call_reference = statistics.call_reference,
7451 processing = ?statistics.processing,
7452 "ignoring mismatched connection-statistics response"
7453 );
7454 return Ok(());
7455 }
7456 state
7457 .pending_connection_statistics
7458 .remove(&statistics.call_reference);
7459 let snapshot = MediaStatisticsSnapshot {
7460 request_generation: pending.request_generation,
7461 call_id: pending.call_id,
7462 line_instance: LineInstance::new(pending.line_instance),
7463 codec: pending.codec,
7464 packet_ms: pending.packet_ms,
7465 max_frames_per_packet: pending.max_frames_per_packet,
7466 receive_peer: pending.receive_peer,
7467 transmit_peer: pending.transmit_peer,
7468 packets_sent: statistics.packets_sent,
7469 octets_sent: statistics.octets_sent,
7470 packets_received: statistics.packets_received,
7471 octets_received: statistics.octets_received,
7472 packets_lost: statistics.packets_lost,
7473 jitter_millis: statistics.jitter_millis,
7474 latency_millis: statistics.latency_millis,
7475 quality_byte_count: statistics.quality.as_bytes().len(),
7476 };
7477 {
7478 let mut latest = context
7479 .latest_media_statistics
7480 .write()
7481 .expect("SCCP media-statistics lock poisoned");
7482 let replace = latest
7483 .get(&state.device.id)
7484 .is_none_or(|existing| existing.request_generation < snapshot.request_generation);
7485 if !replace {
7486 return Ok(());
7487 }
7488 latest.insert(state.device.id.clone(), snapshot.clone());
7489 }
7490 context
7491 .event_tx
7492 .send(Event::device(
7493 state.device.id.clone(),
7494 state.generation,
7495 DeviceEventKind::ConnectionStatisticsCollected { snapshot },
7496 ))
7497 .await
7498 .map_err(|_| ServerError::Stopped)
7499}
7500
7501fn status_message_frames(
7502 message: HandsetStatusMessage,
7503 device_type: DeviceType,
7504 persistent: &mut bool,
7505) -> Vec<ServerMessage> {
7506 let prompt_for_timed_message = matches!(
7507 device_type,
7508 DeviceType::Cisco6901
7509 | DeviceType::Cisco6921
7510 | DeviceType::Cisco6941
7511 | DeviceType::Cisco6945
7512 | DeviceType::Cisco6961
7513 );
7514 match message {
7515 HandsetStatusMessage::Display {
7516 text,
7517 timeout_seconds,
7518 priority: Some(priority),
7519 } => vec![ServerMessage::DisplayPriorityNotify {
7520 timeout_seconds: u32::from(timeout_seconds),
7521 priority,
7522 text,
7523 }],
7524 HandsetStatusMessage::Clear {
7525 priority: Some(priority),
7526 } => vec![ServerMessage::ClearPriorityNotify { priority }],
7527 HandsetStatusMessage::Display {
7528 text,
7529 timeout_seconds,
7530 priority: None,
7531 } if timeout_seconds == 0 || prompt_for_timed_message => {
7532 if timeout_seconds == 0 {
7533 *persistent = true;
7534 }
7535 vec![ServerMessage::DisplayPrompt {
7536 timeout_seconds: u32::from(timeout_seconds),
7537 text,
7538 line_instance: 0,
7539 call_reference: 0,
7540 }]
7541 }
7542 HandsetStatusMessage::Display {
7543 text,
7544 timeout_seconds,
7545 priority: None,
7546 } => vec![ServerMessage::DisplayPriorityNotify {
7547 timeout_seconds: u32::from(timeout_seconds),
7548 priority: NotificationPriority::Timed,
7549 text,
7550 }],
7551 HandsetStatusMessage::Clear { priority: None } => {
7552 let clear_prompt = std::mem::take(persistent) || prompt_for_timed_message;
7553 let mut frames = Vec::with_capacity(2);
7554 if clear_prompt {
7555 frames.push(ServerMessage::ClearPrompt {
7556 line_instance: 0,
7557 call_reference: 0,
7558 });
7559 }
7560 if !prompt_for_timed_message {
7561 frames.push(ServerMessage::ClearPriorityNotify {
7562 priority: NotificationPriority::Timed,
7563 });
7564 }
7565 frames
7566 }
7567 }
7568}
7569
7570async fn send_message(
7571 stream: &mut dyn StationIo,
7572 message: &ServerMessage,
7573 session: impl Into<StationSessionContext>,
7574) -> Result<(), ServerError> {
7575 stream
7576 .write_all(&message.encode_for_session(session.into())?)
7577 .await?;
7578 Ok(())
7579}
7580
7581async fn send_station_ui_message(
7582 stream: &mut dyn StationIo,
7583 state: &SessionState,
7584 message: &ServerMessage,
7585) -> Result<(), ServerError> {
7586 let session = state.station_context();
7587 let bytes = if state.features.contains(PhoneFeatures::UTF8) {
7588 message.encode_for_session(session)?
7589 } else {
7590 message.encode_for_legacy_session(session, state.device.ui.legacy_code_page)?
7591 };
7592 stream.write_all(&bytes).await?;
7593 Ok(())
7594}
7595
7596async fn begin_phone_call_ui(
7597 stream: &mut dyn StationIo,
7598 call: &SessionCall,
7599 device: &DeviceDefinition,
7600 session: StationSessionContext,
7601) -> Result<(), ServerError> {
7602 begin_phone_call_ui_with_key_mode(stream, call, device, KeyMode::OffHook, session).await
7603}
7604
7605async fn begin_phone_call_ui_with_key_mode(
7606 stream: &mut dyn StationIo,
7607 call: &SessionCall,
7608 device: &DeviceDefinition,
7609 key_mode: KeyMode,
7610 session: StationSessionContext,
7611) -> Result<(), ServerError> {
7612 let initial_tone = device
7613 .line(call.line_instance)
7614 .map_or(Tone::InsideDial, |line| line.initial_tone);
7615 send_message(
7616 stream,
7617 &ServerMessage::SetSpeakerMode(SpeakerMode::On),
7618 session,
7619 )
7620 .await?;
7621 send_message(
7622 stream,
7623 &ServerMessage::SetLamp {
7624 stimulus: ButtonType::Line,
7625 instance: call.line_instance,
7626 mode: LampMode::On,
7627 },
7628 session,
7629 )
7630 .await?;
7631 send_message(
7632 stream,
7633 &ServerMessage::CallState {
7634 state: CallState::OffHook,
7635 line_instance: call.line_instance,
7636 call_reference: call.wire_reference,
7637 },
7638 session,
7639 )
7640 .await?;
7641 send_message(
7642 stream,
7643 &ServerMessage::ActivateCallPlane {
7644 line_instance: call.line_instance,
7645 },
7646 session,
7647 )
7648 .await?;
7649 send_message(
7650 stream,
7651 &ServerMessage::DisplayPrompt {
7652 timeout_seconds: 0,
7653 text: "Enter number".into(),
7654 line_instance: call.line_instance,
7655 call_reference: call.wire_reference,
7656 },
7657 session,
7658 )
7659 .await?;
7660 send_message(
7661 stream,
7662 &ServerMessage::StartTone {
7663 tone: initial_tone,
7664 direction: ToneDirection::User,
7665 line_instance: call.line_instance,
7666 call_reference: call.wire_reference,
7667 },
7668 session,
7669 )
7670 .await?;
7671 send_message(
7672 stream,
7673 &ServerMessage::SelectSoftKeys {
7674 line_instance: call.line_instance,
7675 call_reference: call.wire_reference,
7676 set: key_mode,
7677 valid_mask: device.soft_keys.valid_mask(key_mode),
7678 },
7679 session,
7680 )
7681 .await
7682}
7683
7684async fn begin_answer_ui(
7685 stream: &mut dyn StationIo,
7686 call: &SessionCall,
7687 protocol: ProtocolVersion,
7688) -> Result<(), ServerError> {
7689 send_message(
7690 stream,
7691 &ServerMessage::SetRinger {
7692 mode: RingerMode::Off,
7693 duration: RingDuration::Normal,
7694 line_instance: call.line_instance,
7695 call_reference: call.wire_reference,
7696 },
7697 protocol,
7698 )
7699 .await?;
7700 send_message(
7701 stream,
7702 &ServerMessage::CallState {
7703 state: CallState::OffHook,
7704 line_instance: call.line_instance,
7705 call_reference: call.wire_reference,
7706 },
7707 protocol,
7708 )
7709 .await?;
7710 send_message(
7711 stream,
7712 &ServerMessage::ActivateCallPlane {
7713 line_instance: call.line_instance,
7714 },
7715 protocol,
7716 )
7717 .await?;
7718 send_message(
7719 stream,
7720 &ServerMessage::StopTone {
7721 line_instance: call.line_instance,
7722 call_reference: call.wire_reference,
7723 },
7724 protocol,
7725 )
7726 .await?;
7727 send_message(
7728 stream,
7729 &ServerMessage::SetLamp {
7730 stimulus: ButtonType::Line,
7731 instance: call.line_instance,
7732 mode: LampMode::On,
7733 },
7734 protocol,
7735 )
7736 .await?;
7737 Ok(())
7738}
7739
7740async fn prepare_call_state_ui(
7741 stream: &mut dyn StationIo,
7742 call: &SessionCall,
7743 state: CallState,
7744 protocol: ProtocolVersion,
7745) -> Result<(), ServerError> {
7746 match state {
7747 CallState::Connected => {
7748 send_message(
7749 stream,
7750 &ServerMessage::SetRinger {
7751 mode: RingerMode::Off,
7752 duration: RingDuration::Normal,
7753 line_instance: call.line_instance,
7754 call_reference: call.wire_reference,
7755 },
7756 protocol,
7757 )
7758 .await?;
7759 send_message(
7760 stream,
7761 &ServerMessage::SetSpeakerMode(SpeakerMode::On),
7762 protocol,
7763 )
7764 .await?;
7765 send_message(
7766 stream,
7767 &ServerMessage::StopTone {
7768 line_instance: call.line_instance,
7769 call_reference: call.wire_reference,
7770 },
7771 protocol,
7772 )
7773 .await?;
7774 send_message(
7775 stream,
7776 &ServerMessage::SetLamp {
7777 stimulus: ButtonType::Line,
7778 instance: call.line_instance,
7779 mode: LampMode::On,
7780 },
7781 protocol,
7782 )
7783 .await?;
7784 }
7785 CallState::RemoteMultiline => {
7786 send_message(
7787 stream,
7788 &ServerMessage::SetRinger {
7789 mode: RingerMode::Off,
7790 duration: RingDuration::Normal,
7791 line_instance: call.line_instance,
7792 call_reference: call.wire_reference,
7793 },
7794 protocol,
7795 )
7796 .await?;
7797 send_message(
7798 stream,
7799 &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
7800 protocol,
7801 )
7802 .await?;
7803 send_message(
7804 stream,
7805 &ServerMessage::SetLamp {
7806 stimulus: ButtonType::Line,
7807 instance: call.line_instance,
7808 mode: LampMode::On,
7809 },
7810 protocol,
7811 )
7812 .await?;
7813 }
7814 CallState::OnHook => {
7815 send_message(
7816 stream,
7817 &ServerMessage::SetRinger {
7818 mode: RingerMode::Off,
7819 duration: RingDuration::Normal,
7820 line_instance: call.line_instance,
7821 call_reference: call.wire_reference,
7822 },
7823 protocol,
7824 )
7825 .await?;
7826 }
7827 CallState::Hold | CallState::HoldYellow | CallState::HoldRed => {
7828 send_message(
7829 stream,
7830 &ServerMessage::SetLamp {
7831 stimulus: ButtonType::Line,
7832 instance: call.line_instance,
7833 mode: LampMode::Wink,
7834 },
7835 protocol,
7836 )
7837 .await?;
7838 }
7839 CallState::RingOut | CallState::Proceed => {
7840 send_message(
7841 stream,
7842 &ServerMessage::SetLamp {
7843 stimulus: ButtonType::Line,
7844 instance: call.line_instance,
7845 mode: LampMode::Blink,
7846 },
7847 protocol,
7848 )
7849 .await?;
7850 }
7851 _ => {}
7852 }
7853 Ok(())
7854}
7855
7856async fn finish_call_state_ui(
7857 stream: &mut dyn StationIo,
7858 call: &SessionCall,
7859 state: CallState,
7860 session: StationSessionContext,
7861) -> Result<(), ServerError> {
7862 let prompt = match state {
7863 CallState::Connected => Some("Connected"),
7864 CallState::Hold | CallState::HoldYellow | CallState::HoldRed => Some("Hold"),
7865 CallState::RingOut => Some("Ring out"),
7866 CallState::Proceed => Some("Call proceeding"),
7867 CallState::Busy => Some("Busy"),
7868 CallState::Congestion => Some("Network congestion"),
7869 CallState::InvalidNumber => Some("Unknown number"),
7870 _ => None,
7871 };
7872 if state == CallState::Connected {
7873 send_message(
7874 stream,
7875 &ServerMessage::ActivateCallPlane {
7876 line_instance: call.line_instance,
7877 },
7878 session,
7879 )
7880 .await?;
7881 } else if matches!(
7882 state,
7883 CallState::Hold | CallState::HoldYellow | CallState::HoldRed
7884 ) {
7885 send_message(
7886 stream,
7887 &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
7888 session,
7889 )
7890 .await?;
7891 }
7892 if let Some(text) = prompt {
7893 send_message(
7894 stream,
7895 &ServerMessage::DisplayPrompt {
7896 timeout_seconds: 0,
7897 text: text.into(),
7898 line_instance: call.line_instance,
7899 call_reference: call.wire_reference,
7900 },
7901 session,
7902 )
7903 .await?;
7904 }
7905 Ok(())
7906}
7907
7908fn normalize_line(state: &SessionState, requested: u32) -> u32 {
7909 if requested != 0 && state.device.line(requested).is_some() {
7910 requested
7911 } else {
7912 state.device.first_line().map_or(1, |line| line.instance)
7913 }
7914}
7915
7916fn ensure_phone_call(
7917 state: &mut SessionState,
7918 wire_reference: u32,
7919 line_instance: u32,
7920 next: &AtomicU64,
7921) -> SessionCall {
7922 let reusable = if wire_reference == 0 {
7923 state
7924 .calls_by_id
7925 .values()
7926 .filter(|call| call.state != CallState::OnHook)
7927 .max_by_key(|call| call.call_id.0)
7928 } else {
7929 find_call(state, wire_reference).filter(|call| call.state != CallState::OnHook)
7930 };
7931 if let Some(call) = reusable {
7932 return call.clone();
7933 }
7934 let mut call = reserve_phone_call(state, line_instance, next);
7935 if wire_reference != 0
7936 && wire_reference != call.wire_reference
7937 && !state.statistics_references.contains(&wire_reference)
7938 {
7939 state.calls_by_wire.remove(&call.wire_reference);
7940 call.wire_reference = wire_reference;
7941 state.calls_by_wire.insert(wire_reference, call.call_id);
7942 state.calls_by_id.insert(call.call_id, call.clone());
7943 }
7944 call
7945}
7946
7947fn reserve_phone_call(
7948 state: &mut SessionState,
7949 line_instance: u32,
7950 next: &AtomicU64,
7951) -> SessionCall {
7952 let call_id = CallId(next.fetch_add(1, Ordering::Relaxed));
7953 insert_call(
7954 state,
7955 call_id,
7956 line_instance,
7957 Codec::Pcmu,
7958 CallState::OffHook,
7959 )
7960}
7961
7962fn insert_call(
7963 state: &mut SessionState,
7964 call_id: CallId,
7965 line_instance: u32,
7966 codec: Codec,
7967 call_state: CallState,
7968) -> SessionCall {
7969 let mut wire_reference = (call_id.0 as u32).max(1);
7970 while state.calls_by_wire.contains_key(&wire_reference)
7971 || state.statistics_references.contains(&wire_reference)
7972 {
7973 wire_reference = wire_reference.wrapping_add(1).max(1);
7974 }
7975 let call = SessionCall {
7976 call_id,
7977 wire_reference,
7978 line_instance,
7979 media: CallMedia::new(codec),
7980 video_receive: VideoReceive::default(),
7981 video_transmit: VideoTransmit::default(),
7982 state: call_state,
7983 history_disposition: if matches!(call_state, CallState::RingIn | CallState::CallWaiting) {
7984 CallHistoryDisposition::Missed
7985 } else {
7986 CallHistoryDisposition::Placed
7987 },
7988 dialed_number: String::new(),
7989 statistics_directory_number: String::new(),
7990 transfer_role: None,
7991 };
7992 state.calls_by_wire.insert(wire_reference, call_id);
7993 state.calls_by_id.insert(call_id, call.clone());
7994 call
7995}
7996
7997fn find_call(state: &SessionState, wire_reference: u32) -> Option<&SessionCall> {
7998 if wire_reference != 0 {
7999 state
8000 .calls_by_wire
8001 .get(&wire_reference)
8002 .and_then(|id| state.calls_by_id.get(id))
8003 } else {
8004 state
8005 .active_call_id
8006 .and_then(|call_id| state.calls_by_id.get(&call_id))
8007 .or_else(|| {
8008 (state.calls_by_id.len() == 1)
8009 .then(|| state.calls_by_id.values().next())
8010 .flatten()
8011 })
8012 }
8013}
8014
8015fn find_answer_call(
8016 state: &SessionState,
8017 wire_reference: u32,
8018 line_instance: u32,
8019 order: CallSelectionOrder,
8020) -> Option<&SessionCall> {
8021 let matches_line = |call: &&SessionCall| {
8022 matches!(call.state, CallState::RingIn | CallState::CallWaiting)
8023 && (line_instance == 0 || call.line_instance == line_instance)
8024 };
8025 if wire_reference != 0 {
8026 return state
8027 .calls_by_wire
8028 .get(&wire_reference)
8029 .and_then(|call_id| state.calls_by_id.get(call_id))
8030 .filter(matches_line);
8031 }
8032 if let Some(active) = state
8033 .active_call_id
8034 .and_then(|call_id| state.calls_by_id.get(&call_id))
8035 .filter(matches_line)
8036 {
8037 return Some(active);
8038 }
8039 let candidates = state.calls_by_id.values().filter(matches_line);
8040 match order {
8041 CallSelectionOrder::OldestFirst => candidates.min_by_key(|call| call.call_id.0),
8042 CallSelectionOrder::LastFirst => candidates.max_by_key(|call| call.call_id.0),
8043 }
8044}
8045
8046fn find_receive_media_call_id(
8047 state: &SessionState,
8048 wire_reference: u32,
8049 passthrough_party_id: u32,
8050) -> Option<CallId> {
8051 find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
8052 call.media.receive.request
8053 })
8054}
8055
8056fn find_multicast_receive_key(
8057 state: &SessionState,
8058 wire_reference: u32,
8059 passthrough_party_id: u32,
8060) -> Option<MulticastKey> {
8061 state.multicast.iter().find_map(|(key, session)| {
8062 session.receive.as_ref().and_then(|receive| {
8063 (matches!(
8064 receive.state,
8065 MulticastReceiveState::AwaitingAcknowledgement { .. }
8066 ) && session.wire_call_reference == wire_reference
8067 && receive.request.token().get() == passthrough_party_id)
8068 .then_some(*key)
8069 })
8070 })
8071}
8072
8073fn find_multicast_transmit_key(
8074 state: &SessionState,
8075 conference_id: u32,
8076 wire_reference: u32,
8077 passthrough_party_id: u32,
8078 address: IpAddr,
8079 port: u16,
8080) -> Option<MulticastKey> {
8081 state.multicast.iter().find_map(|(key, session)| {
8082 session.transmit.as_ref().and_then(|transmit| {
8083 (key.conference_id.get() == conference_id
8084 && session.wire_call_reference == wire_reference
8085 && transmit.request.token().get() == passthrough_party_id
8086 && canonical_ip_address(transmit.route.address) == canonical_ip_address(address)
8087 && transmit.route.port == port)
8088 .then_some(*key)
8089 })
8090 })
8091}
8092
8093fn find_transmit_media_call_id(
8094 state: &SessionState,
8095 conference_id: u32,
8096 wire_reference: u32,
8097 passthrough_party_id: u32,
8098) -> Option<CallId> {
8099 find_media_call_id(state, wire_reference, passthrough_party_id, |call| {
8100 call.media.transmit.request
8101 })
8102 .filter(|call_id| {
8103 state
8104 .calls_by_id
8105 .get(call_id)
8106 .is_some_and(|call| conference_id == 0 || conference_id == call.wire_reference)
8107 })
8108}
8109
8110fn find_media_call_id(
8111 state: &SessionState,
8112 wire_reference: u32,
8113 passthrough_party_id: u32,
8114 request: impl Fn(&SessionCall) -> Option<MediaRequestIdentity>,
8115) -> Option<CallId> {
8116 state
8117 .calls_by_id
8118 .values()
8119 .find(|call| {
8120 request(call).is_some_and(|identity| {
8121 identity.accepts_ack(passthrough_party_id, wire_reference, call.wire_reference)
8122 })
8123 })
8124 .map(|call| call.call_id)
8125}
8126
8127fn require_call(state: &SessionState, call_id: CallId) -> Result<&SessionCall, ServerError> {
8128 state
8129 .calls_by_id
8130 .get(&call_id)
8131 .ok_or(ServerError::UnknownCall(call_id))
8132}
8133
8134fn require_call_mut(
8135 state: &mut SessionState,
8136 call_id: CallId,
8137) -> Result<&mut SessionCall, ServerError> {
8138 state
8139 .calls_by_id
8140 .get_mut(&call_id)
8141 .ok_or(ServerError::UnknownCall(call_id))
8142}
8143
8144fn address_matches_type(address: IpAddr, requested: IpAddressType) -> bool {
8145 match requested {
8146 IpAddressType::Ipv4 => address.is_ipv4(),
8147 IpAddressType::Ipv6 => address.is_ipv6(),
8148 IpAddressType::Ipv4AndIpv6 => true,
8149 IpAddressType::Invalid | IpAddressType::Unknown(_) => false,
8150 }
8151}
8152
8153fn address_type(address: IpAddr) -> IpAddressType {
8154 if address.is_ipv4() {
8155 IpAddressType::Ipv4
8156 } else {
8157 IpAddressType::Ipv6
8158 }
8159}
8160
8161fn endpoint_is_usable(endpoint: MediaEndpointAddress) -> bool {
8162 endpoint.port != 0 && !endpoint.address.is_unspecified() && !endpoint.address.is_multicast()
8163}
8164
8165fn capability_supports_address(
8166 advertised: Option<IpAddressType>,
8167 requested: IpAddressType,
8168) -> bool {
8169 match advertised {
8170 None => requested == IpAddressType::Ipv4,
8171 Some(IpAddressType::Ipv4AndIpv6) => true,
8172 Some(address_type) => address_type == requested,
8173 }
8174}
8175
8176fn validate_multimedia_receive_descriptor(
8177 descriptor: &MultimediaReceiveDescriptor,
8178) -> Result<(), ServerError> {
8179 if !descriptor
8180 .payload
8181 .is_direction(MultimediaPayloadDirection::Receive)
8182 {
8183 return Err(ServerError::InvalidMultimediaReceive(
8184 "payload was not decoded from a receive message",
8185 ));
8186 }
8187 if descriptor.payload.codec().kind() != CodecKind::Video {
8188 return Err(ServerError::InvalidMultimediaReceive("codec is not video"));
8189 }
8190 if !address_matches_type(descriptor.source.address, descriptor.requested_address_type) {
8191 return Err(ServerError::InvalidMultimediaReceive(
8192 "source address does not match the requested address type",
8193 ));
8194 }
8195 if descriptor.source.address.is_multicast() {
8196 return Err(ServerError::InvalidMultimediaReceive(
8197 "source address must not be multicast",
8198 ));
8199 }
8200 Ok(())
8201}
8202
8203fn validate_multimedia_receive(
8204 state: &SessionState,
8205 descriptor: &MultimediaReceiveDescriptor,
8206) -> Result<(), ServerError> {
8207 validate_multimedia_receive_descriptor(descriptor)?;
8208
8209 if !descriptor.payload.is_valid_for(
8210 MultimediaPayloadDirection::Receive,
8211 state.registration.protocol,
8212 ) {
8213 return Err(ServerError::InvalidMultimediaReceive(
8214 "payload protocol does not match the live session",
8215 ));
8216 }
8217
8218 match state.registration.protocol {
8219 protocol if protocol < ProtocolVersion::V12 => {
8220 if descriptor.source
8221 != (MediaEndpointAddress {
8222 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
8223 port: 0,
8224 })
8225 || descriptor.requested_address_type != IpAddressType::Ipv4
8226 {
8227 return Err(ServerError::InvalidMultimediaReceive(
8228 "this protocol version cannot carry a source endpoint",
8229 ));
8230 }
8231 }
8232 protocol
8233 if protocol < ProtocolVersion::V17
8234 && (!descriptor.source.address.is_ipv4()
8235 || descriptor.requested_address_type != IpAddressType::Ipv4) =>
8236 {
8237 return Err(ServerError::InvalidMultimediaReceive(
8238 "this protocol version carries only IPv4 video endpoints",
8239 ));
8240 }
8241 _ => {}
8242 }
8243
8244 let supported = state.media_capabilities.video().iter().any(|capability| {
8245 let encryption_supported = descriptor.encryption.is_none()
8246 || capability.encryption_capability == Some(EncryptionCapability::Capable);
8247 capability.codec == descriptor.payload.codec()
8248 && capability.direction.contains(ReceiveTransmit::RECEIVE)
8249 && capability_supports_address(
8250 capability.address_type,
8251 descriptor.requested_address_type,
8252 )
8253 && encryption_supported
8254 });
8255 supported
8256 .then_some(())
8257 .ok_or(ServerError::UnsupportedMultimediaReceive)
8258}
8259
8260fn validate_multimedia_transmit_descriptor(
8261 descriptor: &MultimediaTransmitDescriptor,
8262) -> Result<(), ServerError> {
8263 if !descriptor
8264 .payload
8265 .is_direction(MultimediaPayloadDirection::Transmit)
8266 {
8267 return Err(ServerError::InvalidMultimediaTransmit(
8268 "payload was not decoded from a transmit message",
8269 ));
8270 }
8271 if descriptor.payload.codec().kind() != CodecKind::Video {
8272 return Err(ServerError::InvalidMultimediaTransmit("codec is not video"));
8273 }
8274 if !endpoint_is_usable(descriptor.endpoint) {
8275 return Err(ServerError::InvalidMultimediaTransmit(
8276 "destination endpoint must be unicast and nonzero",
8277 ));
8278 }
8279 Ok(())
8280}
8281
8282fn validate_multimedia_transmit(
8283 state: &SessionState,
8284 descriptor: &MultimediaTransmitDescriptor,
8285) -> Result<(), ServerError> {
8286 validate_multimedia_transmit_descriptor(descriptor)?;
8287 if !descriptor.payload.is_valid_for(
8288 MultimediaPayloadDirection::Transmit,
8289 state.registration.protocol,
8290 ) {
8291 return Err(ServerError::InvalidMultimediaTransmit(
8292 "payload protocol does not match the live session",
8293 ));
8294 }
8295 if state.registration.protocol < ProtocolVersion::V17 && descriptor.endpoint.address.is_ipv6() {
8296 return Err(ServerError::InvalidMultimediaTransmit(
8297 "this protocol version carries only IPv4 video endpoints",
8298 ));
8299 }
8300 let requested_address = address_type(descriptor.endpoint.address);
8301 let supported = state.media_capabilities.video().iter().any(|capability| {
8302 let encryption_supported = descriptor.encryption.is_none()
8303 || capability.encryption_capability == Some(EncryptionCapability::Capable);
8304 capability.codec == descriptor.payload.codec()
8305 && capability.direction.contains(ReceiveTransmit::TRANSMIT)
8306 && capability_supports_address(capability.address_type, requested_address)
8307 && encryption_supported
8308 });
8309 supported
8310 .then_some(())
8311 .ok_or(ServerError::UnsupportedMultimediaTransmit)
8312}
8313
8314fn allocate_video_receive_identity(
8315 state: &mut SessionState,
8316 call_id: CallId,
8317) -> Result<MediaRequestIdentity, ServerError> {
8318 let generation = require_call(state, call_id)?
8319 .video_receive
8320 .generation
8321 .checked_add(1)
8322 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8323 let token = state
8324 .next_media_token
8325 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8326 let request = MediaRequestIdentity::new(generation, token)
8327 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8328 state.next_media_token = token.checked_next();
8329 require_call_mut(state, call_id)?.video_receive.generation = generation;
8330 Ok(request)
8331}
8332
8333fn multimedia_receive_close_message(call: &SessionCall, leg: &VideoReceiveLeg) -> ServerMessage {
8334 ServerMessage::CloseMultimediaReceiveChannel(MultimediaStreamControl {
8335 conference_id: leg.conference_id,
8336 passthrough_party_id: leg.request.token().get().into(),
8337 call_reference: CallReference::new(call.wire_reference),
8338 port_handling_flag: 0,
8339 })
8340}
8341
8342fn take_multimedia_receive_close(
8343 state: &mut SessionState,
8344 call_id: CallId,
8345) -> Option<ServerMessage> {
8346 let call = state.calls_by_id.get_mut(&call_id)?;
8347 let leg = call.video_receive.leg.take()?;
8348 Some(multimedia_receive_close_message(call, &leg))
8349}
8350
8351fn take_all_multimedia_receive_closes(state: &mut SessionState) -> Vec<ServerMessage> {
8352 let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
8353 call_ids.sort_unstable_by_key(|call_id| call_id.get());
8354 call_ids
8355 .into_iter()
8356 .filter_map(|call_id| take_multimedia_receive_close(state, call_id))
8357 .collect()
8358}
8359
8360fn expire_multimedia_receive_acknowledgements(
8361 state: &mut SessionState,
8362 now: Instant,
8363) -> Vec<ExpiredVideoReceive> {
8364 let mut call_ids = state
8365 .calls_by_id
8366 .iter()
8367 .filter_map(|(&call_id, call)| {
8368 call.video_receive.leg.as_ref().and_then(|leg| {
8369 (leg.state == MediaChannelState::Opening
8370 && leg.deadline.is_some_and(|deadline| deadline <= now))
8371 .then_some(call_id)
8372 })
8373 })
8374 .collect::<Vec<_>>();
8375 call_ids.sort_unstable_by_key(|call_id| call_id.get());
8376 call_ids
8377 .into_iter()
8378 .filter_map(|call_id| {
8379 let leg = state
8380 .calls_by_id
8381 .get(&call_id)?
8382 .video_receive
8383 .leg
8384 .as_ref()?;
8385 let codec = leg.codec;
8386 let passthrough_party_id = leg.request.token().get().into();
8387 take_multimedia_receive_close(state, call_id).map(|close| ExpiredVideoReceive {
8388 call_id,
8389 codec,
8390 passthrough_party_id,
8391 close,
8392 })
8393 })
8394 .collect()
8395}
8396
8397fn allocate_video_transmit_identity(
8398 state: &mut SessionState,
8399 call_id: CallId,
8400) -> Result<MediaRequestIdentity, ServerError> {
8401 let generation = require_call(state, call_id)?
8402 .video_transmit
8403 .generation
8404 .checked_add(1)
8405 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8406 let token = state
8407 .next_media_token
8408 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8409 let request = MediaRequestIdentity::new(generation, token)
8410 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8411 state.next_media_token = token.checked_next();
8412 require_call_mut(state, call_id)?.video_transmit.generation = generation;
8413 Ok(request)
8414}
8415
8416fn multimedia_transmit_control_identity(
8417 state: &SessionState,
8418 call_id: CallId,
8419 passthrough_party_id: PassthroughPartyId,
8420) -> Result<(ConferenceId, CallReference), ServerError> {
8421 let call = require_call(state, call_id)?;
8422 if call.state != CallState::Connected {
8423 return Err(ServerError::InvalidCallTransaction {
8424 call_id,
8425 operation: "control video transmit media",
8426 state: call.state,
8427 });
8428 }
8429 let leg = call
8430 .video_transmit
8431 .leg
8432 .as_ref()
8433 .filter(|leg| {
8434 leg.state == MediaChannelState::Open
8435 && leg.request.token().get() == passthrough_party_id.get()
8436 })
8437 .ok_or(ServerError::StaleMultimediaTransmitControl {
8438 call_id,
8439 passthrough_party_id,
8440 })?;
8441 Ok((leg.conference_id, CallReference::new(call.wire_reference)))
8442}
8443
8444fn encode_multimedia_transmit_control(
8445 control: MultimediaTransmitControl,
8446) -> Result<(MiscCommandType, BoundedBytes<36>), ServerError> {
8447 let (command, words) = match control {
8448 MultimediaTransmitControl::FreezePicture => {
8449 (MiscCommandType::VideoFreezePicture, Vec::new())
8450 }
8451 MultimediaTransmitControl::FastPictureUpdate {
8452 first_gob,
8453 gob_count,
8454 } => (
8455 MiscCommandType::VideoFastUpdatePicture,
8456 vec![first_gob, gob_count],
8457 ),
8458 MultimediaTransmitControl::FastGobUpdate {
8459 first_gob,
8460 gob_count,
8461 } => (
8462 MiscCommandType::VideoFastUpdateGob,
8463 vec![first_gob, gob_count],
8464 ),
8465 MultimediaTransmitControl::FastMacroblockUpdate {
8466 first_gob,
8467 first_macroblock,
8468 macroblock_count,
8469 } => (
8470 MiscCommandType::VideoFastUpdateMacroblock,
8471 vec![first_gob, first_macroblock, macroblock_count],
8472 ),
8473 MultimediaTransmitControl::LostPicture {
8474 picture_number,
8475 long_term_picture_index,
8476 } => (
8477 MiscCommandType::LostPicture,
8478 vec![picture_number, long_term_picture_index],
8479 ),
8480 MultimediaTransmitControl::LostPartialPicture {
8481 picture_number,
8482 long_term_picture_index,
8483 first_macroblock,
8484 macroblock_count,
8485 } => (
8486 MiscCommandType::LostPartialPicture,
8487 vec![
8488 picture_number,
8489 long_term_picture_index,
8490 first_macroblock,
8491 macroblock_count,
8492 ],
8493 ),
8494 MultimediaTransmitControl::RecoveryReferencePicture { pictures } => {
8495 let words =
8496 std::iter::once(pictures.as_slice().len() as u32)
8497 .chain(pictures.as_slice().iter().flat_map(|picture| {
8498 [picture.picture_number, picture.long_term_picture_index]
8499 }))
8500 .collect();
8501 (MiscCommandType::RecoveryReferencePicture, words)
8502 }
8503 MultimediaTransmitControl::TemporalSpatialTradeoff { value } => {
8504 (MiscCommandType::TemporalSpatialTradeoff, vec![value])
8505 }
8506 };
8507 let data = words
8508 .into_iter()
8509 .flat_map(u32::to_le_bytes)
8510 .collect::<Vec<_>>();
8511 let data = BoundedBytes::new(data.into_boxed_slice()).map_err(|_| {
8512 ServerError::InvalidMultimediaTransmitControl("parameter area exceeds 36 bytes")
8513 })?;
8514 Ok((command, data))
8515}
8516
8517fn multimedia_transmit_stop_message(call: &SessionCall, leg: &VideoTransmitLeg) -> ServerMessage {
8518 ServerMessage::StopMultimediaTransmission(MultimediaStreamControl {
8519 conference_id: leg.conference_id,
8520 passthrough_party_id: leg.request.token().get().into(),
8521 call_reference: CallReference::new(call.wire_reference),
8522 port_handling_flag: 0,
8523 })
8524}
8525
8526fn take_multimedia_transmit_stop(
8527 state: &mut SessionState,
8528 call_id: CallId,
8529) -> Option<ServerMessage> {
8530 let call = state.calls_by_id.get_mut(&call_id)?;
8531 let leg = call.video_transmit.leg.take()?;
8532 Some(multimedia_transmit_stop_message(call, &leg))
8533}
8534
8535fn take_all_multimedia_transmit_stops(state: &mut SessionState) -> Vec<ServerMessage> {
8536 let mut call_ids = state.calls_by_id.keys().copied().collect::<Vec<_>>();
8537 call_ids.sort_unstable_by_key(|call_id| call_id.get());
8538 call_ids
8539 .into_iter()
8540 .filter_map(|call_id| take_multimedia_transmit_stop(state, call_id))
8541 .collect()
8542}
8543
8544fn expire_multimedia_transmit_acknowledgements(
8545 state: &mut SessionState,
8546 now: Instant,
8547) -> Vec<ExpiredVideoTransmit> {
8548 let mut call_ids = state
8549 .calls_by_id
8550 .iter()
8551 .filter_map(|(&call_id, call)| {
8552 call.video_transmit.leg.as_ref().and_then(|leg| {
8553 (leg.state == MediaChannelState::Opening
8554 && leg.deadline.is_some_and(|deadline| deadline <= now))
8555 .then_some(call_id)
8556 })
8557 })
8558 .collect::<Vec<_>>();
8559 call_ids.sort_unstable_by_key(|call_id| call_id.get());
8560 call_ids
8561 .into_iter()
8562 .filter_map(|call_id| {
8563 let leg = state
8564 .calls_by_id
8565 .get(&call_id)?
8566 .video_transmit
8567 .leg
8568 .as_ref()?;
8569 let codec = leg.codec;
8570 let passthrough_party_id = leg.request.token().get().into();
8571 take_multimedia_transmit_stop(state, call_id).map(|stop| ExpiredVideoTransmit {
8572 call_id,
8573 codec,
8574 passthrough_party_id,
8575 stop,
8576 })
8577 })
8578 .collect()
8579}
8580
8581fn validate_multicast_route(
8582 state: &SessionState,
8583 route: MulticastMediaRoute,
8584 max_frames_per_packet: Option<u32>,
8585) -> Result<(), ServerError> {
8586 if !route.address.is_multicast() {
8587 return Err(ServerError::InvalidMulticastMedia(
8588 "address must be multicast",
8589 ));
8590 }
8591 if route.address.is_ipv6() && state.registration.protocol < ProtocolVersion::V17 {
8592 return Err(ServerError::InvalidMulticastMedia(
8593 "IPv6 requires protocol v17 or later",
8594 ));
8595 }
8596 if route.port == 0 {
8597 return Err(ServerError::InvalidMulticastMedia("port must be nonzero"));
8598 }
8599 if route.packet_millis == 0 {
8600 return Err(ServerError::InvalidMulticastMedia(
8601 "packet duration must be nonzero",
8602 ));
8603 }
8604 if route.codec.kind() != CodecKind::Audio {
8605 return Err(ServerError::UnsupportedMulticastCodec);
8606 }
8607 let capability = state
8608 .media_capabilities
8609 .audio()
8610 .iter()
8611 .find(|capability| capability.codec == route.codec)
8612 .filter(|capability| capability.max_frames_per_packet != 0)
8613 .ok_or(ServerError::UnsupportedMulticastCodec)?;
8614 if let Some(requested) = max_frames_per_packet
8615 && (requested == 0 || requested > capability.max_frames_per_packet)
8616 {
8617 return Err(ServerError::InvalidMulticastMedia(
8618 "packet framing exceeds the advertised capability",
8619 ));
8620 }
8621 Ok(())
8622}
8623
8624fn allocate_multicast_request_identity(
8625 state: &mut SessionState,
8626) -> Result<MediaRequestIdentity, ServerError> {
8627 let generation = state
8628 .next_multicast_generation
8629 .checked_add(1)
8630 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8631 let token = state
8632 .next_media_token
8633 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8634 let identity = MediaRequestIdentity::new(generation, token)
8635 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8636 state.next_multicast_generation = generation;
8637 state.next_media_token = token.checked_next();
8638 Ok(identity)
8639}
8640
8641fn multicast_stop_message(
8642 key: MulticastKey,
8643 wire_call_reference: u32,
8644 request: MediaRequestIdentity,
8645 receive: bool,
8646) -> ServerMessage {
8647 if receive {
8648 ServerMessage::StopMulticastMediaReception {
8649 conference_id: key.conference_id,
8650 passthrough_party_id: request.token().get().into(),
8651 call_reference: CallReference::new(wire_call_reference),
8652 }
8653 } else {
8654 ServerMessage::StopMulticastMediaTransmission {
8655 conference_id: key.conference_id,
8656 passthrough_party_id: request.token().get().into(),
8657 call_reference: CallReference::new(wire_call_reference),
8658 }
8659 }
8660}
8661
8662fn take_multicast_stop(
8663 state: &mut SessionState,
8664 key: MulticastKey,
8665 receive: bool,
8666) -> Option<ServerMessage> {
8667 let session = state.multicast.get_mut(&key)?;
8668 let request = if receive {
8669 session.receive.take().map(|leg| leg.request)
8670 } else {
8671 session.transmit.take().map(|leg| leg.request)
8672 }?;
8673 let message = multicast_stop_message(key, session.wire_call_reference, request, receive);
8674 if session.receive.is_none() && session.transmit.is_none() {
8675 state.multicast.remove(&key);
8676 }
8677 Some(message)
8678}
8679
8680fn expire_multicast_reception_acknowledgements(
8681 state: &mut SessionState,
8682 now: Instant,
8683) -> Vec<(MulticastKey, ServerMessage)> {
8684 let mut expired = state
8685 .multicast
8686 .iter()
8687 .filter_map(|(key, session)| {
8688 session.receive.as_ref().and_then(|receive| {
8689 matches!(
8690 receive.state,
8691 MulticastReceiveState::AwaitingAcknowledgement { deadline }
8692 if deadline <= now
8693 )
8694 .then_some(*key)
8695 })
8696 })
8697 .collect::<Vec<_>>();
8698 expired.sort_unstable_by_key(|key| (key.conference_id.get(), key.call_id.get()));
8699 expired
8700 .into_iter()
8701 .filter_map(|key| take_multicast_stop(state, key, true).map(|stop| (key, stop)))
8702 .collect()
8703}
8704
8705fn take_multicast_stops_for_call(state: &mut SessionState, call_id: CallId) -> Vec<ServerMessage> {
8706 let mut keys = state
8707 .multicast
8708 .keys()
8709 .copied()
8710 .filter(|key| key.call_id == call_id)
8711 .collect::<Vec<_>>();
8712 keys.sort_unstable_by_key(|key| key.conference_id.get());
8713 keys.into_iter()
8714 .flat_map(|key| {
8715 [
8716 take_multicast_stop(state, key, true),
8717 take_multicast_stop(state, key, false),
8718 ]
8719 .into_iter()
8720 .flatten()
8721 })
8722 .collect()
8723}
8724
8725fn take_all_multicast_stops(state: &mut SessionState) -> Vec<ServerMessage> {
8726 let mut sessions = std::mem::take(&mut state.multicast)
8727 .into_iter()
8728 .collect::<Vec<_>>();
8729 sessions.sort_unstable_by_key(|(key, _)| (key.conference_id.get(), key.call_id.get()));
8730 sessions
8731 .into_iter()
8732 .flat_map(|(key, session)| {
8733 [
8734 session.receive.map(|leg| {
8735 multicast_stop_message(key, session.wire_call_reference, leg.request, true)
8736 }),
8737 session.transmit.map(|leg| {
8738 multicast_stop_message(key, session.wire_call_reference, leg.request, false)
8739 }),
8740 ]
8741 .into_iter()
8742 .flatten()
8743 })
8744 .collect()
8745}
8746
8747async fn drain_session_media(stream: &mut dyn StationIo, state: &mut SessionState) {
8748 let protocol = state.registration.protocol;
8749 let messages = take_all_multimedia_receive_closes(state)
8750 .into_iter()
8751 .chain(take_all_multimedia_transmit_stops(state))
8752 .chain(take_all_multicast_stops(state));
8753 for message in messages {
8754 if send_message(stream, &message, protocol).await.is_err() {
8755 break;
8756 }
8757 }
8758}
8759
8760async fn stop_call_multicast(
8761 stream: &mut dyn StationIo,
8762 state: &mut SessionState,
8763 call_id: CallId,
8764 protocol: ProtocolVersion,
8765) -> Result<(), ServerError> {
8766 for message in take_multicast_stops_for_call(state, call_id) {
8767 send_message(stream, &message, protocol).await?;
8768 }
8769 Ok(())
8770}
8771
8772fn allocate_media_request_identity(
8773 state: &mut SessionState,
8774 call_id: CallId,
8775) -> Result<MediaRequestIdentity, ServerError> {
8776 let generation = require_call(state, call_id)?
8777 .media
8778 .generation
8779 .checked_add(1)
8780 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8781 let token = state
8782 .next_media_token
8783 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8784 let identity = MediaRequestIdentity::new(generation, token)
8785 .ok_or(ServerError::MediaRequestIdentityExhausted)?;
8786 state.next_media_token = token.checked_next();
8787 require_call_mut(state, call_id)?.media.generation = generation;
8788 Ok(identity)
8789}
8790
8791fn media_request_party_id(
8792 request: Option<MediaRequestIdentity>,
8793 stable_call_reference: u32,
8794) -> u32 {
8795 request.map_or(stable_call_reference, |identity| identity.token().get())
8796}
8797
8798fn remove_call(state: &mut SessionState, call_id: CallId) {
8799 if let Some(call) = state.calls_by_id.remove(&call_id) {
8800 state.calls_by_wire.remove(&call.wire_reference);
8801 if state.active_call_id == Some(call_id) {
8802 state.active_call_id = None;
8803 }
8804 }
8805}
8806
8807fn canonical_ip_address(address: IpAddr) -> IpAddr {
8808 match address {
8809 IpAddr::V6(address) => address
8810 .to_ipv4_mapped()
8811 .map_or(IpAddr::V6(address), IpAddr::V4),
8812 address => address,
8813 }
8814}
8815
8816fn server_response_address(
8817 local: IpAddr,
8818 configured_ipv4_fallback: Ipv4Addr,
8819 configured_ipv6_fallback: Option<Ipv6Addr>,
8820) -> IpAddr {
8821 match canonical_ip_address(local) {
8822 IpAddr::V4(address) if address.is_unspecified() => IpAddr::V4(configured_ipv4_fallback),
8823 IpAddr::V6(address) if address.is_unspecified() => {
8824 configured_ipv6_fallback.map_or(IpAddr::V4(configured_ipv4_fallback), IpAddr::V6)
8825 }
8826 local => local,
8827 }
8828}
8829
8830fn server_response_endpoints(
8831 context: &SessionContext,
8832 protocol: ProtocolVersion,
8833) -> Result<Vec<SignalingServerEndpoint>, ServerError> {
8834 let local_endpoint = || {
8835 let address = server_response_address(
8836 context.local.ip(),
8837 context.config.advertised_address,
8838 context.config.advertised_ipv6_address,
8839 );
8840 let address = if protocol < ProtocolVersion::V17 && address.is_ipv6() {
8841 IpAddr::V4(context.config.advertised_address)
8842 } else {
8843 address
8844 };
8845 if address.is_unspecified() {
8846 return Err(ServerError::InvalidConfig(
8847 "server-list fallback address is unspecified".into(),
8848 ));
8849 }
8850 Ok(SignalingServerEndpoint {
8851 name: context.config.server_name.clone(),
8852 address,
8853 port: NonZeroU16::new(context.local.port()).ok_or_else(|| {
8854 ServerError::InvalidConfig("accepted local endpoint has port zero".into())
8855 })?,
8856 })
8857 };
8858 if context.config.signaling_servers.is_empty() {
8859 return local_endpoint().map(|endpoint| vec![endpoint]);
8860 }
8861
8862 let mut routes = context.config.signaling_servers.iter().collect::<Vec<_>>();
8863 routes.sort_unstable_by_key(|route| route.priority);
8864 let endpoints = routes
8865 .into_iter()
8866 .filter(|route| protocol >= ProtocolVersion::V17 || route.address.is_ipv4())
8867 .filter_map(|route| route.endpoint(context.transport))
8868 .collect::<Vec<_>>();
8869 if endpoints.is_empty() {
8870 local_endpoint().map(|endpoint| vec![endpoint])
8871 } else {
8872 Ok(endpoints)
8873 }
8874}
8875
8876async fn close_call_media_messages(
8877 stream: &mut dyn StationIo,
8878 call: &SessionCall,
8879 protocol: ProtocolVersion,
8880) -> Result<(), ServerError> {
8881 if let Some(leg) = &call.video_receive.leg {
8882 send_message(
8883 stream,
8884 &multimedia_receive_close_message(call, leg),
8885 protocol,
8886 )
8887 .await?;
8888 }
8889 if let Some(leg) = &call.video_transmit.leg {
8890 send_message(
8891 stream,
8892 &multimedia_transmit_stop_message(call, leg),
8893 protocol,
8894 )
8895 .await?;
8896 }
8897 if call.media.receive.state != MediaChannelState::Closed {
8898 send_message(
8899 stream,
8900 &ServerMessage::CloseReceiveChannel(AudioStreamControl {
8901 conference_id: ConferenceId::new(call.wire_reference),
8902 call_reference: CallReference::new(call.wire_reference),
8903 passthrough_party_id: media_request_party_id(
8904 call.media.receive.request,
8905 call.wire_reference,
8906 )
8907 .into(),
8908 port_handling_flag: 0,
8909 }),
8910 protocol,
8911 )
8912 .await?;
8913 }
8914 if call.media.transmit.state != MediaChannelState::Closed {
8915 send_message(
8916 stream,
8917 &ServerMessage::StopMediaTransmission(AudioStreamControl {
8918 conference_id: ConferenceId::new(call.wire_reference),
8919 call_reference: CallReference::new(call.wire_reference),
8920 passthrough_party_id: media_request_party_id(
8921 call.media.transmit.request,
8922 call.wire_reference,
8923 )
8924 .into(),
8925 port_handling_flag: 0,
8926 }),
8927 protocol,
8928 )
8929 .await?;
8930 }
8931 Ok(())
8932}
8933
8934async fn close_call_messages(
8935 stream: &mut dyn StationIo,
8936 call: &SessionCall,
8937 soft_keys: &SoftKeyProfile,
8938 protocol: ProtocolVersion,
8939 timezone_offset_minutes: i16,
8940) -> Result<(), ServerError> {
8941 send_message(
8942 stream,
8943 &ServerMessage::StopTone {
8944 line_instance: call.line_instance,
8945 call_reference: call.wire_reference,
8946 },
8947 protocol,
8948 )
8949 .await?;
8950 send_message(
8951 stream,
8952 &ServerMessage::SetLamp {
8953 stimulus: ButtonType::Line,
8954 instance: call.line_instance,
8955 mode: LampMode::Off,
8956 },
8957 protocol,
8958 )
8959 .await?;
8960 send_message(
8961 stream,
8962 &ServerMessage::ClearPrompt {
8963 line_instance: call.line_instance,
8964 call_reference: call.wire_reference,
8965 },
8966 protocol,
8967 )
8968 .await?;
8969 send_message(
8970 stream,
8971 &ServerMessage::CallState {
8972 state: CallState::OnHook,
8973 line_instance: call.line_instance,
8974 call_reference: call.wire_reference,
8975 },
8976 protocol,
8977 )
8978 .await?;
8979 send_message(
8980 stream,
8981 &ServerMessage::SelectSoftKeys {
8982 line_instance: 0,
8983 call_reference: 0,
8984 set: KeyMode::OnHook,
8985 valid_mask: soft_keys.valid_mask(KeyMode::OnHook),
8986 },
8987 protocol,
8988 )
8989 .await?;
8990 send_message(
8991 stream,
8992 &time_date_message(timezone_offset_minutes),
8993 protocol,
8994 )
8995 .await?;
8996 send_message(
8997 stream,
8998 &ServerMessage::SetSpeakerMode(SpeakerMode::Off),
8999 protocol,
9000 )
9001 .await?;
9002 send_message(
9004 stream,
9005 &ServerMessage::SetRinger {
9006 mode: RingerMode::Off,
9007 duration: RingDuration::Normal,
9008 line_instance: call.line_instance,
9009 call_reference: call.wire_reference,
9010 },
9011 protocol,
9012 )
9013 .await?;
9014 Ok(())
9015}
9016
9017fn time_date_message(timezone_offset_minutes: i16) -> ServerMessage {
9018 time_date_message_at(SystemTime::now(), timezone_offset_minutes)
9019}
9020
9021fn time_date_message_at(now: SystemTime, timezone_offset_minutes: i16) -> ServerMessage {
9022 let unix = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
9023 let local = (unix as i128 + i128::from(timezone_offset_minutes) * 60)
9024 .clamp(0, i128::from(u32::MAX)) as u64;
9025 let days = (local / 86_400) as i64;
9026 let seconds = local % 86_400;
9027 let (year, month, day) = civil_from_days(days);
9028 ServerMessage::TimeDate {
9029 year: year as u32,
9030 month,
9031 weekday: ((days + 4).rem_euclid(7) + 1) as u32,
9032 day,
9033 hour: (seconds / 3600) as u32,
9034 minute: ((seconds % 3600) / 60) as u32,
9035 second: (seconds % 60) as u32,
9036 milliseconds: 0,
9037 unix_seconds: local as u32,
9038 }
9039}
9040
9041fn civil_from_days(days_since_epoch: i64) -> (i64, u32, u32) {
9043 let z = days_since_epoch + 719_468;
9044 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
9045 let doe = z - era * 146_097;
9046 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
9047 let mut year = yoe + era * 400;
9048 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
9049 let mp = (5 * doy + 2) / 153;
9050 let day = doy - (153 * mp + 2) / 5 + 1;
9051 let month = mp + if mp < 10 { 3 } else { -9 };
9052 year += i64::from(month <= 2);
9053 (year, month as u32, day as u32)
9054}
9055
9056#[cfg(test)]
9057mod tests {
9058 use std::net::Ipv6Addr;
9059
9060 use super::*;
9061 use crate::PhoneSoftKeyPosition;
9062 use crate::message::values::{
9063 AnnouncementPlayMode, EndOfAnnouncementAck, IpAddressType, RFC2833_TELEPHONE_EVENT_PAYLOAD,
9064 ReceiveTransmit,
9065 };
9066 use crate::message::wire::Frame;
9067 use crate::message::{
9068 ClientMessage, MediaTransmissionAck, RegistrationMessage, ServerMessage, id,
9069 };
9070 use crate::types::{
9071 BlfSpeedDialDefinition, FeatureDefinition, LineAppearance, LineDefinition,
9072 ServiceDefinition, SpeedDialDefinition,
9073 };
9074
9075 fn definition() -> DeviceDefinition {
9076 definition_for("SEP001122334455")
9077 }
9078
9079 fn definition_for(device_id: &str) -> DeviceDefinition {
9080 DeviceDefinition {
9081 id: DeviceId::new(device_id).unwrap(),
9082 description: "Test phone".into(),
9083 transport: StationTransportRequirement::Either,
9084 signaling_qos: None,
9085 buttons: vec![ButtonDefinition::Line(LineAppearance::new(
9086 1,
9087 LineDefinition {
9088 number: "1001".into(),
9089 display_name: "Desk 1001".into(),
9090 },
9091 ))],
9092 soft_keys: SoftKeyProfile::default(),
9093 ui: Default::default(),
9094 }
9095 }
9096
9097 #[test]
9098 fn session_generations_are_nonzero_monotonic_and_fail_closed_at_exhaustion() {
9099 let next = AtomicU64::new(1);
9100 let first = allocate_session_generation(&next).unwrap();
9101 let second = allocate_session_generation(&next).unwrap();
9102 assert_eq!(u64::from(first), 1);
9103 assert_eq!(u64::from(second), 2);
9104
9105 let exhausted = AtomicU64::new(u64::MAX);
9106 assert!(matches!(
9107 allocate_session_generation(&exhausted),
9108 Err(ServerError::SessionGenerationExhausted)
9109 ));
9110 assert_eq!(exhausted.load(Ordering::Relaxed), u64::MAX);
9111
9112 let boundary = AtomicU64::new(u64::MAX - 1);
9113 assert_eq!(
9114 u64::from(allocate_session_generation(&boundary).unwrap()),
9115 u64::MAX - 1
9116 );
9117 assert!(matches!(
9118 allocate_session_generation(&boundary),
9119 Err(ServerError::SessionGenerationExhausted)
9120 ));
9121 assert_eq!(boundary.load(Ordering::Relaxed), u64::MAX);
9122
9123 let invalid = AtomicU64::new(0);
9124 assert!(matches!(
9125 allocate_session_generation(&invalid),
9126 Err(ServerError::SessionGenerationExhausted)
9127 ));
9128 assert_eq!(invalid.load(Ordering::Relaxed), 0);
9129 }
9130
9131 fn multicast_test_state(protocol: ProtocolVersion) -> SessionState {
9132 let device = definition();
9133 SessionState {
9134 registration: DeviceRegistration {
9135 id: device.id.clone(),
9136 peer: "127.0.0.1:2000".parse().unwrap(),
9137 transport: StationTransport::Clear,
9138 reported_address: Some(Ipv4Addr::LOCALHOST),
9139 reported_ipv6_address: None,
9140 device_type: DeviceType::Undefined,
9141 protocol,
9142 firmware: "test".into(),
9143 },
9144 device,
9145 features: PhoneFeatures::empty(),
9146 generation: SessionGeneration::new(1).unwrap(),
9147 calls_by_id: HashMap::new(),
9148 calls_by_wire: HashMap::new(),
9149 media_capabilities: vec![MediaCapability {
9150 codec: Codec::Pcmu,
9151 max_frames_per_packet: 2,
9152 codec_parameters: [0; 8],
9153 }]
9154 .into(),
9155 next_media_token: MediaRequestToken::new(1),
9156 next_multicast_generation: 0,
9157 multicast: HashMap::new(),
9158 pending_connection_statistics: HashMap::new(),
9159 statistics_references: HashSet::new(),
9160 cancelled_calls: HashSet::new(),
9161 last_number_by_line: HashMap::new(),
9162 forwarding_by_line: HashMap::new(),
9163 feature_states: HashMap::new(),
9164 mwi_by_line: HashMap::new(),
9165 mobility_appearances: HashMap::new(),
9166 active_key_mode: KeyMode::OnHook,
9167 active_call_id: None,
9168 pending_parking_menu: None,
9169 persistent_status_message: false,
9170 headset_enabled: false,
9171 media_path_states: HashMap::new(),
9172 pending_media_path_release: None,
9173 }
9174 }
9175
9176 fn multicast_route(address: IpAddr, codec: Codec) -> MulticastMediaRoute {
9177 MulticastMediaRoute {
9178 address,
9179 port: 5004,
9180 codec,
9181 packet_millis: 20,
9182 }
9183 }
9184
9185 const fn test_rtp_payload_number(value: u8) -> crate::RtpPayloadNumber {
9186 match crate::RtpPayloadNumber::new(value as u32) {
9187 Ok(payload_number) => payload_number,
9188 Err(_) => panic!("test RTP payload number is out of range"),
9189 }
9190 }
9191
9192 fn video_receive_descriptor(
9193 conference_id: u32,
9194 source: MediaEndpointAddress,
9195 ) -> MultimediaReceiveDescriptor {
9196 MultimediaReceiveDescriptor {
9197 conference_id: ConferenceId::new(conference_id),
9198 payload: MultimediaPayload::from_wire(
9199 0,
9200 test_rtp_payload_number(97),
9201 [0xa5; crate::MULTIMEDIA_CAPABILITY_BYTES],
9202 Codec::H264,
9203 MultimediaPayloadDirection::Receive,
9204 ProtocolVersion::V22,
9205 ),
9206 conference_creator: false,
9207 encryption: None,
9208 stream_passthrough_id: conference_id + 100,
9209 associated_stream_id: 0,
9210 source,
9211 requested_address_type: IpAddressType::Ipv4AndIpv6,
9212 }
9213 }
9214
9215 fn video_transmit_descriptor(
9216 conference_id: u32,
9217 endpoint: MediaEndpointAddress,
9218 ) -> MultimediaTransmitDescriptor {
9219 MultimediaTransmitDescriptor {
9220 conference_id: ConferenceId::new(conference_id),
9221 endpoint,
9222 payload: MultimediaPayload::from_wire(
9223 0,
9224 test_rtp_payload_number(98),
9225 [0x5a; crate::MULTIMEDIA_CAPABILITY_BYTES],
9226 Codec::H264,
9227 MultimediaPayloadDirection::Transmit,
9228 ProtocolVersion::V22,
9229 ),
9230 traffic_class: MediaTrafficClass::from_wire(136),
9231 encryption: None,
9232 stream_passthrough_id: conference_id + 200,
9233 associated_stream_id: 0,
9234 }
9235 }
9236
9237 #[test]
9238 fn video_receive_validation_uses_typed_station_policy_without_touching_audio() {
9239 let mut state = multicast_test_state(ProtocolVersion::V22);
9240 state.media_capabilities = StationMediaCapabilities::new(
9241 state.media_capabilities.audio().to_vec(),
9242 vec![crate::message::capabilities::VideoCapability {
9243 codec: Codec::H264,
9244 direction: ReceiveTransmit::RECEIVE,
9245 level_preferences: Vec::new(),
9246 codec_parameters: Vec::new(),
9247 encryption_capability: Some(EncryptionCapability::Capable),
9248 address_type: Some(IpAddressType::Ipv4AndIpv6),
9249 }],
9250 );
9251 let audio_before = state.media_capabilities.audio().to_vec();
9252 let descriptor = video_receive_descriptor(
9253 70,
9254 MediaEndpointAddress {
9255 address: "192.0.2.10".parse().unwrap(),
9256 port: 5004,
9257 },
9258 );
9259 assert!(validate_multimedia_receive(&state, &descriptor).is_ok());
9260 assert_eq!(state.media_capabilities.audio(), audio_before);
9261
9262 let receive_capability = state.media_capabilities.video()[0].clone();
9263 state.media_capabilities = StationMediaCapabilities::new(
9264 audio_before.clone(),
9265 vec![crate::message::capabilities::VideoCapability {
9266 direction: ReceiveTransmit::TRANSMIT,
9267 ..receive_capability.clone()
9268 }],
9269 );
9270 assert!(matches!(
9271 validate_multimedia_receive(&state, &descriptor),
9272 Err(ServerError::UnsupportedMultimediaReceive)
9273 ));
9274 state.media_capabilities =
9275 StationMediaCapabilities::new(audio_before.clone(), vec![receive_capability]);
9276
9277 let mut wrong_direction = descriptor.clone();
9278 wrong_direction.payload = video_transmit_descriptor(
9279 70,
9280 MediaEndpointAddress {
9281 address: "192.0.2.10".parse().unwrap(),
9282 port: 5004,
9283 },
9284 )
9285 .payload;
9286 assert!(matches!(
9287 wrong_direction.validate(),
9288 Err(ServerError::InvalidMultimediaReceive(_))
9289 ));
9290
9291 let mut mismatched_address = descriptor.clone();
9292 mismatched_address.requested_address_type = IpAddressType::Ipv6;
9293 assert!(matches!(
9294 mismatched_address.validate(),
9295 Err(ServerError::InvalidMultimediaReceive(_))
9296 ));
9297
9298 for protocol in [
9299 ProtocolVersion::V3,
9300 ProtocolVersion::V10,
9301 ProtocolVersion::V11,
9302 ] {
9303 state.registration.protocol = protocol;
9304 let mut legacy_descriptor = descriptor.clone();
9305 legacy_descriptor.source = MediaEndpointAddress {
9306 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9307 port: 0,
9308 };
9309 legacy_descriptor.requested_address_type = IpAddressType::Ipv4;
9310 legacy_descriptor.payload = MultimediaPayload::from_wire(
9311 0,
9312 test_rtp_payload_number(97),
9313 [0xa5; crate::MULTIMEDIA_CAPABILITY_BYTES],
9314 Codec::H264,
9315 MultimediaPayloadDirection::Receive,
9316 protocol,
9317 );
9318 assert!(validate_multimedia_receive(&state, &legacy_descriptor).is_ok());
9319 assert_eq!(state.media_capabilities.audio(), audio_before);
9320 }
9321 }
9322
9323 #[test]
9324 fn video_transmit_validation_requires_exact_direction_endpoint_and_protocol() {
9325 let mut state = multicast_test_state(ProtocolVersion::V22);
9326 let audio_before = state.media_capabilities.audio().to_vec();
9327 let capability = crate::message::capabilities::VideoCapability {
9328 codec: Codec::H264,
9329 direction: ReceiveTransmit::TRANSMIT,
9330 level_preferences: Vec::new(),
9331 codec_parameters: Vec::new(),
9332 encryption_capability: Some(EncryptionCapability::Capable),
9333 address_type: Some(IpAddressType::Ipv4AndIpv6),
9334 };
9335 state.media_capabilities =
9336 StationMediaCapabilities::new(audio_before.clone(), vec![capability.clone()]);
9337 let descriptor = video_transmit_descriptor(
9338 80,
9339 MediaEndpointAddress {
9340 address: "192.0.2.80".parse().unwrap(),
9341 port: 5080,
9342 },
9343 );
9344 assert!(validate_multimedia_transmit(&state, &descriptor).is_ok());
9345 assert_eq!(state.media_capabilities.audio(), audio_before);
9346
9347 state.media_capabilities = StationMediaCapabilities::new(
9348 audio_before.clone(),
9349 vec![crate::message::capabilities::VideoCapability {
9350 direction: ReceiveTransmit::RECEIVE,
9351 ..capability.clone()
9352 }],
9353 );
9354 assert!(matches!(
9355 validate_multimedia_transmit(&state, &descriptor),
9356 Err(ServerError::UnsupportedMultimediaTransmit)
9357 ));
9358 state.media_capabilities =
9359 StationMediaCapabilities::new(audio_before.clone(), vec![capability]);
9360
9361 for invalid in [
9362 MultimediaTransmitDescriptor {
9363 payload: video_receive_descriptor(
9364 80,
9365 MediaEndpointAddress {
9366 address: "192.0.2.80".parse().unwrap(),
9367 port: 5080,
9368 },
9369 )
9370 .payload,
9371 ..descriptor.clone()
9372 },
9373 MultimediaTransmitDescriptor {
9374 endpoint: MediaEndpointAddress {
9375 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9376 port: 5080,
9377 },
9378 ..descriptor.clone()
9379 },
9380 ] {
9381 assert!(matches!(
9382 invalid.validate(),
9383 Err(ServerError::InvalidMultimediaTransmit(_))
9384 ));
9385 }
9386
9387 for protocol in [ProtocolVersion::V3, ProtocolVersion::V10] {
9388 state.registration.protocol = protocol;
9389 let mut legacy = descriptor.clone();
9390 legacy.payload = MultimediaPayload::from_wire(
9391 0,
9392 test_rtp_payload_number(98),
9393 [0x5a; crate::MULTIMEDIA_CAPABILITY_BYTES],
9394 Codec::H264,
9395 MultimediaPayloadDirection::Transmit,
9396 protocol,
9397 );
9398 assert!(validate_multimedia_transmit(&state, &legacy).is_ok());
9399 }
9400
9401 state.registration.protocol = ProtocolVersion::V16;
9402 let ipv6 = MultimediaTransmitDescriptor {
9403 endpoint: MediaEndpointAddress {
9404 address: "2001:db8::80".parse().unwrap(),
9405 port: 5080,
9406 },
9407 ..descriptor
9408 };
9409 assert!(matches!(
9410 validate_multimedia_transmit(&state, &ipv6),
9411 Err(ServerError::InvalidMultimediaTransmit(_))
9412 ));
9413 assert_eq!(state.media_capabilities.audio(), audio_before);
9414 }
9415
9416 #[test]
9417 fn multimedia_transmit_controls_encode_only_their_typed_parameter_words() {
9418 fn assert_control(
9419 control: MultimediaTransmitControl,
9420 expected_command: MiscCommandType,
9421 expected_words: &[u32],
9422 ) {
9423 let (command, data) = encode_multimedia_transmit_control(control).unwrap();
9424 let expected = expected_words
9425 .iter()
9426 .flat_map(|word| word.to_le_bytes())
9427 .collect::<Vec<_>>();
9428 assert_eq!(command, expected_command);
9429 assert_eq!(data.as_bytes(), expected);
9430 }
9431
9432 assert_control(
9433 MultimediaTransmitControl::FreezePicture,
9434 MiscCommandType::VideoFreezePicture,
9435 &[],
9436 );
9437 assert_control(
9438 MultimediaTransmitControl::FastPictureUpdate {
9439 first_gob: 1,
9440 gob_count: 2,
9441 },
9442 MiscCommandType::VideoFastUpdatePicture,
9443 &[1, 2],
9444 );
9445 assert_control(
9446 MultimediaTransmitControl::FastGobUpdate {
9447 first_gob: 3,
9448 gob_count: 4,
9449 },
9450 MiscCommandType::VideoFastUpdateGob,
9451 &[3, 4],
9452 );
9453 assert_control(
9454 MultimediaTransmitControl::FastMacroblockUpdate {
9455 first_gob: 5,
9456 first_macroblock: 6,
9457 macroblock_count: 7,
9458 },
9459 MiscCommandType::VideoFastUpdateMacroblock,
9460 &[5, 6, 7],
9461 );
9462 assert_control(
9463 MultimediaTransmitControl::LostPicture {
9464 picture_number: 8,
9465 long_term_picture_index: 9,
9466 },
9467 MiscCommandType::LostPicture,
9468 &[8, 9],
9469 );
9470 assert_control(
9471 MultimediaTransmitControl::LostPartialPicture {
9472 picture_number: 10,
9473 long_term_picture_index: 11,
9474 first_macroblock: 12,
9475 macroblock_count: 13,
9476 },
9477 MiscCommandType::LostPartialPicture,
9478 &[10, 11, 12, 13],
9479 );
9480 let pictures = VideoPictureReferences::new([
9481 VideoPictureReference {
9482 picture_number: 14,
9483 long_term_picture_index: 15,
9484 },
9485 VideoPictureReference {
9486 picture_number: 16,
9487 long_term_picture_index: 17,
9488 },
9489 ])
9490 .unwrap();
9491 assert_control(
9492 MultimediaTransmitControl::RecoveryReferencePicture { pictures },
9493 MiscCommandType::RecoveryReferencePicture,
9494 &[2, 14, 15, 16, 17],
9495 );
9496 assert_control(
9497 MultimediaTransmitControl::TemporalSpatialTradeoff { value: 18 },
9498 MiscCommandType::TemporalSpatialTradeoff,
9499 &[18],
9500 );
9501 assert!(matches!(
9502 VideoPictureReferences::new(std::iter::repeat(VideoPictureReference {
9503 picture_number: 1,
9504 long_term_picture_index: 2,
9505 })),
9506 Err(ServerError::InvalidMultimediaTransmitControl(_))
9507 ));
9508 }
9509
9510 #[tokio::test]
9511 async fn video_receive_session_correlates_fragmented_acknowledgements_and_preserves_audio() {
9512 let device = definition();
9513 let device_id = device.id.clone();
9514 let config = ServerConfig {
9515 bind: "127.0.0.1:0".parse().unwrap(),
9516 advertised_address: Ipv4Addr::LOCALHOST,
9517 ..ServerConfig::default()
9518 };
9519 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
9520 let address = server.local_addr().unwrap();
9521 let task = tokio::spawn(server.run());
9522 let mut phone = TcpStream::connect(address).await.unwrap();
9523 let mut decoder = FrameDecoder::new();
9524 let protocol = ProtocolVersion::V22;
9525 let call_id = CallId::new(71);
9526
9527 phone.write_all(®ister_bytes(protocol)).await.unwrap();
9528 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
9529 assert!(matches!(
9530 events.recv().await,
9531 Some(Event::Device(DeviceEvent {
9532 event: DeviceEventKind::Registered(_),
9533 ..
9534 }))
9535 ));
9536 phone
9537 .write_all(&capability_update_bytes(
9538 protocol,
9539 Codec::Pcmu,
9540 Codec::H264,
9541 71,
9542 ))
9543 .await
9544 .unwrap();
9545 assert!(matches!(
9546 events.recv().await,
9547 Some(Event::Device(DeviceEvent {
9548 event: DeviceEventKind::Capabilities { .. },
9549 ..
9550 }))
9551 ));
9552
9553 handle
9554 .send_confirmed(Command::new(
9555 device_id.clone(),
9556 CommandAction::BeginCall {
9557 line_instance: LineInstance::new(1),
9558 call_id,
9559 codec: Codec::Pcmu,
9560 },
9561 ))
9562 .await
9563 .unwrap();
9564 let begin = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9565 matches!(message, ServerMessage::SelectSoftKeys { .. })
9566 })
9567 .await;
9568 let wire_reference = begin
9569 .iter()
9570 .find_map(|message| match message {
9571 ServerMessage::CallState { call_reference, .. } => {
9572 Some(CallReference::new(*call_reference))
9573 }
9574 _ => None,
9575 })
9576 .expect("begin call omitted its wire identity");
9577 assert!(matches!(
9578 handle
9579 .send_confirmed(Command::new(
9580 device_id.clone(),
9581 CommandAction::OpenMultimediaReceiveChannel {
9582 call_id,
9583 descriptor: video_receive_descriptor(
9584 699,
9585 MediaEndpointAddress {
9586 address: "192.0.2.69".parse().unwrap(),
9587 port: 5068,
9588 },
9589 ),
9590 },
9591 ))
9592 .await,
9593 Err(ServerError::CommandWrite(_))
9594 ));
9595 handle
9596 .send_confirmed(Command::new(
9597 device_id.clone(),
9598 CommandAction::SetCallState {
9599 call_id,
9600 state: CallState::Connected,
9601 },
9602 ))
9603 .await
9604 .unwrap();
9605 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
9606
9607 handle
9608 .send_confirmed(Command::new(
9609 device_id.clone(),
9610 CommandAction::OpenReceiveChannel {
9611 call_id,
9612 source: None,
9613 codec: Codec::Pcmu,
9614 packet_ms: 20,
9615 max_frames_per_packet: 2,
9616 dtmf_mode: DtmfMode::Rfc2833,
9617 audio_processing: AudioProcessingPolicy::default(),
9618 },
9619 ))
9620 .await
9621 .unwrap();
9622 let audio_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9623 matches!(message, ServerMessage::OpenReceiveChannel { .. })
9624 })
9625 .await;
9626 let audio_token = audio_open
9627 .iter()
9628 .find_map(|message| match message {
9629 ServerMessage::OpenReceiveChannel {
9630 passthrough_party_id,
9631 ..
9632 } => Some(*passthrough_party_id),
9633 _ => None,
9634 })
9635 .unwrap();
9636
9637 let first_descriptor = video_receive_descriptor(
9638 700,
9639 MediaEndpointAddress {
9640 address: "192.0.2.70".parse().unwrap(),
9641 port: 5070,
9642 },
9643 );
9644 handle
9645 .send_confirmed(Command::new(
9646 device_id.clone(),
9647 CommandAction::OpenMultimediaReceiveChannel {
9648 call_id,
9649 descriptor: first_descriptor.clone(),
9650 },
9651 ))
9652 .await
9653 .unwrap();
9654 let first_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9655 matches!(message, ServerMessage::OpenMultimediaChannel(_))
9656 })
9657 .await
9658 .into_iter()
9659 .find_map(|message| match message {
9660 ServerMessage::OpenMultimediaChannel(open) => Some(open),
9661 _ => None,
9662 })
9663 .unwrap();
9664 assert_eq!(first_open.payload.codec(), Codec::H264);
9665 assert_eq!(first_open.line_instance, 1);
9666 assert_eq!(first_open.call_reference, wire_reference);
9667 assert_eq!(first_open.payload, first_descriptor.payload);
9668 let first_token = first_open.passthrough_party_id;
9669 let endpoint = MediaEndpointAddress {
9670 address: "198.51.100.70".parse().unwrap(),
9671 port: 6070,
9672 };
9673 let wrong_call = ClientMessage::OpenMultimediaReceiveChannelAck(
9674 crate::OpenMultimediaReceiveChannelAck {
9675 status: MediaStatus::Ok,
9676 endpoint,
9677 passthrough_party_id: first_token,
9678 call_reference: CallReference::new(wire_reference.get() + 1),
9679 },
9680 )
9681 .encode(protocol)
9682 .unwrap();
9683 let exact = ClientMessage::OpenMultimediaReceiveChannelAck(
9684 crate::OpenMultimediaReceiveChannelAck {
9685 status: MediaStatus::Ok,
9686 endpoint,
9687 passthrough_party_id: first_token,
9688 call_reference: wire_reference,
9689 },
9690 )
9691 .encode(protocol)
9692 .unwrap();
9693 let mut coalesced_prefix = wrong_call;
9694 coalesced_prefix.extend(
9695 ClientMessage::OpenMultimediaReceiveChannelAck(
9696 crate::OpenMultimediaReceiveChannelAck {
9697 status: MediaStatus::Ok,
9698 endpoint: MediaEndpointAddress {
9699 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
9700 port: endpoint.port,
9701 },
9702 passthrough_party_id: first_token,
9703 call_reference: wire_reference,
9704 },
9705 )
9706 .encode(protocol)
9707 .unwrap(),
9708 );
9709 coalesced_prefix.push(exact[0]);
9710 phone.write_all(&coalesced_prefix).await.unwrap();
9711 assert!(
9712 tokio::time::timeout(Duration::from_millis(25), events.recv())
9713 .await
9714 .is_err()
9715 );
9716 for fragment in exact[1..].chunks(3) {
9717 phone.write_all(fragment).await.unwrap();
9718 }
9719 assert!(matches!(
9720 events.recv().await,
9721 Some(Event::Device(DeviceEvent {
9722 event: DeviceEventKind::MultimediaReceiveChannelOpened {
9723 call_id: actual_call,
9724 codec: Codec::H264,
9725 endpoint: actual_endpoint,
9726 passthrough_party_id,
9727 },
9728 ..
9729 })) if actual_call == call_id
9730 && actual_endpoint == endpoint
9731 && passthrough_party_id == first_token
9732 ));
9733 phone.write_all(&exact).await.unwrap();
9734 assert!(
9735 tokio::time::timeout(Duration::from_millis(25), events.recv())
9736 .await
9737 .is_err()
9738 );
9739
9740 phone
9741 .write_all(
9742 &ClientMessage::OpenReceiveChannelAck {
9743 status: MediaStatus::Ok,
9744 address: "198.51.100.71".parse().unwrap(),
9745 port: 6072,
9746 call_reference: wire_reference.get(),
9747 passthrough_party_id: audio_token,
9748 }
9749 .encode(protocol)
9750 .unwrap(),
9751 )
9752 .await
9753 .unwrap();
9754 assert!(matches!(
9755 events.recv().await,
9756 Some(Event::Device(DeviceEvent {
9757 event: DeviceEventKind::ReceiveChannelOpened {
9758 call_id: actual_call,
9759 status: MediaStatus::Ok,
9760 ..
9761 },
9762 ..
9763 })) if actual_call == call_id
9764 ));
9765
9766 let replacement_descriptor = video_receive_descriptor(
9767 701,
9768 MediaEndpointAddress {
9769 address: "192.0.2.71".parse().unwrap(),
9770 port: 5072,
9771 },
9772 );
9773 handle
9774 .send_confirmed(Command::new(
9775 device_id.clone(),
9776 CommandAction::OpenMultimediaReceiveChannel {
9777 call_id,
9778 descriptor: replacement_descriptor,
9779 },
9780 ))
9781 .await
9782 .unwrap();
9783 let replacement =
9784 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9785 matches!(
9786 message,
9787 ServerMessage::OpenMultimediaChannel(open)
9788 if open.conference_id == ConferenceId::new(701)
9789 )
9790 })
9791 .await;
9792 let close_index = replacement
9793 .iter()
9794 .position(|message| {
9795 matches!(
9796 message,
9797 ServerMessage::CloseMultimediaReceiveChannel(control)
9798 if control.passthrough_party_id == first_token
9799 )
9800 })
9801 .expect("replacement omitted the old video close");
9802 let (open_index, replacement_open) = replacement
9803 .iter()
9804 .enumerate()
9805 .find_map(|(index, message)| match message {
9806 ServerMessage::OpenMultimediaChannel(open)
9807 if open.conference_id == ConferenceId::new(701) =>
9808 {
9809 Some((index, open))
9810 }
9811 _ => None,
9812 })
9813 .unwrap();
9814 assert!(close_index < open_index);
9815 assert_ne!(replacement_open.passthrough_party_id, first_token);
9816
9817 let negative = ClientMessage::OpenMultimediaReceiveChannelAck(
9818 crate::OpenMultimediaReceiveChannelAck {
9819 status: MediaStatus::OutOfChannels,
9820 endpoint,
9821 passthrough_party_id: replacement_open.passthrough_party_id,
9822 call_reference: wire_reference,
9823 },
9824 )
9825 .encode(protocol)
9826 .unwrap();
9827 phone.write_all(&exact).await.unwrap();
9828 phone.write_all(&negative).await.unwrap();
9829 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9830 matches!(
9831 message,
9832 ServerMessage::CloseMultimediaReceiveChannel(control)
9833 if control.passthrough_party_id
9834 == replacement_open.passthrough_party_id
9835 )
9836 })
9837 .await;
9838 assert!(matches!(
9839 events.recv().await,
9840 Some(Event::Device(DeviceEvent {
9841 event: DeviceEventKind::MultimediaReceiveChannelFailed {
9842 call_id: actual_call,
9843 codec: Codec::H264,
9844 status: MediaStatus::OutOfChannels,
9845 endpoint: actual_endpoint,
9846 passthrough_party_id,
9847 },
9848 ..
9849 })) if actual_call == call_id
9850 && actual_endpoint == endpoint
9851 && passthrough_party_id == replacement_open.passthrough_party_id
9852 ));
9853 phone.write_all(&negative).await.unwrap();
9854 assert!(
9855 tokio::time::timeout(Duration::from_millis(25), events.recv())
9856 .await
9857 .is_err()
9858 );
9859
9860 handle
9861 .send_confirmed(Command::new(
9862 device_id.clone(),
9863 CommandAction::OpenMultimediaReceiveChannel {
9864 call_id,
9865 descriptor: video_receive_descriptor(
9866 702,
9867 MediaEndpointAddress {
9868 address: "192.0.2.72".parse().unwrap(),
9869 port: 5074,
9870 },
9871 ),
9872 },
9873 ))
9874 .await
9875 .unwrap();
9876 let final_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9877 matches!(
9878 message,
9879 ServerMessage::OpenMultimediaChannel(open)
9880 if open.conference_id == ConferenceId::new(702)
9881 )
9882 })
9883 .await
9884 .into_iter()
9885 .find_map(|message| match message {
9886 ServerMessage::OpenMultimediaChannel(open)
9887 if open.conference_id == ConferenceId::new(702) =>
9888 {
9889 Some(open)
9890 }
9891 _ => None,
9892 })
9893 .unwrap();
9894 handle
9895 .send_confirmed(Command::new(
9896 device_id.clone(),
9897 CommandAction::CloseCall { call_id },
9898 ))
9899 .await
9900 .unwrap();
9901 let close_messages =
9902 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9903 matches!(
9904 message,
9905 ServerMessage::CallState {
9906 state: CallState::OnHook,
9907 ..
9908 }
9909 )
9910 })
9911 .await;
9912 let video_close = close_messages
9913 .iter()
9914 .position(|message| {
9915 matches!(
9916 message,
9917 ServerMessage::CloseMultimediaReceiveChannel(control)
9918 if control.passthrough_party_id
9919 == final_open.passthrough_party_id
9920 )
9921 })
9922 .expect("call close omitted the video receive leg");
9923 let audio_close = close_messages
9924 .iter()
9925 .position(|message| {
9926 matches!(
9927 message,
9928 ServerMessage::CloseReceiveChannel(control)
9929 if control.passthrough_party_id.get() == audio_token
9930 )
9931 })
9932 .expect("call close omitted the independently opened audio receive leg");
9933 let on_hook = close_messages
9934 .iter()
9935 .position(|message| {
9936 matches!(
9937 message,
9938 ServerMessage::CallState {
9939 state: CallState::OnHook,
9940 ..
9941 }
9942 )
9943 })
9944 .unwrap();
9945 assert!(video_close < audio_close && audio_close < on_hook);
9946
9947 let reconfigured_call_id = CallId::new(73);
9948 handle
9949 .send_confirmed(Command::new(
9950 device_id.clone(),
9951 CommandAction::BeginCall {
9952 line_instance: LineInstance::new(1),
9953 call_id: reconfigured_call_id,
9954 codec: Codec::Pcmu,
9955 },
9956 ))
9957 .await
9958 .unwrap();
9959 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
9960 handle
9961 .send_confirmed(Command::new(
9962 device_id.clone(),
9963 CommandAction::SetCallState {
9964 call_id: reconfigured_call_id,
9965 state: CallState::Connected,
9966 },
9967 ))
9968 .await
9969 .unwrap();
9970 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
9971 handle
9972 .send_confirmed(Command::new(
9973 device_id.clone(),
9974 CommandAction::OpenMultimediaReceiveChannel {
9975 call_id: reconfigured_call_id,
9976 descriptor: video_receive_descriptor(
9977 705,
9978 MediaEndpointAddress {
9979 address: "192.0.2.75".parse().unwrap(),
9980 port: 5080,
9981 },
9982 ),
9983 },
9984 ))
9985 .await
9986 .unwrap();
9987 let reconfigure_open =
9988 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
9989 matches!(
9990 message,
9991 ServerMessage::OpenMultimediaChannel(open)
9992 if open.conference_id == ConferenceId::new(705)
9993 )
9994 })
9995 .await
9996 .into_iter()
9997 .find_map(|message| match message {
9998 ServerMessage::OpenMultimediaChannel(open)
9999 if open.conference_id == ConferenceId::new(705) =>
10000 {
10001 Some(open)
10002 }
10003 _ => None,
10004 })
10005 .unwrap();
10006 handle
10007 .send_confirmed(Command::new(
10008 device_id,
10009 CommandAction::StartMultimediaTransmission {
10010 call_id: reconfigured_call_id,
10011 descriptor: video_transmit_descriptor(
10012 706,
10013 MediaEndpointAddress {
10014 address: "192.0.2.76".parse().unwrap(),
10015 port: 5082,
10016 },
10017 ),
10018 },
10019 ))
10020 .await
10021 .unwrap();
10022 let reconfigure_start =
10023 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10024 matches!(
10025 message,
10026 ServerMessage::StartMultimediaTransmission(start)
10027 if start.conference_id == ConferenceId::new(706)
10028 )
10029 })
10030 .await
10031 .into_iter()
10032 .find_map(|message| match message {
10033 ServerMessage::StartMultimediaTransmission(start)
10034 if start.conference_id == ConferenceId::new(706) =>
10035 {
10036 Some(start)
10037 }
10038 _ => None,
10039 })
10040 .unwrap();
10041 let mut replacement = definition();
10042 replacement.description = "replacement".into();
10043 handle.reconfigure([replacement]).await.unwrap();
10044 let reconfigure_messages =
10045 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10046 matches!(
10047 message,
10048 ServerMessage::StopMultimediaTransmission(control)
10049 if control.passthrough_party_id
10050 == reconfigure_start.passthrough_party_id
10051 )
10052 })
10053 .await;
10054 let receive_close = reconfigure_messages
10055 .iter()
10056 .position(|message| {
10057 matches!(
10058 message,
10059 ServerMessage::CloseMultimediaReceiveChannel(control)
10060 if control.passthrough_party_id
10061 == reconfigure_open.passthrough_party_id
10062 )
10063 })
10064 .expect("reconfigure omitted the video receive close");
10065 let transmit_stop = reconfigure_messages
10066 .iter()
10067 .position(|message| {
10068 matches!(
10069 message,
10070 ServerMessage::StopMultimediaTransmission(control)
10071 if control.passthrough_party_id
10072 == reconfigure_start.passthrough_party_id
10073 )
10074 })
10075 .expect("reconfigure omitted the video transmit stop");
10076 assert!(receive_close < transmit_stop);
10077 assert!(matches!(
10078 events.recv().await,
10079 Some(Event::Device(DeviceEvent {
10080 event: DeviceEventKind::Disconnected {},
10081 ..
10082 }))
10083 ));
10084
10085 handle.shutdown().await.unwrap();
10086 task.await.unwrap().unwrap();
10087 }
10088
10089 #[tokio::test]
10090 async fn video_transmit_session_correlates_frames_and_preserves_receive_and_audio() {
10091 let device = definition();
10092 let device_id = device.id.clone();
10093 let config = ServerConfig {
10094 bind: "127.0.0.1:0".parse().unwrap(),
10095 advertised_address: Ipv4Addr::LOCALHOST,
10096 ..ServerConfig::default()
10097 };
10098 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
10099 let address = server.local_addr().unwrap();
10100 let task = tokio::spawn(server.run());
10101 let mut phone = TcpStream::connect(address).await.unwrap();
10102 let mut decoder = FrameDecoder::new();
10103 let protocol = ProtocolVersion::V22;
10104 let call_id = CallId::new(81);
10105
10106 phone.write_all(®ister_bytes(protocol)).await.unwrap();
10107 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
10108 assert!(matches!(
10109 events.recv().await,
10110 Some(Event::Device(DeviceEvent {
10111 event: DeviceEventKind::Registered(_),
10112 ..
10113 }))
10114 ));
10115 phone
10116 .write_all(&capability_update_bytes(
10117 protocol,
10118 Codec::Pcmu,
10119 Codec::H264,
10120 81,
10121 ))
10122 .await
10123 .unwrap();
10124 assert!(matches!(
10125 events.recv().await,
10126 Some(Event::Device(DeviceEvent {
10127 event: DeviceEventKind::Capabilities { .. },
10128 ..
10129 }))
10130 ));
10131 handle
10132 .send_confirmed(Command::new(
10133 device_id.clone(),
10134 CommandAction::BeginCall {
10135 line_instance: LineInstance::new(1),
10136 call_id,
10137 codec: Codec::Pcmu,
10138 },
10139 ))
10140 .await
10141 .unwrap();
10142 let begin = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10143 matches!(message, ServerMessage::SelectSoftKeys { .. })
10144 })
10145 .await;
10146 let call_reference = begin
10147 .iter()
10148 .find_map(|message| match message {
10149 ServerMessage::CallState { call_reference, .. } => {
10150 Some(CallReference::new(*call_reference))
10151 }
10152 _ => None,
10153 })
10154 .unwrap();
10155 assert!(matches!(
10156 handle
10157 .send_confirmed(Command::new(
10158 device_id.clone(),
10159 CommandAction::StartMultimediaTransmission {
10160 call_id,
10161 descriptor: video_transmit_descriptor(
10162 809,
10163 MediaEndpointAddress {
10164 address: "192.0.2.89".parse().unwrap(),
10165 port: 5088,
10166 },
10167 ),
10168 },
10169 ))
10170 .await,
10171 Err(ServerError::CommandWrite(_))
10172 ));
10173 handle
10174 .send_confirmed(Command::new(
10175 device_id.clone(),
10176 CommandAction::SetCallState {
10177 call_id,
10178 state: CallState::Connected,
10179 },
10180 ))
10181 .await
10182 .unwrap();
10183 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
10184
10185 handle
10186 .send_confirmed(Command::new(
10187 device_id.clone(),
10188 CommandAction::OpenReceiveChannel {
10189 call_id,
10190 source: None,
10191 codec: Codec::Pcmu,
10192 packet_ms: 20,
10193 max_frames_per_packet: 2,
10194 dtmf_mode: DtmfMode::Rfc2833,
10195 audio_processing: AudioProcessingPolicy::default(),
10196 },
10197 ))
10198 .await
10199 .unwrap();
10200 let audio_open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10201 matches!(message, ServerMessage::OpenReceiveChannel { .. })
10202 })
10203 .await;
10204 let audio_token = audio_open
10205 .iter()
10206 .find_map(|message| match message {
10207 ServerMessage::OpenReceiveChannel {
10208 passthrough_party_id,
10209 ..
10210 } => Some(*passthrough_party_id),
10211 _ => None,
10212 })
10213 .unwrap();
10214
10215 handle
10216 .send_confirmed(Command::new(
10217 device_id.clone(),
10218 CommandAction::OpenMultimediaReceiveChannel {
10219 call_id,
10220 descriptor: video_receive_descriptor(
10221 810,
10222 MediaEndpointAddress {
10223 address: "192.0.2.81".parse().unwrap(),
10224 port: 5082,
10225 },
10226 ),
10227 },
10228 ))
10229 .await
10230 .unwrap();
10231 let receive_open =
10232 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10233 matches!(message, ServerMessage::OpenMultimediaChannel(_))
10234 })
10235 .await
10236 .into_iter()
10237 .find_map(|message| match message {
10238 ServerMessage::OpenMultimediaChannel(open) => Some(open),
10239 _ => None,
10240 })
10241 .unwrap();
10242
10243 let first_descriptor = video_transmit_descriptor(
10244 811,
10245 MediaEndpointAddress {
10246 address: "192.0.2.82".parse().unwrap(),
10247 port: 5084,
10248 },
10249 );
10250 handle
10251 .send_confirmed(Command::new(
10252 device_id.clone(),
10253 CommandAction::StartMultimediaTransmission {
10254 call_id,
10255 descriptor: first_descriptor.clone(),
10256 },
10257 ))
10258 .await
10259 .unwrap();
10260 let first_start =
10261 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10262 matches!(message, ServerMessage::StartMultimediaTransmission(_))
10263 })
10264 .await
10265 .into_iter()
10266 .find_map(|message| match message {
10267 ServerMessage::StartMultimediaTransmission(start) => Some(start),
10268 _ => None,
10269 })
10270 .unwrap();
10271 assert_eq!(first_start.call_reference, call_reference);
10272 assert_eq!(first_start.endpoint, first_descriptor.endpoint);
10273 assert_eq!(first_start.payload, first_descriptor.payload);
10274 let first_token = first_start.passthrough_party_id;
10275 assert!(matches!(
10276 handle
10277 .send_confirmed(Command::new(
10278 device_id.clone(),
10279 CommandAction::ControlMultimediaTransmission {
10280 call_id,
10281 passthrough_party_id: first_token,
10282 control: MultimediaTransmitControl::FreezePicture,
10283 },
10284 ))
10285 .await,
10286 Err(ServerError::CommandWrite(_))
10287 ));
10288 let station_endpoint = MediaEndpointAddress {
10289 address: "198.51.100.81".parse().unwrap(),
10290 port: 6082,
10291 };
10292 let wrong_conference =
10293 ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10294 conference_id: ConferenceId::new(812),
10295 passthrough_party_id: first_token,
10296 call_reference,
10297 endpoint: station_endpoint,
10298 status: MediaStatus::Ok,
10299 })
10300 .encode(protocol)
10301 .unwrap();
10302 let wrong_call =
10303 ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10304 conference_id: first_start.conference_id,
10305 passthrough_party_id: first_token,
10306 call_reference: CallReference::new(call_reference.get() + 1),
10307 endpoint: station_endpoint,
10308 status: MediaStatus::Ok,
10309 })
10310 .encode(protocol)
10311 .unwrap();
10312 let exact =
10313 ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10314 conference_id: first_start.conference_id,
10315 passthrough_party_id: first_token,
10316 call_reference,
10317 endpoint: station_endpoint,
10318 status: MediaStatus::Ok,
10319 })
10320 .encode(protocol)
10321 .unwrap();
10322 let mut coalesced = wrong_conference;
10323 coalesced.extend(wrong_call);
10324 coalesced.extend(
10325 ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10326 conference_id: first_start.conference_id,
10327 passthrough_party_id: PassthroughPartyId::new(first_token.get() + 1),
10328 call_reference,
10329 endpoint: station_endpoint,
10330 status: MediaStatus::Ok,
10331 })
10332 .encode(protocol)
10333 .unwrap(),
10334 );
10335 coalesced.extend(
10336 ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10337 conference_id: first_start.conference_id,
10338 passthrough_party_id: first_token,
10339 call_reference,
10340 endpoint: MediaEndpointAddress {
10341 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
10342 port: station_endpoint.port,
10343 },
10344 status: MediaStatus::Ok,
10345 })
10346 .encode(protocol)
10347 .unwrap(),
10348 );
10349 coalesced.push(exact[0]);
10350 phone.write_all(&coalesced).await.unwrap();
10351 assert!(
10352 tokio::time::timeout(Duration::from_millis(25), events.recv())
10353 .await
10354 .is_err()
10355 );
10356 for fragment in exact[1..].chunks(3) {
10357 phone.write_all(fragment).await.unwrap();
10358 }
10359 assert!(matches!(
10360 events.recv().await,
10361 Some(Event::Device(DeviceEvent {
10362 event: DeviceEventKind::MultimediaTransmitStarted {
10363 call_id: actual_call,
10364 codec: Codec::H264,
10365 endpoint,
10366 passthrough_party_id,
10367 },
10368 ..
10369 })) if actual_call == call_id
10370 && endpoint == station_endpoint
10371 && passthrough_party_id == first_token
10372 ));
10373 phone.write_all(&exact).await.unwrap();
10374 assert!(
10375 tokio::time::timeout(Duration::from_millis(25), events.recv())
10376 .await
10377 .is_err()
10378 );
10379
10380 assert!(matches!(
10381 handle
10382 .send_confirmed(Command::new(
10383 device_id.clone(),
10384 CommandAction::SetMultimediaTransmitBitRate {
10385 call_id,
10386 passthrough_party_id: PassthroughPartyId::new(first_token.get() + 1),
10387 maximum_bit_rate: 512_000,
10388 },
10389 ))
10390 .await,
10391 Err(ServerError::CommandWrite(_))
10392 ));
10393 for action in [
10394 CommandAction::SetMultimediaTransmitBitRate {
10395 call_id,
10396 passthrough_party_id: first_token,
10397 maximum_bit_rate: 512_000,
10398 },
10399 CommandAction::NotifyMultimediaTransmitBitRate {
10400 call_id,
10401 passthrough_party_id: first_token,
10402 maximum_bit_rate: 384_000,
10403 },
10404 CommandAction::ControlMultimediaTransmission {
10405 call_id,
10406 passthrough_party_id: first_token,
10407 control: MultimediaTransmitControl::FastPictureUpdate {
10408 first_gob: 4,
10409 gob_count: 2,
10410 },
10411 },
10412 ] {
10413 handle
10414 .send_confirmed(Command::new(device_id.clone(), action))
10415 .await
10416 .unwrap();
10417 }
10418 let control_messages =
10419 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10420 matches!(message, ServerMessage::MiscellaneousCommand(_))
10421 })
10422 .await;
10423 assert!(matches!(
10424 control_messages.as_slice(),
10425 [
10426 ServerMessage::FlowControlCommand(VideoFlowControl {
10427 conference_id,
10428 passthrough_party_id,
10429 call_reference: actual_call,
10430 maximum_bit_rate: 512_000,
10431 }),
10432 ServerMessage::FlowControlNotify(VideoFlowControl {
10433 conference_id: notify_conference,
10434 passthrough_party_id: notify_token,
10435 call_reference: notify_call,
10436 maximum_bit_rate: 384_000,
10437 }),
10438 ServerMessage::MiscellaneousCommand(MiscellaneousCommand {
10439 conference_id: command_conference,
10440 passthrough_party_id: command_token,
10441 call_reference: command_call,
10442 command: MiscCommandType::VideoFastUpdatePicture,
10443 data,
10444 }),
10445 ] if *conference_id == first_start.conference_id
10446 && *passthrough_party_id == first_token
10447 && *actual_call == call_reference
10448 && *notify_conference == first_start.conference_id
10449 && *notify_token == first_token
10450 && *notify_call == call_reference
10451 && *command_conference == first_start.conference_id
10452 && *command_token == first_token
10453 && *command_call == call_reference
10454 && data.as_bytes()[..8]
10455 == [4_u32.to_le_bytes(), 2_u32.to_le_bytes()].concat()
10456 && data.as_bytes()[8..].iter().all(|byte| *byte == 0)
10457 ));
10458
10459 phone
10460 .write_all(
10461 &ClientMessage::OpenMultimediaReceiveChannelAck(
10462 crate::OpenMultimediaReceiveChannelAck {
10463 status: MediaStatus::Ok,
10464 endpoint: MediaEndpointAddress {
10465 address: "198.51.100.82".parse().unwrap(),
10466 port: 6084,
10467 },
10468 passthrough_party_id: receive_open.passthrough_party_id,
10469 call_reference,
10470 },
10471 )
10472 .encode(protocol)
10473 .unwrap(),
10474 )
10475 .await
10476 .unwrap();
10477 assert!(matches!(
10478 events.recv().await,
10479 Some(Event::Device(DeviceEvent {
10480 event: DeviceEventKind::MultimediaReceiveChannelOpened {
10481 call_id: actual_call,
10482 ..
10483 },
10484 ..
10485 })) if actual_call == call_id
10486 ));
10487 phone
10488 .write_all(
10489 &ClientMessage::OpenReceiveChannelAck {
10490 status: MediaStatus::Ok,
10491 address: "198.51.100.83".parse().unwrap(),
10492 port: 6086,
10493 call_reference: call_reference.get(),
10494 passthrough_party_id: audio_token,
10495 }
10496 .encode(protocol)
10497 .unwrap(),
10498 )
10499 .await
10500 .unwrap();
10501 assert!(matches!(
10502 events.recv().await,
10503 Some(Event::Device(DeviceEvent {
10504 event: DeviceEventKind::ReceiveChannelOpened {
10505 call_id: actual_call,
10506 status: MediaStatus::Ok,
10507 ..
10508 },
10509 ..
10510 })) if actual_call == call_id
10511 ));
10512
10513 let replacement_descriptor = video_transmit_descriptor(
10514 813,
10515 MediaEndpointAddress {
10516 address: "192.0.2.83".parse().unwrap(),
10517 port: 5086,
10518 },
10519 );
10520 handle
10521 .send_confirmed(Command::new(
10522 device_id.clone(),
10523 CommandAction::StartMultimediaTransmission {
10524 call_id,
10525 descriptor: replacement_descriptor,
10526 },
10527 ))
10528 .await
10529 .unwrap();
10530 let replacement =
10531 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10532 matches!(
10533 message,
10534 ServerMessage::StartMultimediaTransmission(start)
10535 if start.conference_id == ConferenceId::new(813)
10536 )
10537 })
10538 .await;
10539 let stop_index = replacement
10540 .iter()
10541 .position(|message| {
10542 matches!(
10543 message,
10544 ServerMessage::StopMultimediaTransmission(control)
10545 if control.passthrough_party_id == first_token
10546 )
10547 })
10548 .expect("replacement omitted the old video transmit stop");
10549 let (start_index, replacement_start) = replacement
10550 .iter()
10551 .enumerate()
10552 .find_map(|(index, message)| match message {
10553 ServerMessage::StartMultimediaTransmission(start)
10554 if start.conference_id == ConferenceId::new(813) =>
10555 {
10556 Some((index, start))
10557 }
10558 _ => None,
10559 })
10560 .unwrap();
10561 assert!(stop_index < start_index);
10562 assert_ne!(replacement_start.passthrough_party_id, first_token);
10563 for passthrough_party_id in [first_token, replacement_start.passthrough_party_id] {
10564 assert!(matches!(
10565 handle
10566 .send_confirmed(Command::new(
10567 device_id.clone(),
10568 CommandAction::NotifyMultimediaTransmitBitRate {
10569 call_id,
10570 passthrough_party_id,
10571 maximum_bit_rate: 256_000,
10572 },
10573 ))
10574 .await,
10575 Err(ServerError::CommandWrite(_))
10576 ));
10577 }
10578
10579 let negative =
10580 ClientMessage::StartMultimediaTransmissionAck(crate::StartMultimediaTransmissionAck {
10581 conference_id: replacement_start.conference_id,
10582 passthrough_party_id: replacement_start.passthrough_party_id,
10583 call_reference,
10584 endpoint: station_endpoint,
10585 status: MediaStatus::OutOfChannels,
10586 })
10587 .encode(protocol)
10588 .unwrap();
10589 phone.write_all(&exact).await.unwrap();
10590 phone.write_all(&negative).await.unwrap();
10591 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10592 matches!(
10593 message,
10594 ServerMessage::StopMultimediaTransmission(control)
10595 if control.passthrough_party_id
10596 == replacement_start.passthrough_party_id
10597 )
10598 })
10599 .await;
10600 assert!(matches!(
10601 events.recv().await,
10602 Some(Event::Device(DeviceEvent {
10603 event: DeviceEventKind::MultimediaTransmitFailed {
10604 call_id: actual_call,
10605 codec: Codec::H264,
10606 status: MediaStatus::OutOfChannels,
10607 endpoint,
10608 passthrough_party_id,
10609 },
10610 ..
10611 })) if actual_call == call_id
10612 && endpoint == station_endpoint
10613 && passthrough_party_id == replacement_start.passthrough_party_id
10614 ));
10615 phone.write_all(&negative).await.unwrap();
10616 assert!(
10617 tokio::time::timeout(Duration::from_millis(25), events.recv())
10618 .await
10619 .is_err()
10620 );
10621
10622 handle
10623 .send_confirmed(Command::new(
10624 device_id.clone(),
10625 CommandAction::StartMultimediaTransmission {
10626 call_id,
10627 descriptor: video_transmit_descriptor(
10628 814,
10629 MediaEndpointAddress {
10630 address: "192.0.2.84".parse().unwrap(),
10631 port: 5088,
10632 },
10633 ),
10634 },
10635 ))
10636 .await
10637 .unwrap();
10638 let stopped_start =
10639 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10640 matches!(
10641 message,
10642 ServerMessage::StartMultimediaTransmission(start)
10643 if start.conference_id == ConferenceId::new(814)
10644 )
10645 })
10646 .await
10647 .into_iter()
10648 .find_map(|message| match message {
10649 ServerMessage::StartMultimediaTransmission(start)
10650 if start.conference_id == ConferenceId::new(814) =>
10651 {
10652 Some(start)
10653 }
10654 _ => None,
10655 })
10656 .unwrap();
10657 handle
10658 .send_confirmed(Command::new(
10659 device_id.clone(),
10660 CommandAction::StopMultimediaTransmission { call_id },
10661 ))
10662 .await
10663 .unwrap();
10664 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10665 matches!(
10666 message,
10667 ServerMessage::StopMultimediaTransmission(control)
10668 if control.passthrough_party_id
10669 == stopped_start.passthrough_party_id
10670 )
10671 })
10672 .await;
10673 handle
10674 .send_confirmed(Command::new(
10675 device_id.clone(),
10676 CommandAction::StopMultimediaTransmission { call_id },
10677 ))
10678 .await
10679 .unwrap();
10680 phone
10681 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
10682 .await
10683 .unwrap();
10684 let after_duplicate_stop =
10685 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10686 matches!(message, ServerMessage::KeepAliveAck)
10687 })
10688 .await;
10689 assert!(!after_duplicate_stop.iter().any(|message| {
10690 matches!(
10691 message,
10692 ServerMessage::StopMultimediaTransmission(control)
10693 if control.passthrough_party_id
10694 == stopped_start.passthrough_party_id
10695 )
10696 }));
10697
10698 handle
10699 .send_confirmed(Command::new(
10700 device_id.clone(),
10701 CommandAction::StartMultimediaTransmission {
10702 call_id,
10703 descriptor: video_transmit_descriptor(
10704 815,
10705 MediaEndpointAddress {
10706 address: "192.0.2.85".parse().unwrap(),
10707 port: 5090,
10708 },
10709 ),
10710 },
10711 ))
10712 .await
10713 .unwrap();
10714 let final_start =
10715 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10716 matches!(
10717 message,
10718 ServerMessage::StartMultimediaTransmission(start)
10719 if start.conference_id == ConferenceId::new(815)
10720 )
10721 })
10722 .await
10723 .into_iter()
10724 .find_map(|message| match message {
10725 ServerMessage::StartMultimediaTransmission(start)
10726 if start.conference_id == ConferenceId::new(815) =>
10727 {
10728 Some(start)
10729 }
10730 _ => None,
10731 })
10732 .unwrap();
10733 handle
10734 .send_confirmed(Command::new(
10735 device_id,
10736 CommandAction::CloseCall { call_id },
10737 ))
10738 .await
10739 .unwrap();
10740 let close_messages =
10741 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10742 matches!(
10743 message,
10744 ServerMessage::CallState {
10745 state: CallState::OnHook,
10746 ..
10747 }
10748 )
10749 })
10750 .await;
10751 let video_receive_close = close_messages
10752 .iter()
10753 .position(|message| {
10754 matches!(
10755 message,
10756 ServerMessage::CloseMultimediaReceiveChannel(control)
10757 if control.passthrough_party_id
10758 == receive_open.passthrough_party_id
10759 )
10760 })
10761 .unwrap();
10762 let video_transmit_stop = close_messages
10763 .iter()
10764 .position(|message| {
10765 matches!(
10766 message,
10767 ServerMessage::StopMultimediaTransmission(control)
10768 if control.passthrough_party_id
10769 == final_start.passthrough_party_id
10770 )
10771 })
10772 .unwrap();
10773 let audio_close = close_messages
10774 .iter()
10775 .position(|message| {
10776 matches!(
10777 message,
10778 ServerMessage::CloseReceiveChannel(control)
10779 if control.passthrough_party_id.get() == audio_token
10780 )
10781 })
10782 .unwrap();
10783 let on_hook = close_messages
10784 .iter()
10785 .position(|message| {
10786 matches!(
10787 message,
10788 ServerMessage::CallState {
10789 state: CallState::OnHook,
10790 ..
10791 }
10792 )
10793 })
10794 .unwrap();
10795 assert!(
10796 video_receive_close < video_transmit_stop
10797 && video_transmit_stop < audio_close
10798 && audio_close < on_hook
10799 );
10800
10801 handle.shutdown().await.unwrap();
10802 task.await.unwrap().unwrap();
10803 }
10804
10805 #[tokio::test(start_paused = true)]
10806 async fn video_receive_deadline_closes_and_retires_the_exact_generation() {
10807 let device = definition();
10808 let device_id = device.id.clone();
10809 let config = ServerConfig {
10810 bind: "127.0.0.1:0".parse().unwrap(),
10811 advertised_address: Ipv4Addr::LOCALHOST,
10812 ..ServerConfig::default()
10813 };
10814 let (server, handle, mut events, ingress) = Server::with_ingress(config, [device]).unwrap();
10815 let task = tokio::spawn(server.run());
10816 let (server_stream, mut phone) = tokio::io::duplex(8_192);
10817 ingress
10818 .accept(
10819 server_stream,
10820 SocketAddr::from(([127, 0, 0, 1], 40_071)),
10821 SocketAddr::from(([127, 0, 0, 1], 2_000)),
10822 StationTransport::Clear,
10823 )
10824 .await
10825 .unwrap();
10826 let mut decoder = FrameDecoder::new();
10827 let protocol = ProtocolVersion::V22;
10828 let call_id = CallId::new(72);
10829
10830 phone.write_all(®ister_bytes(protocol)).await.unwrap();
10831 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
10832 assert!(matches!(
10833 events.recv().await,
10834 Some(Event::Device(DeviceEvent {
10835 event: DeviceEventKind::Registered(_),
10836 ..
10837 }))
10838 ));
10839 phone
10840 .write_all(&capability_update_bytes(
10841 protocol,
10842 Codec::Pcmu,
10843 Codec::H264,
10844 72,
10845 ))
10846 .await
10847 .unwrap();
10848 assert!(matches!(
10849 events.recv().await,
10850 Some(Event::Device(DeviceEvent {
10851 event: DeviceEventKind::Capabilities { .. },
10852 ..
10853 }))
10854 ));
10855 handle
10856 .send_confirmed(Command::new(
10857 device_id.clone(),
10858 CommandAction::BeginCall {
10859 line_instance: LineInstance::new(1),
10860 call_id,
10861 codec: Codec::Pcmu,
10862 },
10863 ))
10864 .await
10865 .unwrap();
10866 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
10867 handle
10868 .send_confirmed(Command::new(
10869 device_id.clone(),
10870 CommandAction::SetCallState {
10871 call_id,
10872 state: CallState::Connected,
10873 },
10874 ))
10875 .await
10876 .unwrap();
10877 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
10878 handle
10879 .send_confirmed(Command::new(
10880 device_id.clone(),
10881 CommandAction::OpenMultimediaReceiveChannel {
10882 call_id,
10883 descriptor: video_receive_descriptor(
10884 703,
10885 MediaEndpointAddress {
10886 address: "192.0.2.73".parse().unwrap(),
10887 port: 5076,
10888 },
10889 ),
10890 },
10891 ))
10892 .await
10893 .unwrap();
10894 let open = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10895 matches!(message, ServerMessage::OpenMultimediaChannel(_))
10896 })
10897 .await
10898 .into_iter()
10899 .find_map(|message| match message {
10900 ServerMessage::OpenMultimediaChannel(open) => Some(open),
10901 _ => None,
10902 })
10903 .unwrap();
10904
10905 tokio::time::advance(HANDSET_ACKNOWLEDGEMENT_TIMEOUT + Duration::from_millis(100)).await;
10906 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10907 matches!(
10908 message,
10909 ServerMessage::CloseMultimediaReceiveChannel(control)
10910 if control.passthrough_party_id == open.passthrough_party_id
10911 )
10912 })
10913 .await;
10914 assert!(matches!(
10915 events.recv().await,
10916 Some(Event::Device(DeviceEvent {
10917 event: DeviceEventKind::MultimediaReceiveChannelTimedOut {
10918 call_id: actual_call,
10919 codec: Codec::H264,
10920 passthrough_party_id,
10921 },
10922 ..
10923 })) if actual_call == call_id
10924 && passthrough_party_id == open.passthrough_party_id
10925 ));
10926
10927 phone
10928 .write_all(
10929 &ClientMessage::OpenMultimediaReceiveChannelAck(
10930 crate::OpenMultimediaReceiveChannelAck {
10931 status: MediaStatus::Ok,
10932 endpoint: MediaEndpointAddress {
10933 address: "198.51.100.73".parse().unwrap(),
10934 port: 6076,
10935 },
10936 passthrough_party_id: open.passthrough_party_id,
10937 call_reference: open.call_reference,
10938 },
10939 )
10940 .encode(protocol)
10941 .unwrap(),
10942 )
10943 .await
10944 .unwrap();
10945 assert!(
10946 tokio::time::timeout(Duration::from_millis(25), events.recv())
10947 .await
10948 .is_err()
10949 );
10950 phone
10951 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
10952 .await
10953 .unwrap();
10954 read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
10955
10956 handle
10957 .send_confirmed(Command::new(
10958 device_id,
10959 CommandAction::OpenMultimediaReceiveChannel {
10960 call_id,
10961 descriptor: video_receive_descriptor(
10962 704,
10963 MediaEndpointAddress {
10964 address: "192.0.2.74".parse().unwrap(),
10965 port: 5078,
10966 },
10967 ),
10968 },
10969 ))
10970 .await
10971 .unwrap();
10972 let shutdown_open =
10973 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10974 matches!(
10975 message,
10976 ServerMessage::OpenMultimediaChannel(open)
10977 if open.conference_id == ConferenceId::new(704)
10978 )
10979 })
10980 .await
10981 .into_iter()
10982 .find_map(|message| match message {
10983 ServerMessage::OpenMultimediaChannel(open)
10984 if open.conference_id == ConferenceId::new(704) =>
10985 {
10986 Some(open)
10987 }
10988 _ => None,
10989 })
10990 .unwrap();
10991 handle.shutdown().await.unwrap();
10992 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
10993 matches!(
10994 message,
10995 ServerMessage::CloseMultimediaReceiveChannel(control)
10996 if control.passthrough_party_id == shutdown_open.passthrough_party_id
10997 )
10998 })
10999 .await;
11000 task.await.unwrap().unwrap();
11001 }
11002
11003 #[tokio::test(start_paused = true)]
11004 async fn video_transmit_deadline_stops_and_retires_the_exact_generation() {
11005 let device = definition();
11006 let device_id = device.id.clone();
11007 let config = ServerConfig {
11008 bind: "127.0.0.1:0".parse().unwrap(),
11009 advertised_address: Ipv4Addr::LOCALHOST,
11010 ..ServerConfig::default()
11011 };
11012 let (server, handle, mut events, ingress) = Server::with_ingress(config, [device]).unwrap();
11013 let task = tokio::spawn(server.run());
11014 let (server_stream, mut phone) = tokio::io::duplex(8_192);
11015 ingress
11016 .accept(
11017 server_stream,
11018 SocketAddr::from(([127, 0, 0, 1], 40_081)),
11019 SocketAddr::from(([127, 0, 0, 1], 2_000)),
11020 StationTransport::Clear,
11021 )
11022 .await
11023 .unwrap();
11024 let mut decoder = FrameDecoder::new();
11025 let protocol = ProtocolVersion::V22;
11026 let call_id = CallId::new(82);
11027
11028 phone.write_all(®ister_bytes(protocol)).await.unwrap();
11029 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
11030 assert!(matches!(
11031 events.recv().await,
11032 Some(Event::Device(DeviceEvent {
11033 event: DeviceEventKind::Registered(_),
11034 ..
11035 }))
11036 ));
11037 phone
11038 .write_all(&capability_update_bytes(
11039 protocol,
11040 Codec::Pcmu,
11041 Codec::H264,
11042 82,
11043 ))
11044 .await
11045 .unwrap();
11046 assert!(matches!(
11047 events.recv().await,
11048 Some(Event::Device(DeviceEvent {
11049 event: DeviceEventKind::Capabilities { .. },
11050 ..
11051 }))
11052 ));
11053 handle
11054 .send_confirmed(Command::new(
11055 device_id.clone(),
11056 CommandAction::BeginCall {
11057 line_instance: LineInstance::new(1),
11058 call_id,
11059 codec: Codec::Pcmu,
11060 },
11061 ))
11062 .await
11063 .unwrap();
11064 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
11065 handle
11066 .send_confirmed(Command::new(
11067 device_id.clone(),
11068 CommandAction::SetCallState {
11069 call_id,
11070 state: CallState::Connected,
11071 },
11072 ))
11073 .await
11074 .unwrap();
11075 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
11076 handle
11077 .send_confirmed(Command::new(
11078 device_id.clone(),
11079 CommandAction::StartMultimediaTransmission {
11080 call_id,
11081 descriptor: video_transmit_descriptor(
11082 820,
11083 MediaEndpointAddress {
11084 address: "192.0.2.82".parse().unwrap(),
11085 port: 5090,
11086 },
11087 ),
11088 },
11089 ))
11090 .await
11091 .unwrap();
11092 let start = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11093 matches!(message, ServerMessage::StartMultimediaTransmission(_))
11094 })
11095 .await
11096 .into_iter()
11097 .find_map(|message| match message {
11098 ServerMessage::StartMultimediaTransmission(start) => Some(start),
11099 _ => None,
11100 })
11101 .unwrap();
11102
11103 tokio::time::advance(HANDSET_ACKNOWLEDGEMENT_TIMEOUT + Duration::from_millis(100)).await;
11104 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11105 matches!(
11106 message,
11107 ServerMessage::StopMultimediaTransmission(control)
11108 if control.passthrough_party_id == start.passthrough_party_id
11109 )
11110 })
11111 .await;
11112 assert!(matches!(
11113 events.recv().await,
11114 Some(Event::Device(DeviceEvent {
11115 event: DeviceEventKind::MultimediaTransmitTimedOut {
11116 call_id: actual_call,
11117 codec: Codec::H264,
11118 passthrough_party_id,
11119 },
11120 ..
11121 })) if actual_call == call_id
11122 && passthrough_party_id == start.passthrough_party_id
11123 ));
11124 phone
11125 .write_all(
11126 &ClientMessage::StartMultimediaTransmissionAck(
11127 crate::StartMultimediaTransmissionAck {
11128 conference_id: start.conference_id,
11129 passthrough_party_id: start.passthrough_party_id,
11130 call_reference: start.call_reference,
11131 endpoint: MediaEndpointAddress {
11132 address: "198.51.100.82".parse().unwrap(),
11133 port: 6090,
11134 },
11135 status: MediaStatus::Ok,
11136 },
11137 )
11138 .encode(protocol)
11139 .unwrap(),
11140 )
11141 .await
11142 .unwrap();
11143 assert!(
11144 tokio::time::timeout(Duration::from_millis(25), events.recv())
11145 .await
11146 .is_err()
11147 );
11148
11149 handle
11150 .send_confirmed(Command::new(
11151 device_id,
11152 CommandAction::StartMultimediaTransmission {
11153 call_id,
11154 descriptor: video_transmit_descriptor(
11155 821,
11156 MediaEndpointAddress {
11157 address: "192.0.2.83".parse().unwrap(),
11158 port: 5092,
11159 },
11160 ),
11161 },
11162 ))
11163 .await
11164 .unwrap();
11165 let shutdown_start =
11166 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11167 matches!(
11168 message,
11169 ServerMessage::StartMultimediaTransmission(start)
11170 if start.conference_id == ConferenceId::new(821)
11171 )
11172 })
11173 .await
11174 .into_iter()
11175 .find_map(|message| match message {
11176 ServerMessage::StartMultimediaTransmission(start)
11177 if start.conference_id == ConferenceId::new(821) =>
11178 {
11179 Some(start)
11180 }
11181 _ => None,
11182 })
11183 .unwrap();
11184 handle.shutdown().await.unwrap();
11185 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11186 matches!(
11187 message,
11188 ServerMessage::StopMultimediaTransmission(control)
11189 if control.passthrough_party_id
11190 == shutdown_start.passthrough_party_id
11191 )
11192 })
11193 .await;
11194 task.await.unwrap().unwrap();
11195 }
11196
11197 #[test]
11198 fn multicast_admission_requires_a_routable_supported_audio_shape() {
11199 let state = multicast_test_state(ProtocolVersion::V22);
11200 let valid = multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcmu);
11201 assert!(validate_multicast_route(&state, valid, Some(2)).is_ok());
11202
11203 for route in [
11204 MulticastMediaRoute {
11205 address: "192.0.2.1".parse().unwrap(),
11206 ..valid
11207 },
11208 MulticastMediaRoute { port: 0, ..valid },
11209 MulticastMediaRoute {
11210 packet_millis: 0,
11211 ..valid
11212 },
11213 ] {
11214 assert!(matches!(
11215 validate_multicast_route(&state, route, None),
11216 Err(ServerError::InvalidMulticastMedia(_))
11217 ));
11218 }
11219 assert!(matches!(
11220 validate_multicast_route(
11221 &state,
11222 multicast_route("239.1.2.3".parse().unwrap(), Codec::H264),
11223 None,
11224 ),
11225 Err(ServerError::UnsupportedMulticastCodec)
11226 ));
11227 assert!(matches!(
11228 validate_multicast_route(
11229 &state,
11230 multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcma),
11231 None,
11232 ),
11233 Err(ServerError::UnsupportedMulticastCodec)
11234 ));
11235 for requested_frames in [0, 3] {
11236 assert!(matches!(
11237 validate_multicast_route(&state, valid, Some(requested_frames)),
11238 Err(ServerError::InvalidMulticastMedia(_))
11239 ));
11240 }
11241
11242 let legacy = multicast_test_state(ProtocolVersion::V16);
11243 assert!(matches!(
11244 validate_multicast_route(
11245 &legacy,
11246 multicast_route("ff15::1".parse().unwrap(), Codec::Pcmu),
11247 None,
11248 ),
11249 Err(ServerError::InvalidMulticastMedia(_))
11250 ));
11251 assert!(
11252 validate_multicast_route(
11253 &state,
11254 multicast_route("ff15::1".parse().unwrap(), Codec::Pcmu),
11255 None,
11256 )
11257 .is_ok()
11258 );
11259 }
11260
11261 #[test]
11262 fn multicast_transactions_correlate_exactly_and_retire_once_in_wire_order() {
11263 let now = Instant::now();
11264 let mut state = multicast_test_state(ProtocolVersion::V22);
11265 let call = insert_call(&mut state, CallId(10), 1, Codec::Pcmu, CallState::Connected);
11266 let key = MulticastKey {
11267 conference_id: ConferenceId::new(90),
11268 call_id: call.call_id,
11269 };
11270 let receive_request =
11271 MediaRequestIdentity::new(1, MediaRequestToken::new(101).expect("nonzero media token"))
11272 .expect("nonzero generation");
11273 let transmit_request =
11274 MediaRequestIdentity::new(2, MediaRequestToken::new(102).expect("nonzero media token"))
11275 .expect("nonzero generation");
11276 let route = multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcmu);
11277 state.multicast.insert(
11278 key,
11279 MulticastSession {
11280 wire_call_reference: call.wire_reference,
11281 receive: Some(MulticastReceive {
11282 request: receive_request,
11283 route,
11284 state: MulticastReceiveState::AwaitingAcknowledgement { deadline: now },
11285 }),
11286 transmit: Some(MulticastTransmit {
11287 request: transmit_request,
11288 route,
11289 }),
11290 },
11291 );
11292
11293 assert_eq!(
11294 find_multicast_receive_key(&state, call.wire_reference, 100),
11295 None
11296 );
11297 assert_eq!(
11298 find_multicast_receive_key(&state, call.wire_reference + 1, 101),
11299 None
11300 );
11301 assert_eq!(
11302 find_multicast_receive_key(&state, call.wire_reference, 101),
11303 Some(key)
11304 );
11305 assert_eq!(
11306 find_multicast_transmit_key(
11307 &state,
11308 90,
11309 call.wire_reference,
11310 102,
11311 route.address,
11312 route.port + 1,
11313 ),
11314 None
11315 );
11316 assert_eq!(
11317 find_multicast_transmit_key(
11318 &state,
11319 90,
11320 call.wire_reference,
11321 102,
11322 route.address,
11323 route.port,
11324 ),
11325 Some(key)
11326 );
11327
11328 let expired = expire_multicast_reception_acknowledgements(&mut state, now);
11329 assert!(matches!(
11330 expired.as_slice(),
11331 [(
11332 actual_key,
11333 ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
11334 )] if *actual_key == key && passthrough_party_id.get() == 101
11335 ));
11336 assert!(expire_multicast_reception_acknowledgements(&mut state, now).is_empty());
11337
11338 let other_call = insert_call(&mut state, CallId(20), 1, Codec::Pcmu, CallState::Connected);
11339 let other_key = MulticastKey {
11340 conference_id: ConferenceId::new(91),
11341 call_id: other_call.call_id,
11342 };
11343 state.multicast.insert(
11344 other_key,
11345 MulticastSession {
11346 wire_call_reference: other_call.wire_reference,
11347 receive: Some(MulticastReceive {
11348 request: MediaRequestIdentity::new(
11349 3,
11350 MediaRequestToken::new(103).expect("nonzero media token"),
11351 )
11352 .expect("nonzero generation"),
11353 route,
11354 state: MulticastReceiveState::Open,
11355 }),
11356 transmit: None,
11357 },
11358 );
11359
11360 let remaining = take_multicast_stops_for_call(&mut state, call.call_id);
11361 assert!(matches!(
11362 remaining.as_slice(),
11363 [ServerMessage::StopMulticastMediaTransmission { passthrough_party_id, .. }]
11364 if passthrough_party_id.get() == 102
11365 ));
11366 assert!(take_multicast_stops_for_call(&mut state, call.call_id).is_empty());
11367 assert!(state.multicast.contains_key(&other_key));
11368 let shutdown_stops = take_all_multicast_stops(&mut state);
11369 assert!(matches!(
11370 shutdown_stops.as_slice(),
11371 [ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }]
11372 if passthrough_party_id.get() == 103
11373 ));
11374 assert!(take_all_multicast_stops(&mut state).is_empty());
11375 assert!(state.multicast.is_empty());
11376 }
11377
11378 #[tokio::test]
11379 async fn multicast_session_enforces_transaction_identity_order_and_teardown() {
11380 let device = definition();
11381 let device_id = device.id.clone();
11382 let config = ServerConfig {
11383 bind: "127.0.0.1:0".parse().unwrap(),
11384 advertised_address: Ipv4Addr::LOCALHOST,
11385 ..ServerConfig::default()
11386 };
11387 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
11388 let address = server.local_addr().unwrap();
11389 let task = tokio::spawn(server.run());
11390 let mut phone = TcpStream::connect(address).await.unwrap();
11391 let mut decoder = FrameDecoder::new();
11392 let protocol = ProtocolVersion::V22;
11393 let call_id = CallId(41);
11394 let conference_id = ConferenceId::new(900);
11395 let first_route = multicast_route("239.1.2.3".parse().unwrap(), Codec::Pcmu);
11396 let second_route = MulticastMediaRoute {
11397 address: "239.1.2.4".parse().unwrap(),
11398 port: 5006,
11399 ..first_route
11400 };
11401
11402 phone.write_all(®ister_bytes(protocol)).await.unwrap();
11403 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
11404 assert!(matches!(
11405 events.recv().await,
11406 Some(Event::Device(DeviceEvent {
11407 session_generation: _,
11408 event: DeviceEventKind::Registered(_),
11409 ..
11410 }))
11411 ));
11412 phone
11413 .write_all(
11414 &ClientMessage::CapabilitiesResponse(vec![MediaCapability {
11415 codec: Codec::Pcmu,
11416 max_frames_per_packet: 2,
11417 codec_parameters: [0; 8],
11418 }])
11419 .encode(protocol)
11420 .unwrap(),
11421 )
11422 .await
11423 .unwrap();
11424 assert!(matches!(
11425 events.recv().await,
11426 Some(Event::Device(DeviceEvent {
11427 session_generation: _,
11428 event: DeviceEventKind::Capabilities { .. },
11429 ..
11430 }))
11431 ));
11432 handle
11433 .send_confirmed(Command::new(
11434 device_id.clone(),
11435 CommandAction::BeginCall {
11436 line_instance: LineInstance::new(1),
11437 call_id,
11438 codec: Codec::Pcmu,
11439 },
11440 ))
11441 .await
11442 .unwrap();
11443 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11444 matches!(message, ServerMessage::SelectSoftKeys { .. })
11445 })
11446 .await;
11447 let wire_call_reference = messages
11448 .iter()
11449 .find_map(|message| match message {
11450 ServerMessage::CallState {
11451 state: CallState::OffHook,
11452 call_reference,
11453 ..
11454 } => Some(*call_reference),
11455 _ => None,
11456 })
11457 .expect("begin call omitted its wire reference");
11458
11459 for invalid_route in [
11460 MulticastMediaRoute {
11461 address: "192.0.2.1".parse().unwrap(),
11462 ..first_route
11463 },
11464 MulticastMediaRoute {
11465 codec: Codec::Pcma,
11466 ..first_route
11467 },
11468 ] {
11469 assert!(matches!(
11470 handle
11471 .send_confirmed(Command::new(
11472 device_id.clone(),
11473 CommandAction::StartMulticastReception {
11474 conference_id,
11475 call_id,
11476 route: invalid_route,
11477 echo_cancellation: EchoCancellation::On,
11478 g723_bitrate: G723BitRate::Rate5_3,
11479 },
11480 ))
11481 .await,
11482 Err(ServerError::CommandWrite(_))
11483 ));
11484 }
11485 phone
11486 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
11487 .await
11488 .unwrap();
11489 read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
11490
11491 handle
11492 .send_confirmed(Command::new(
11493 device_id.clone(),
11494 CommandAction::StartMulticastReception {
11495 conference_id,
11496 call_id,
11497 route: first_route,
11498 echo_cancellation: EchoCancellation::On,
11499 g723_bitrate: G723BitRate::Rate5_3,
11500 },
11501 ))
11502 .await
11503 .unwrap();
11504 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11505 matches!(message, ServerMessage::StartMulticastMediaReception(_))
11506 })
11507 .await;
11508 let first_receive_token = messages
11509 .iter()
11510 .find_map(|message| match message {
11511 ServerMessage::StartMulticastMediaReception(request) => {
11512 assert_eq!(request.conference_id, conference_id);
11513 assert_eq!(request.call_reference.get(), wire_call_reference);
11514 assert_eq!(request.address, first_route.address);
11515 assert_eq!(request.port, first_route.port);
11516 assert_eq!(request.codec, first_route.codec);
11517 Some(request.passthrough_party_id)
11518 }
11519 _ => None,
11520 })
11521 .expect("multicast reception omitted its request");
11522
11523 let mismatched = ClientMessage::MulticastMediaReceptionAck {
11524 status: MediaStatus::Ok,
11525 passthrough_party_id: first_receive_token,
11526 call_reference: CallReference::new(wire_call_reference + 1),
11527 }
11528 .encode(protocol)
11529 .unwrap();
11530 let exact = ClientMessage::MulticastMediaReceptionAck {
11531 status: MediaStatus::Ok,
11532 passthrough_party_id: first_receive_token,
11533 call_reference: CallReference::new(wire_call_reference),
11534 }
11535 .encode(protocol)
11536 .unwrap();
11537 let mut coalesced_prefix = mismatched;
11538 coalesced_prefix.push(exact[0]);
11539 phone.write_all(&coalesced_prefix).await.unwrap();
11540 assert!(
11541 tokio::time::timeout(Duration::from_millis(25), events.recv())
11542 .await
11543 .is_err(),
11544 "a mismatched acknowledgement completed the transaction"
11545 );
11546 for fragment in exact[1..].chunks(2) {
11547 phone.write_all(fragment).await.unwrap();
11548 }
11549 assert!(matches!(
11550 events.recv().await,
11551 Some(Event::Device(DeviceEvent { session_generation: _,
11552 event: DeviceEventKind::MulticastReceptionStarted {
11553 conference_id: actual_conference,
11554 call_id: actual_call,
11555 route,
11556 },
11557 ..
11558 })) if actual_conference == conference_id && actual_call == call_id && route == first_route
11559 ));
11560 phone.write_all(&exact).await.unwrap();
11561 assert!(
11562 tokio::time::timeout(Duration::from_millis(25), events.recv())
11563 .await
11564 .is_err(),
11565 "a duplicate acknowledgement emitted another event"
11566 );
11567
11568 handle
11569 .send_confirmed(Command::new(
11570 device_id.clone(),
11571 CommandAction::StartMulticastReception {
11572 conference_id,
11573 call_id,
11574 route: second_route,
11575 echo_cancellation: EchoCancellation::Off,
11576 g723_bitrate: G723BitRate::Rate6_3,
11577 },
11578 ))
11579 .await
11580 .unwrap();
11581 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11582 matches!(
11583 message,
11584 ServerMessage::StartMulticastMediaReception(request)
11585 if request.address == second_route.address
11586 )
11587 })
11588 .await;
11589 let stop_index = messages
11590 .iter()
11591 .position(|message| {
11592 matches!(
11593 message,
11594 ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
11595 if *passthrough_party_id == first_receive_token
11596 )
11597 })
11598 .expect("replacement did not stop the previous generation");
11599 let (start_index, second_receive_token) = messages
11600 .iter()
11601 .enumerate()
11602 .find_map(|(index, message)| match message {
11603 ServerMessage::StartMulticastMediaReception(request)
11604 if request.address == second_route.address =>
11605 {
11606 Some((index, request.passthrough_party_id))
11607 }
11608 _ => None,
11609 })
11610 .expect("replacement did not start a fresh generation");
11611 assert!(stop_index < start_index);
11612 assert_ne!(first_receive_token, second_receive_token);
11613
11614 let negative = ClientMessage::MulticastMediaReceptionAck {
11615 status: MediaStatus::OutOfChannels,
11616 passthrough_party_id: second_receive_token,
11617 call_reference: CallReference::new(wire_call_reference),
11618 }
11619 .encode(protocol)
11620 .unwrap();
11621 phone.write_all(&negative).await.unwrap();
11622 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11623 matches!(
11624 message,
11625 ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
11626 if *passthrough_party_id == second_receive_token
11627 )
11628 })
11629 .await;
11630 assert!(matches!(
11631 events.recv().await,
11632 Some(Event::Device(DeviceEvent { session_generation: _,
11633 event: DeviceEventKind::MulticastReceptionFailed {
11634 conference_id: actual_conference,
11635 call_id: actual_call,
11636 status: MediaStatus::OutOfChannels,
11637 },
11638 ..
11639 })) if actual_conference == conference_id && actual_call == call_id
11640 ));
11641 phone.write_all(&negative).await.unwrap();
11642 assert!(
11643 tokio::time::timeout(Duration::from_millis(25), events.recv())
11644 .await
11645 .is_err(),
11646 "a duplicate failure acknowledgement emitted another event"
11647 );
11648
11649 handle
11650 .send_confirmed(Command::new(
11651 device_id.clone(),
11652 CommandAction::StartMulticastTransmission {
11653 conference_id,
11654 call_id,
11655 route: first_route,
11656 precedence: 0,
11657 silence_suppression: SilenceSuppression::Off,
11658 max_frames_per_packet: 2,
11659 g723_bitrate: G723BitRate::Rate5_3,
11660 },
11661 ))
11662 .await
11663 .unwrap();
11664 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11665 matches!(message, ServerMessage::StartMulticastMediaTransmission(_))
11666 })
11667 .await;
11668 let transmit_token = messages
11669 .iter()
11670 .find_map(|message| match message {
11671 ServerMessage::StartMulticastMediaTransmission(request) => {
11672 Some(request.passthrough_party_id.get())
11673 }
11674 _ => None,
11675 })
11676 .expect("multicast transmission omitted its request");
11677 let started_event = events.recv().await;
11678 assert!(matches!(
11679 started_event,
11680 Some(Event::Device(DeviceEvent { session_generation: _,
11681 event: DeviceEventKind::MulticastTransmissionStarted {
11682 conference_id: actual_conference,
11683 call_id: actual_call,
11684 route,
11685 },
11686 ..
11687 })) if actual_conference == conference_id && actual_call == call_id && route == first_route
11688 ));
11689 let mismatch_failure = ClientMessage::MediaTransmissionFailure {
11690 conference_id: conference_id.get(),
11691 passthrough_party_id: transmit_token,
11692 address: first_route.address,
11693 port: first_route.port + 1,
11694 call_reference: wire_call_reference,
11695 status: MediaStatus::UnspecifiedError,
11696 }
11697 .encode(protocol)
11698 .unwrap();
11699 phone.write_all(&mismatch_failure).await.unwrap();
11700 assert!(
11701 tokio::time::timeout(Duration::from_millis(25), events.recv())
11702 .await
11703 .is_err(),
11704 "a mismatched transmission failure retired the transaction"
11705 );
11706 let exact_failure = ClientMessage::MediaTransmissionFailure {
11707 conference_id: conference_id.get(),
11708 passthrough_party_id: transmit_token,
11709 address: first_route.address,
11710 port: first_route.port,
11711 call_reference: wire_call_reference,
11712 status: MediaStatus::UnspecifiedError,
11713 }
11714 .encode(protocol)
11715 .unwrap();
11716 phone.write_all(&exact_failure).await.unwrap();
11717 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11718 matches!(
11719 message,
11720 ServerMessage::StopMulticastMediaTransmission { passthrough_party_id, .. }
11721 if passthrough_party_id.get() == transmit_token
11722 )
11723 })
11724 .await;
11725 let failure_event = events.recv().await;
11726 assert!(
11727 matches!(
11728 failure_event,
11729 Some(Event::Device(DeviceEvent { session_generation: _,
11730 event: DeviceEventKind::MulticastTransmissionFailed {
11731 conference_id: actual_conference,
11732 call_id: actual_call,
11733 status: MediaStatus::UnspecifiedError,
11734 ..
11735 },
11736 ..
11737 })) if actual_conference == conference_id && actual_call == call_id
11738 ),
11739 "unexpected multicast transmission failure event: {failure_event:?}"
11740 );
11741 phone.write_all(&exact_failure).await.unwrap();
11742 assert!(
11743 tokio::time::timeout(Duration::from_millis(25), events.recv())
11744 .await
11745 .is_err(),
11746 "a duplicate transmission failure emitted another event"
11747 );
11748
11749 phone
11750 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
11751 .await
11752 .unwrap();
11753 read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
11754
11755 handle
11756 .send_confirmed(Command::new(
11757 device_id.clone(),
11758 CommandAction::StartMulticastReception {
11759 conference_id,
11760 call_id,
11761 route: first_route,
11762 echo_cancellation: EchoCancellation::On,
11763 g723_bitrate: G723BitRate::Rate5_3,
11764 },
11765 ))
11766 .await
11767 .unwrap();
11768 read_until_message(
11769 &mut phone,
11770 &mut decoder,
11771 id::START_MULTICAST_MEDIA_RECEPTION,
11772 )
11773 .await;
11774 handle
11775 .send_confirmed(Command::new(
11776 device_id.clone(),
11777 CommandAction::StartMulticastTransmission {
11778 conference_id,
11779 call_id,
11780 route: first_route,
11781 precedence: 0,
11782 silence_suppression: SilenceSuppression::Off,
11783 max_frames_per_packet: 2,
11784 g723_bitrate: G723BitRate::Rate5_3,
11785 },
11786 ))
11787 .await
11788 .unwrap();
11789 read_until_message(
11790 &mut phone,
11791 &mut decoder,
11792 id::START_MULTICAST_MEDIA_TRANSMISSION,
11793 )
11794 .await;
11795 assert!(matches!(
11796 events.recv().await,
11797 Some(Event::Device(DeviceEvent {
11798 session_generation: _,
11799 event: DeviceEventKind::MulticastTransmissionStarted { .. },
11800 ..
11801 }))
11802 ));
11803 handle
11804 .send_confirmed(Command::new(
11805 device_id.clone(),
11806 CommandAction::CloseCall { call_id },
11807 ))
11808 .await
11809 .unwrap();
11810 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11811 matches!(
11812 message,
11813 ServerMessage::CallState {
11814 state: CallState::OnHook,
11815 ..
11816 }
11817 )
11818 })
11819 .await;
11820 let receive_stop = messages
11821 .iter()
11822 .position(|message| {
11823 matches!(message, ServerMessage::StopMulticastMediaReception { .. })
11824 })
11825 .expect("call close omitted multicast reception stop");
11826 let transmit_stop = messages
11827 .iter()
11828 .position(|message| {
11829 matches!(
11830 message,
11831 ServerMessage::StopMulticastMediaTransmission { .. }
11832 )
11833 })
11834 .expect("call close omitted multicast transmission stop");
11835 let on_hook = messages
11836 .iter()
11837 .position(|message| {
11838 matches!(
11839 message,
11840 ServerMessage::CallState {
11841 state: CallState::OnHook,
11842 ..
11843 }
11844 )
11845 })
11846 .unwrap();
11847 assert!(receive_stop < transmit_stop && transmit_stop < on_hook);
11848
11849 let disconnect_call = CallId(42);
11850 handle
11851 .send_confirmed(Command::new(
11852 device_id.clone(),
11853 CommandAction::BeginCall {
11854 line_instance: LineInstance::new(1),
11855 call_id: disconnect_call,
11856 codec: Codec::Pcmu,
11857 },
11858 ))
11859 .await
11860 .unwrap();
11861 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
11862 for action in [
11863 CommandAction::StartMulticastReception {
11864 conference_id,
11865 call_id: disconnect_call,
11866 route: first_route,
11867 echo_cancellation: EchoCancellation::On,
11868 g723_bitrate: G723BitRate::Rate5_3,
11869 },
11870 CommandAction::StartMulticastTransmission {
11871 conference_id,
11872 call_id: disconnect_call,
11873 route: first_route,
11874 precedence: 0,
11875 silence_suppression: SilenceSuppression::Off,
11876 max_frames_per_packet: 2,
11877 g723_bitrate: G723BitRate::Rate5_3,
11878 },
11879 ] {
11880 handle
11881 .send_confirmed(Command::new(device_id.clone(), action))
11882 .await
11883 .unwrap();
11884 }
11885 read_until_message(
11886 &mut phone,
11887 &mut decoder,
11888 id::START_MULTICAST_MEDIA_TRANSMISSION,
11889 )
11890 .await;
11891 assert!(matches!(
11892 events.recv().await,
11893 Some(Event::Device(DeviceEvent { session_generation: _,
11894 event: DeviceEventKind::MulticastTransmissionStarted {
11895 call_id: actual_call,
11896 ..
11897 },
11898 ..
11899 })) if actual_call == disconnect_call
11900 ));
11901
11902 let mut replacement = definition();
11903 replacement.description = "replacement".into();
11904 handle.reconfigure([replacement]).await.unwrap();
11905 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
11906 matches!(
11907 message,
11908 ServerMessage::StopMulticastMediaTransmission { .. }
11909 )
11910 })
11911 .await;
11912 let receive_stops = messages
11913 .iter()
11914 .enumerate()
11915 .filter_map(|(index, message)| {
11916 matches!(message, ServerMessage::StopMulticastMediaReception { .. })
11917 .then_some(index)
11918 })
11919 .collect::<Vec<_>>();
11920 let transmit_stops = messages
11921 .iter()
11922 .enumerate()
11923 .filter_map(|(index, message)| {
11924 matches!(
11925 message,
11926 ServerMessage::StopMulticastMediaTransmission { .. }
11927 )
11928 .then_some(index)
11929 })
11930 .collect::<Vec<_>>();
11931 assert_eq!(receive_stops.len(), 1);
11932 assert_eq!(transmit_stops.len(), 1);
11933 assert!(receive_stops[0] < transmit_stops[0]);
11934 assert!(matches!(
11935 events.recv().await,
11936 Some(Event::Device(DeviceEvent {
11937 session_generation: _,
11938 event: DeviceEventKind::Disconnected {},
11939 ..
11940 }))
11941 ));
11942
11943 handle.shutdown().await.unwrap();
11944 task.await.unwrap().unwrap();
11945 }
11946
11947 #[tokio::test(start_paused = true)]
11948 async fn multicast_receive_deadline_stops_and_retires_the_pending_generation() {
11949 let device = definition();
11950 let device_id = device.id.clone();
11951 let config = ServerConfig {
11952 bind: "127.0.0.1:0".parse().unwrap(),
11953 advertised_address: Ipv4Addr::LOCALHOST,
11954 ..ServerConfig::default()
11955 };
11956 let (server, handle, mut events, ingress) = Server::with_ingress(config, [device]).unwrap();
11957 let task = tokio::spawn(server.run());
11958 let (server_stream, mut phone) = tokio::io::duplex(8_192);
11959 ingress
11960 .accept(
11961 server_stream,
11962 SocketAddr::from(([127, 0, 0, 1], 40_000)),
11963 SocketAddr::from(([127, 0, 0, 1], 2_000)),
11964 StationTransport::Clear,
11965 )
11966 .await
11967 .unwrap();
11968 let mut decoder = FrameDecoder::new();
11969 let protocol = ProtocolVersion::V22;
11970 let call_id = CallId(43);
11971 let conference_id = ConferenceId::new(901);
11972 let route = multicast_route("239.1.2.5".parse().unwrap(), Codec::Pcmu);
11973
11974 phone.write_all(®ister_bytes(protocol)).await.unwrap();
11975 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
11976 assert!(matches!(
11977 events.recv().await,
11978 Some(Event::Device(DeviceEvent {
11979 session_generation: _,
11980 event: DeviceEventKind::Registered(_),
11981 ..
11982 }))
11983 ));
11984 phone
11985 .write_all(
11986 &ClientMessage::CapabilitiesResponse(vec![MediaCapability {
11987 codec: Codec::Pcmu,
11988 max_frames_per_packet: 1,
11989 codec_parameters: [0; 8],
11990 }])
11991 .encode(protocol)
11992 .unwrap(),
11993 )
11994 .await
11995 .unwrap();
11996 assert!(matches!(
11997 events.recv().await,
11998 Some(Event::Device(DeviceEvent {
11999 session_generation: _,
12000 event: DeviceEventKind::Capabilities { .. },
12001 ..
12002 }))
12003 ));
12004 handle
12005 .send_confirmed(Command::new(
12006 device_id.clone(),
12007 CommandAction::BeginCall {
12008 line_instance: LineInstance::new(1),
12009 call_id,
12010 codec: Codec::Pcmu,
12011 },
12012 ))
12013 .await
12014 .unwrap();
12015 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
12016 handle
12017 .send_confirmed(Command::new(
12018 device_id,
12019 CommandAction::StartMulticastReception {
12020 conference_id,
12021 call_id,
12022 route,
12023 echo_cancellation: EchoCancellation::On,
12024 g723_bitrate: G723BitRate::Rate5_3,
12025 },
12026 ))
12027 .await
12028 .unwrap();
12029 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
12030 matches!(message, ServerMessage::StartMulticastMediaReception(_))
12031 })
12032 .await;
12033 let request = messages
12034 .iter()
12035 .find_map(|message| match message {
12036 ServerMessage::StartMulticastMediaReception(request) => Some(request.clone()),
12037 _ => None,
12038 })
12039 .unwrap();
12040
12041 tokio::time::advance(HANDSET_ACKNOWLEDGEMENT_TIMEOUT + Duration::from_millis(100)).await;
12042 read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
12043 matches!(
12044 message,
12045 ServerMessage::StopMulticastMediaReception { passthrough_party_id, .. }
12046 if *passthrough_party_id == request.passthrough_party_id
12047 )
12048 })
12049 .await;
12050 assert!(matches!(
12051 events.recv().await,
12052 Some(Event::Device(DeviceEvent { session_generation: _,
12053 event: DeviceEventKind::MulticastReceptionTimedOut {
12054 conference_id: actual_conference,
12055 call_id: actual_call,
12056 },
12057 ..
12058 })) if actual_conference == conference_id && actual_call == call_id
12059 ));
12060 phone
12061 .write_all(
12062 &ClientMessage::MulticastMediaReceptionAck {
12063 status: MediaStatus::Ok,
12064 passthrough_party_id: request.passthrough_party_id,
12065 call_reference: request.call_reference,
12066 }
12067 .encode(protocol)
12068 .unwrap(),
12069 )
12070 .await
12071 .unwrap();
12072 assert!(
12073 tokio::time::timeout(Duration::from_millis(25), events.recv())
12074 .await
12075 .is_err(),
12076 "a late acknowledgement resurrected the expired generation"
12077 );
12078 phone
12079 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
12080 .await
12081 .unwrap();
12082 read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
12083
12084 handle.shutdown().await.unwrap();
12085 task.await.unwrap().unwrap();
12086 }
12087
12088 fn mixed_definition() -> DeviceDefinition {
12089 let mut device = definition();
12090 device.buttons.extend([
12091 ButtonDefinition::SpeedDial(SpeedDialDefinition {
12092 instance: 1,
12093 number: "2001".into(),
12094 display_name: "Reception".into(),
12095 }),
12096 ButtonDefinition::Feature(FeatureDefinition {
12097 instance: 1,
12098 label: "DND".into(),
12099 feature: ButtonType::DoNotDisturb,
12100 }),
12101 ButtonDefinition::Service(ServiceDefinition {
12102 instance: 1,
12103 label: "Directory".into(),
12104 url: "http://services.invalid/directory".into(),
12105 }),
12106 ButtonDefinition::Unused,
12107 ButtonDefinition::BlfSpeedDial(BlfSpeedDialDefinition {
12108 instance: 2,
12109 number: "2002".into(),
12110 display_name: "Warehouse".into(),
12111 hint: "2002@internal".into(),
12112 }),
12113 ]);
12114 device
12115 }
12116
12117 fn profile_with(mode: KeyMode, actions: Vec<SoftKey>) -> SoftKeyProfile {
12118 let default = SoftKeyProfile::default();
12119 SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|candidate| {
12120 if candidate == mode {
12121 (candidate, actions.clone())
12122 } else {
12123 (candidate, default.actions(candidate).to_vec())
12124 }
12125 }))
12126 .unwrap()
12127 }
12128
12129 fn session_call(call_id: u64) -> SessionCall {
12130 SessionCall {
12131 call_id: CallId(call_id),
12132 wire_reference: call_id as u32,
12133 line_instance: 1,
12134 media: CallMedia::new(Codec::Pcmu),
12135 video_receive: VideoReceive::default(),
12136 video_transmit: VideoTransmit::default(),
12137 state: CallState::Connected,
12138 history_disposition: CallHistoryDisposition::Placed,
12139 dialed_number: String::new(),
12140 statistics_directory_number: String::new(),
12141 transfer_role: None,
12142 }
12143 }
12144
12145 #[test]
12146 fn every_call_state_and_soft_key_has_an_explicit_availability_result() {
12147 let expected_modes = [
12148 (CallState::OffHook, KeyMode::OffHook),
12149 (CallState::OnHook, KeyMode::OnHook),
12150 (CallState::RingOut, KeyMode::RingOut),
12151 (CallState::RingIn, KeyMode::RingIn),
12152 (CallState::Connected, KeyMode::Connected),
12153 (CallState::Busy, KeyMode::OffHook),
12154 (CallState::Congestion, KeyMode::OffHook),
12155 (CallState::Hold, KeyMode::OnHold),
12156 (CallState::CallWaiting, KeyMode::RingIn),
12157 (CallState::Transfer, KeyMode::ConnectedTransfer),
12158 (CallState::Park, KeyMode::OnHook),
12159 (CallState::Proceed, KeyMode::RingOut),
12160 (CallState::RemoteMultiline, KeyMode::OnHookStealable),
12161 (CallState::InvalidNumber, KeyMode::OffHook),
12162 (CallState::HoldYellow, KeyMode::OnHold),
12163 (CallState::IntercomOneWay, KeyMode::OffHook),
12164 (CallState::HoldRed, KeyMode::OnHold),
12165 ];
12166 assert_eq!(CallState::ALL_KNOWN.len(), expected_modes.len());
12167 assert_eq!(
12168 CallState::ALL_KNOWN,
12169 expected_modes
12170 .iter()
12171 .map(|(state, _)| *state)
12172 .collect::<Vec<_>>()
12173 );
12174 assert_eq!(
12175 key_mode_for_call_state(CallState::Unknown(99)),
12176 KeyMode::OnHook
12177 );
12178
12179 let profile = SoftKeyProfile::built_in();
12180 for (state, expected_mode) in expected_modes {
12181 let mode = key_mode_for_call_state(state);
12182 assert_eq!(mode, expected_mode, "unexpected key mode for {state:?}");
12183 let expected_actions: &[SoftKey] = match mode {
12184 KeyMode::OnHook => &[SoftKey::NewCall],
12185 KeyMode::Connected | KeyMode::ConnectedTransfer => {
12186 &[SoftKey::Hold, SoftKey::EndCall, SoftKey::Transfer]
12187 }
12188 KeyMode::OnHold | KeyMode::OffHookFeature | KeyMode::HoldConference => {
12189 &[SoftKey::Resume, SoftKey::NewCall, SoftKey::EndCall]
12190 }
12191 KeyMode::RingIn => &[SoftKey::Answer, SoftKey::EndCall],
12192 KeyMode::OffHook | KeyMode::RingOut => &[SoftKey::EndCall],
12193 KeyMode::DigitsFollowing => &[SoftKey::Backspace, SoftKey::EndCall, SoftKey::Dial],
12194 KeyMode::ConnectedConference => &[SoftKey::Hold, SoftKey::EndCall],
12195 KeyMode::OnHookStealable => &[SoftKey::Intercept, SoftKey::NewCall],
12196 KeyMode::InUseHint | KeyMode::Empty | KeyMode::Unknown(_) => &[],
12197 };
12198 for &soft_key in SoftKey::ALL_KNOWN {
12199 assert_eq!(
12200 profile.allows(mode, soft_key),
12201 expected_actions.contains(&soft_key),
12202 "state={state:?} mode={mode:?} soft_key={soft_key:?}"
12203 );
12204 }
12205 }
12206 }
12207
12208 #[test]
12209 fn blf_updates_map_every_state_to_icon_and_lamp() {
12210 let cases = [
12211 (BlfState::Idle, BusyLampFieldState::Idle, LampMode::Off),
12212 (
12213 BlfState::Ringing,
12214 BusyLampFieldState::Alerting,
12215 LampMode::Blink,
12216 ),
12217 (BlfState::Busy, BusyLampFieldState::InUse, LampMode::On),
12218 (BlfState::Held, BusyLampFieldState::InUse, LampMode::Hold),
12219 (
12220 BlfState::Unavailable,
12221 BusyLampFieldState::UnknownState,
12222 LampMode::Flash,
12223 ),
12224 (
12225 BlfState::Unknown,
12226 BusyLampFieldState::UnknownState,
12227 LampMode::Wink,
12228 ),
12229 ];
12230
12231 for (state, expected_icon, expected_lamp) in cases {
12232 let [speed_dial, feature, lamp] =
12233 blf_status_messages(7, "4100", "Support", state, None);
12234 assert!(matches!(
12235 speed_dial,
12236 ServerMessage::SpeedDialStatus {
12237 instance: 7,
12238 ref number,
12239 ref display_name,
12240 } if number == "4100" && display_name == "Support"
12241 ));
12242 assert!(matches!(
12243 feature,
12244 ServerMessage::FeatureStatus {
12245 instance: 7,
12246 button_type: ButtonType::BlfSpeedDial,
12247 ref label,
12248 state,
12249 } if label == "Support" && state == expected_icon.wire_value()
12250 ));
12251 assert!(matches!(
12252 lamp,
12253 ServerMessage::SetLamp {
12254 stimulus: ButtonType::BlfSpeedDial,
12255 instance: 7,
12256 mode,
12257 } if mode == expected_lamp
12258 ));
12259 }
12260 }
12261
12262 #[test]
12263 fn hinted_ringing_policy_adds_only_a_ringing_notification() {
12264 let caller = BlfCallerInfo {
12265 name: "Taylor".into(),
12266 number: "5550100".into(),
12267 };
12268 let mut disabled = definition();
12269 assert_eq!(
12270 hinted_ringing_notification(&disabled, "Dispatch", Some(&caller), BlfState::Ringing,),
12271 None
12272 );
12273
12274 disabled.ui.hinted_ringing_notification = true;
12275 assert_eq!(
12276 hinted_ringing_notification(&disabled, "Dispatch", Some(&caller), BlfState::Ringing,),
12277 Some(HandsetStatusMessage::Display {
12278 text: "Dispatch is ringing: Taylor (5550100)".into(),
12279 timeout_seconds: 5,
12280 priority: None,
12281 })
12282 );
12283 for state in [
12284 BlfState::Idle,
12285 BlfState::Busy,
12286 BlfState::Held,
12287 BlfState::Unavailable,
12288 BlfState::Unknown,
12289 ] {
12290 assert_eq!(
12291 hinted_ringing_notification(&disabled, "Dispatch", Some(&caller), state),
12292 None,
12293 "non-ringing BLF state {state:?} must not be replaced by a notification"
12294 );
12295 assert_eq!(
12296 blf_status_messages(7, "4100", "Dispatch", state, Some(&caller)).len(),
12297 3,
12298 "ordinary BLF projection must remain intact"
12299 );
12300 }
12301 }
12302
12303 #[test]
12304 fn last_number_uses_the_configured_terminator_recording_policy() {
12305 let without_terminator = ServerConfig {
12306 dial_terminator: Digit::Star,
12307 record_dial_terminator: false,
12308 ..ServerConfig::default()
12309 };
12310 let with_terminator = ServerConfig {
12311 record_dial_terminator: true,
12312 ..without_terminator.clone()
12313 };
12314
12315 assert_eq!(
12316 normalized_last_number(" 5551212* ", &without_terminator),
12317 Some("5551212".into())
12318 );
12319 assert_eq!(
12320 normalized_last_number(" 5551212* ", &with_terminator),
12321 Some("5551212*".into())
12322 );
12323 assert_eq!(
12324 normalized_last_number("5551212#", &without_terminator),
12325 Some("5551212#".into()),
12326 "non-terminator DTMF must remain part of the remembered number"
12327 );
12328 assert_eq!(normalized_last_number("***", &without_terminator), None);
12329 }
12330
12331 #[tokio::test]
12332 async fn invalid_server_dial_terminator_is_rejected_before_binding() {
12333 let result = Server::bind(
12334 ServerConfig {
12335 dial_terminator: Digit::Unknown(99),
12336 ..ServerConfig::default()
12337 },
12338 [definition()],
12339 )
12340 .await;
12341 assert!(matches!(result, Err(ServerError::InvalidConfig(_))));
12342 }
12343
12344 #[test]
12345 fn invalid_server_signaling_qos_is_rejected_for_external_ingress() {
12346 let result = Server::with_ingress(
12347 ServerConfig {
12348 signaling_qos: SignalingQos::new(64, 0),
12349 ..ServerConfig::default()
12350 },
12351 [definition()],
12352 );
12353
12354 assert!(
12355 matches!(result, Err(ServerError::InvalidConfig(message)) if message.contains("DSCP 64"))
12356 );
12357 }
12358
12359 #[test]
12360 fn invalid_failover_policy_is_rejected_before_ingress_starts() {
12361 let route = |priority| SignalingServerRoute {
12362 priority,
12363 name: format!("node-{priority}"),
12364 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, priority)),
12365 clear_port: NonZeroU16::new(2000),
12366 secure_port: None,
12367 };
12368 let invalid = [
12369 ServerConfig {
12370 advertised_address: Ipv4Addr::UNSPECIFIED,
12371 ..ServerConfig::default()
12372 },
12373 ServerConfig {
12374 secondary_keepalive_seconds: 4,
12375 ..ServerConfig::default()
12376 },
12377 ServerConfig {
12378 registration_tokens: RegistrationTokenPolicy {
12379 backoff: Duration::from_secs(29),
12380 ..RegistrationTokenPolicy::default()
12381 },
12382 ..ServerConfig::default()
12383 },
12384 ServerConfig {
12385 signaling_servers: vec![route(1), route(1)],
12386 ..ServerConfig::default()
12387 },
12388 ServerConfig {
12389 signaling_servers: vec![route(2)],
12390 ..ServerConfig::default()
12391 },
12392 ServerConfig {
12393 signaling_servers: (1..=6).map(route).collect(),
12394 ..ServerConfig::default()
12395 },
12396 ];
12397
12398 for config in invalid {
12399 assert!(matches!(
12400 Server::with_ingress(config, [definition()]),
12401 Err(ServerError::InvalidConfig(_))
12402 ));
12403 }
12404 }
12405
12406 #[test]
12407 fn blf_update_only_displays_explicitly_permitted_caller_information() {
12408 let [_, without_caller, _] =
12409 blf_status_messages(2, "4200", "Dispatch", BlfState::Ringing, None);
12410 assert!(matches!(
12411 without_caller,
12412 ServerMessage::FeatureStatus { label, .. } if label == "Dispatch"
12413 ));
12414
12415 let caller = BlfCallerInfo {
12416 name: "Taylor".into(),
12417 number: "5550100".into(),
12418 };
12419 let [_, with_caller, _] =
12420 blf_status_messages(2, "4200", "Dispatch", BlfState::Ringing, Some(&caller));
12421 assert!(matches!(
12422 with_caller,
12423 ServerMessage::FeatureStatus { label, .. }
12424 if label == "Dispatch: Taylor (5550100)"
12425 ));
12426 }
12427
12428 #[test]
12429 fn button_template_uses_ordered_semantic_device_buttons() {
12430 let device = mixed_definition();
12431
12432 assert_eq!(
12433 button_template(&device),
12434 vec![
12435 ButtonTemplateEntry {
12436 instance: 1,
12437 button_type: ButtonType::Line,
12438 },
12439 ButtonTemplateEntry {
12440 instance: 1,
12441 button_type: ButtonType::SpeedDial,
12442 },
12443 ButtonTemplateEntry {
12444 instance: 1,
12445 button_type: ButtonType::DoNotDisturb,
12446 },
12447 ButtonTemplateEntry {
12448 instance: 1,
12449 button_type: ButtonType::ServiceUrl,
12450 },
12451 ButtonTemplateEntry {
12452 instance: 0,
12453 button_type: ButtonType::Unused,
12454 },
12455 ButtonTemplateEntry {
12456 instance: 2,
12457 button_type: ButtonType::BlfSpeedDial,
12458 },
12459 ]
12460 );
12461 }
12462
12463 #[test]
12464 fn expansion_module_reserves_exact_model_capacity_and_places_configured_keys() {
12465 let mut device = definition();
12466 device.buttons.extend([
12467 ButtonDefinition::AddonModule(crate::types::AddonModuleDefinition {
12468 slot: 1,
12469 device_type: DeviceType::CiscoAddon7914,
12470 }),
12471 ButtonDefinition::SpeedDial(SpeedDialDefinition {
12472 instance: 1,
12473 number: "2001".into(),
12474 display_name: "Reception".into(),
12475 }),
12476 ButtonDefinition::Feature(FeatureDefinition {
12477 instance: 1,
12478 label: "DND".into(),
12479 feature: ButtonType::DoNotDisturb,
12480 }),
12481 ]);
12482 device.validate().unwrap();
12483
12484 let layout = button_template(&device);
12485 assert_eq!(layout.len(), 15, "one base key plus fourteen sidecar keys");
12486 assert_eq!(
12487 &layout[..3],
12488 [
12489 ButtonTemplateEntry {
12490 instance: 1,
12491 button_type: ButtonType::Line,
12492 },
12493 ButtonTemplateEntry {
12494 instance: 1,
12495 button_type: ButtonType::SpeedDial,
12496 },
12497 ButtonTemplateEntry {
12498 instance: 1,
12499 button_type: ButtonType::DoNotDisturb,
12500 },
12501 ]
12502 );
12503 assert!(
12504 layout[3..]
12505 .iter()
12506 .all(|button| { button.instance == 0 && button.button_type == ButtonType::Unused })
12507 );
12508
12509 let mut over_capacity = definition();
12510 over_capacity.buttons.push(ButtonDefinition::AddonModule(
12511 crate::types::AddonModuleDefinition {
12512 slot: 1,
12513 device_type: DeviceType::AddonSpa500s,
12514 },
12515 ));
12516 over_capacity
12517 .buttons
12518 .extend(std::iter::repeat_n(ButtonDefinition::Unused, 33));
12519 assert!(matches!(
12520 over_capacity.validate(),
12521 Err(CodecError::InvalidDefinition(message))
12522 if message.contains("more buttons than its addon module provides")
12523 ));
12524 }
12525
12526 #[test]
12527 fn static_button_statuses_use_typed_instances_and_safe_unknowns() {
12528 let device = mixed_definition();
12529
12530 assert_eq!(
12531 speed_dial_status(&device, 1),
12532 ServerMessage::SpeedDialStatus {
12533 instance: 1,
12534 number: "2001".into(),
12535 display_name: "Reception".into(),
12536 }
12537 );
12538 assert_eq!(
12539 speed_dial_status(&device, 99),
12540 ServerMessage::SpeedDialStatus {
12541 instance: 99,
12542 number: String::new(),
12543 display_name: String::new(),
12544 }
12545 );
12546 assert_eq!(
12547 feature_status(&device, 1, 0),
12548 Some(ServerMessage::FeatureStatus {
12549 instance: 1,
12550 button_type: ButtonType::DoNotDisturb,
12551 label: "DND".into(),
12552 state: 0,
12553 })
12554 );
12555 assert_eq!(feature_status(&device, 99, 0), None);
12556 assert_eq!(
12557 speed_dial_status(&device, 2),
12558 ServerMessage::SpeedDialStatus {
12559 instance: 2,
12560 number: "2002".into(),
12561 display_name: "Warehouse".into(),
12562 }
12563 );
12564 assert_eq!(
12565 feature_status(&device, 2, 1),
12566 Some(ServerMessage::FeatureStatus {
12567 instance: 2,
12568 button_type: ButtonType::BlfSpeedDial,
12569 label: "Warehouse".into(),
12570 state: BusyLampFieldState::UnknownState.wire_value(),
12571 })
12572 );
12573 assert_eq!(
12574 service_url_status(&device, 1),
12575 Some(ServerMessage::ServiceUrlStatus {
12576 index: 1,
12577 url: "http://services.invalid/directory".into(),
12578 label: "Directory".into(),
12579 extension_text: String::new(),
12580 })
12581 );
12582 assert_eq!(service_url_status(&device, 99), None);
12583 }
12584
12585 #[test]
12586 fn do_not_disturb_status_preserves_exact_mode_and_button_behavior() {
12587 let device = mixed_definition();
12588
12589 for (mode, state, lamp) in [
12590 (DoNotDisturbMode::Off, 0x010000, LampMode::Off),
12591 (DoNotDisturbMode::Reject, 0x020202, LampMode::On),
12592 (DoNotDisturbMode::Silent, 0x030302, LampMode::Blink),
12593 ] {
12594 assert_eq!(
12595 do_not_disturb_state_messages(
12596 &device,
12597 1,
12598 mode,
12599 DoNotDisturbButtonMode::Cycle,
12600 ProtocolVersion::V22,
12601 ),
12602 Some([
12603 ServerMessage::FeatureStatus {
12604 instance: 1,
12605 button_type: ButtonType::MultiblinkFeature,
12606 label: "DND".into(),
12607 state,
12608 },
12609 ServerMessage::SetLamp {
12610 stimulus: ButtonType::DoNotDisturb,
12611 instance: 1,
12612 mode: lamp,
12613 },
12614 ])
12615 );
12616 }
12617
12618 for (button_mode, mode, enabled, lamp) in [
12619 (
12620 DoNotDisturbButtonMode::Silent,
12621 DoNotDisturbMode::Silent,
12622 1,
12623 LampMode::Blink,
12624 ),
12625 (
12626 DoNotDisturbButtonMode::Silent,
12627 DoNotDisturbMode::Reject,
12628 0,
12629 LampMode::Off,
12630 ),
12631 (
12632 DoNotDisturbButtonMode::Reject,
12633 DoNotDisturbMode::Reject,
12634 1,
12635 LampMode::On,
12636 ),
12637 (
12638 DoNotDisturbButtonMode::Reject,
12639 DoNotDisturbMode::Silent,
12640 0,
12641 LampMode::Off,
12642 ),
12643 ] {
12644 let [feature, lamp_message] =
12645 do_not_disturb_state_messages(&device, 1, mode, button_mode, ProtocolVersion::V22)
12646 .unwrap();
12647 assert!(matches!(
12648 feature,
12649 ServerMessage::FeatureStatus {
12650 button_type: ButtonType::DoNotDisturb,
12651 state,
12652 ..
12653 } if state == enabled
12654 ));
12655 assert!(matches!(
12656 lamp_message,
12657 ServerMessage::SetLamp { mode, .. } if mode == lamp
12658 ));
12659 }
12660
12661 let [legacy_feature, legacy_lamp] = do_not_disturb_state_messages(
12662 &device,
12663 1,
12664 DoNotDisturbMode::Silent,
12665 DoNotDisturbButtonMode::Cycle,
12666 ProtocolVersion::V15,
12667 )
12668 .unwrap();
12669 assert!(matches!(
12670 legacy_feature,
12671 ServerMessage::FeatureStatus {
12672 button_type: ButtonType::DoNotDisturb,
12673 state: 1,
12674 ..
12675 }
12676 ));
12677 assert!(matches!(
12678 legacy_lamp,
12679 ServerMessage::SetLamp {
12680 mode: LampMode::Blink,
12681 ..
12682 }
12683 ));
12684
12685 assert!(
12686 do_not_disturb_state_messages(
12687 &device,
12688 99,
12689 DoNotDisturbMode::Reject,
12690 DoNotDisturbButtonMode::Cycle,
12691 ProtocolVersion::V22,
12692 )
12693 .is_none()
12694 );
12695 }
12696
12697 #[test]
12698 fn shared_line_appearances_keep_distinct_instances_and_labels() {
12699 let logical_line = LineDefinition {
12700 number: "4100".into(),
12701 display_name: "Operations".into(),
12702 };
12703 let mut primary = LineAppearance::new(1, logical_line.clone());
12704 primary.label = Some("Operations primary".into());
12705 let mut shared = LineAppearance::new(2, logical_line);
12706 shared.label = Some("Operations shared".into());
12707 let device = DeviceDefinition {
12708 id: DeviceId::new("SEP00AABBCCDDEE").unwrap(),
12709 description: "Shared line phone".into(),
12710 transport: StationTransportRequirement::Either,
12711 signaling_qos: None,
12712 buttons: vec![
12713 ButtonDefinition::Line(primary),
12714 ButtonDefinition::Line(shared),
12715 ],
12716 soft_keys: SoftKeyProfile::default(),
12717 ui: Default::default(),
12718 };
12719
12720 device.validate().unwrap();
12721 assert_eq!(
12722 line_status(&device, 1),
12723 Some(ServerMessage::LineStatus {
12724 instance: 1,
12725 number: "4100".into(),
12726 display_name: "Operations primary".into(),
12727 })
12728 );
12729 assert_eq!(
12730 line_status(&device, 2),
12731 Some(ServerMessage::LineStatus {
12732 instance: 2,
12733 number: "4100".into(),
12734 display_name: "Operations shared".into(),
12735 })
12736 );
12737 assert_eq!(line_status(&device, 3), None);
12738 }
12739
12740 #[tokio::test]
12741 async fn registered_phone_receives_its_mixed_button_template() {
12742 let device = mixed_definition();
12743 let expected = button_template(&device);
12744 let config = ServerConfig {
12745 bind: "127.0.0.1:0".parse().unwrap(),
12746 advertised_address: Ipv4Addr::LOCALHOST,
12747 ..ServerConfig::default()
12748 };
12749 let (server, handle, _events) = Server::bind(config, [device]).await.unwrap();
12750 let address = server.local_addr().unwrap();
12751 let task = tokio::spawn(server.run());
12752 let mut phone = TcpStream::connect(address).await.unwrap();
12753 let mut decoder = FrameDecoder::new();
12754
12755 phone
12756 .write_all(®ister_bytes(ProtocolVersion::V22))
12757 .await
12758 .unwrap();
12759 read_until_message(&mut phone, &mut decoder, id::REGISTER_ACK).await;
12760 phone
12761 .write_all(
12762 &ClientMessage::ButtonTemplateRequest
12763 .encode(ProtocolVersion::V22)
12764 .unwrap(),
12765 )
12766 .await
12767 .unwrap();
12768 let frames = read_until_message(&mut phone, &mut decoder, id::BUTTON_TEMPLATE).await;
12769 let frame = frames
12770 .into_iter()
12771 .find(|frame| frame.message_id == id::BUTTON_TEMPLATE)
12772 .unwrap();
12773 assert_eq!(
12774 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12775 ServerMessage::ButtonTemplate { buttons: expected }
12776 );
12777
12778 phone
12779 .write_all(
12780 &ClientMessage::SpeedDialStatusRequest {
12781 speed_dial_instance: 1,
12782 }
12783 .encode(ProtocolVersion::V22)
12784 .unwrap(),
12785 )
12786 .await
12787 .unwrap();
12788 let frames =
12789 read_until_message(&mut phone, &mut decoder, id::SPEED_DIAL_STAT_DYNAMIC).await;
12790 let frame = frames
12791 .into_iter()
12792 .find(|frame| frame.message_id == id::SPEED_DIAL_STAT_DYNAMIC)
12793 .unwrap();
12794 assert_eq!(
12795 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12796 ServerMessage::SpeedDialStatus {
12797 instance: 1,
12798 number: "2001".into(),
12799 display_name: "Reception".into(),
12800 }
12801 );
12802
12803 phone
12804 .write_all(
12805 &ClientMessage::SpeedDialStatusRequest {
12806 speed_dial_instance: 2,
12807 }
12808 .encode(ProtocolVersion::V22)
12809 .unwrap(),
12810 )
12811 .await
12812 .unwrap();
12813 let frames =
12814 read_until_message(&mut phone, &mut decoder, id::SPEED_DIAL_STAT_DYNAMIC).await;
12815 let frame = frames
12816 .into_iter()
12817 .find(|frame| frame.message_id == id::SPEED_DIAL_STAT_DYNAMIC)
12818 .unwrap();
12819 assert_eq!(
12820 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12821 ServerMessage::SpeedDialStatus {
12822 instance: 2,
12823 number: "2002".into(),
12824 display_name: "Warehouse".into(),
12825 }
12826 );
12827
12828 phone
12829 .write_all(
12830 &ClientMessage::SpeedDialStatusRequest {
12831 speed_dial_instance: 99,
12832 }
12833 .encode(ProtocolVersion::V22)
12834 .unwrap(),
12835 )
12836 .await
12837 .unwrap();
12838 let frames =
12839 read_until_message(&mut phone, &mut decoder, id::SPEED_DIAL_STAT_DYNAMIC).await;
12840 let frame = frames
12841 .into_iter()
12842 .find(|frame| frame.message_id == id::SPEED_DIAL_STAT_DYNAMIC)
12843 .unwrap();
12844 assert_eq!(
12845 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12846 ServerMessage::SpeedDialStatus {
12847 instance: 99,
12848 number: String::new(),
12849 display_name: String::new(),
12850 }
12851 );
12852
12853 phone
12854 .write_all(
12855 &ClientMessage::FeatureStatusRequest {
12856 index: 1,
12857 capabilities: 0,
12858 }
12859 .encode(ProtocolVersion::V22)
12860 .unwrap(),
12861 )
12862 .await
12863 .unwrap();
12864 let frames = read_until_message(&mut phone, &mut decoder, id::FEATURE_STAT).await;
12865 let frame = frames
12866 .into_iter()
12867 .find(|frame| frame.message_id == id::FEATURE_STAT)
12868 .unwrap();
12869 assert_eq!(
12870 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12871 ServerMessage::FeatureStatus {
12872 instance: 1,
12873 button_type: ButtonType::DoNotDisturb,
12874 label: "DND".into(),
12875 state: 0,
12876 }
12877 );
12878
12879 phone
12880 .write_all(
12881 &ClientMessage::FeatureStatusRequest {
12882 index: 2,
12883 capabilities: 1,
12884 }
12885 .encode(ProtocolVersion::V22)
12886 .unwrap(),
12887 )
12888 .await
12889 .unwrap();
12890 let frames = read_until_message(&mut phone, &mut decoder, id::FEATURE_STAT).await;
12891 let frame = frames
12892 .into_iter()
12893 .find(|frame| frame.message_id == id::FEATURE_STAT)
12894 .unwrap();
12895 assert_eq!(
12896 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12897 ServerMessage::FeatureStatus {
12898 instance: 2,
12899 button_type: ButtonType::BlfSpeedDial,
12900 label: "Warehouse".into(),
12901 state: BusyLampFieldState::UnknownState.wire_value(),
12902 }
12903 );
12904
12905 phone
12906 .write_all(
12907 &ClientMessage::ServiceUrlStatusRequest { index: 1 }
12908 .encode(ProtocolVersion::V22)
12909 .unwrap(),
12910 )
12911 .await
12912 .unwrap();
12913 let frames =
12914 read_until_message(&mut phone, &mut decoder, id::SERVICE_URL_STAT_DYNAMIC).await;
12915 let frame = frames
12916 .into_iter()
12917 .find(|frame| frame.message_id == id::SERVICE_URL_STAT_DYNAMIC)
12918 .unwrap();
12919 assert_eq!(
12920 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
12921 ServerMessage::ServiceUrlStatus {
12922 index: 1,
12923 url: "http://services.invalid/directory".into(),
12924 label: "Directory".into(),
12925 extension_text: String::new(),
12926 }
12927 );
12928
12929 let unknown_requests = [
12930 ClientMessage::FeatureStatusRequest {
12931 index: 99,
12932 capabilities: 0,
12933 }
12934 .encode(ProtocolVersion::V22)
12935 .unwrap(),
12936 ClientMessage::ServiceUrlStatusRequest { index: 99 }
12937 .encode(ProtocolVersion::V22)
12938 .unwrap(),
12939 ClientMessage::KeepAlive
12940 .encode(ProtocolVersion::V22)
12941 .unwrap(),
12942 ]
12943 .concat();
12944 phone.write_all(&unknown_requests).await.unwrap();
12945 let frames = read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
12946 assert!(
12947 frames.iter().all(|frame| !matches!(
12948 frame.message_id,
12949 id::FEATURE_STAT
12950 | id::FEATURE_STAT_DYNAMIC
12951 | id::SERVICE_URL_STAT
12952 | id::SERVICE_URL_STAT_DYNAMIC
12953 )),
12954 "unknown feature and service requests must not produce placeholder statuses"
12955 );
12956
12957 handle.shutdown().await.unwrap();
12958 task.await.unwrap().unwrap();
12959 }
12960
12961 #[tokio::test]
12962 async fn anonymous_hotline_registration_gets_one_restricted_public_line() {
12963 let label = "Guest assistance";
12964 let config = ServerConfig {
12965 bind: "127.0.0.1:0".parse().unwrap(),
12966 advertised_address: Ipv4Addr::LOCALHOST,
12967 anonymous_hotline: Some(AnonymousHotlineDefinition::new(label).unwrap()),
12968 ..ServerConfig::default()
12969 };
12970 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
12971 let address = server.local_addr().unwrap();
12972 let task = tokio::spawn(server.run());
12973 let mut phone = TcpStream::connect(address).await.unwrap();
12974 let mut decoder = FrameDecoder::new();
12975 let protocol = ProtocolVersion::V22;
12976 let device = "SEPFFEEDDCCBBAA";
12977
12978 phone
12979 .write_all(®ister_bytes_for_device(protocol, 115, device))
12980 .await
12981 .unwrap();
12982 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
12983 assert!(matches!(
12984 events.recv().await,
12985 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Registered(registration) })) if registration.id.as_str() == device
12986 ));
12987
12988 phone
12989 .write_all(
12990 &[
12991 ClientMessage::LineStatRequest { line_instance: 1 }
12992 .encode(protocol)
12993 .unwrap(),
12994 ClientMessage::ButtonTemplateRequest
12995 .encode(protocol)
12996 .unwrap(),
12997 ClientMessage::SoftKeySetRequest.encode(protocol).unwrap(),
12998 ]
12999 .concat(),
13000 )
13001 .await
13002 .unwrap();
13003 let frames = read_until_message(&mut phone, &mut decoder, id::SOFT_KEY_SET_RES).await;
13004 assert!(frames.iter().any(|frame| matches!(
13005 ServerMessage::decode(frame.clone(), protocol),
13006 Ok(ServerMessage::LineStatus { instance: 1, number, display_name })
13007 if number == "hotline" && display_name == label
13008 )));
13009 assert!(frames.iter().any(|frame| matches!(
13010 ServerMessage::decode(frame.clone(), protocol),
13011 Ok(ServerMessage::ButtonTemplate { buttons })
13012 if buttons == vec![ButtonTemplateEntry {
13013 instance: 1,
13014 button_type: ButtonType::Line,
13015 }]
13016 )));
13017 assert!(frames.iter().any(|frame| matches!(
13018 ServerMessage::decode(frame.clone(), protocol),
13019 Ok(ServerMessage::SoftKeySet { profile })
13020 if profile.actions(KeyMode::OnHook) == [SoftKey::NewCall]
13021 && profile.actions(KeyMode::OffHook) == [SoftKey::EndCall]
13022 && profile.actions(KeyMode::RingOut) == [SoftKey::EndCall]
13023 && profile.actions(KeyMode::Connected).is_empty()
13024 )));
13025
13026 handle.shutdown().await.unwrap();
13027 task.await.unwrap().unwrap();
13028 }
13029
13030 #[tokio::test]
13031 async fn anonymous_hotline_reload_isolated_from_configured_session_and_is_idempotent() {
13032 let config = ServerConfig {
13033 bind: "127.0.0.1:0".parse().unwrap(),
13034 advertised_address: Ipv4Addr::LOCALHOST,
13035 anonymous_hotline: Some(AnonymousHotlineDefinition::new("Guest A").unwrap()),
13036 ..ServerConfig::default()
13037 };
13038 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
13039 let address = server.local_addr().unwrap();
13040 let task = tokio::spawn(server.run());
13041 let protocol = ProtocolVersion::V22;
13042 let mut configured = TcpStream::connect(address).await.unwrap();
13043 let mut configured_decoder = FrameDecoder::new();
13044 configured
13045 .write_all(®ister_bytes(protocol))
13046 .await
13047 .unwrap();
13048 read_until_message(
13049 &mut configured,
13050 &mut configured_decoder,
13051 id::CAPABILITIES_REQ,
13052 )
13053 .await;
13054 assert!(matches!(
13055 events.recv().await,
13056 Some(Event::Device(DeviceEvent {
13057 session_generation: _,
13058 device_id: _,
13059 event: DeviceEventKind::Registered(_)
13060 }))
13061 ));
13062
13063 let guest_id = "SEPFFEEDDCCBBAA";
13064 let mut guest = TcpStream::connect(address).await.unwrap();
13065 let mut guest_decoder = FrameDecoder::new();
13066 guest
13067 .write_all(®ister_bytes_for_device(protocol, 115, guest_id))
13068 .await
13069 .unwrap();
13070 read_until_message(&mut guest, &mut guest_decoder, id::CAPABILITIES_REQ).await;
13071 loop {
13072 match events.recv().await {
13073 Some(Event::Device(DeviceEvent {
13074 session_generation: _,
13075 device_id: _,
13076 event: DeviceEventKind::Registered(registration),
13077 })) if registration.id.as_str() == guest_id => {
13078 break;
13079 }
13080 Some(_) => {}
13081 None => panic!("server stopped before replacement guest registration"),
13082 }
13083 }
13084
13085 assert_eq!(
13086 handle
13087 .reconfigure_anonymous_hotline(Some(
13088 AnonymousHotlineDefinition::new("Guest A").unwrap(),
13089 ))
13090 .await
13091 .unwrap(),
13092 0
13093 );
13094 let station_policy = handle
13095 .reconfigure_station_policy(
13096 [definition()],
13097 [],
13098 Some(AnonymousHotlineDefinition::new("Guest B").unwrap()),
13099 )
13100 .await
13101 .unwrap();
13102 assert!(station_policy.is_unchanged());
13103 let mut closed = [0_u8; 1];
13104 assert_eq!(
13105 tokio::time::timeout(Duration::from_secs(1), guest.read(&mut closed))
13106 .await
13107 .unwrap()
13108 .unwrap(),
13109 0
13110 );
13111
13112 configured
13113 .write_all(
13114 &ClientMessage::LineStatRequest { line_instance: 1 }
13115 .encode(protocol)
13116 .unwrap(),
13117 )
13118 .await
13119 .unwrap();
13120 let frames = read_until_message(
13121 &mut configured,
13122 &mut configured_decoder,
13123 id::LINE_STAT_DYNAMIC,
13124 )
13125 .await;
13126 assert!(frames.into_iter().any(|frame| matches!(
13127 ServerMessage::decode(frame, protocol),
13128 Ok(ServerMessage::LineStatus { number, .. }) if number == "1001"
13129 )));
13130
13131 let mut replacement = TcpStream::connect(address).await.unwrap();
13132 let mut replacement_decoder = FrameDecoder::new();
13133 replacement
13134 .write_all(®ister_bytes_for_device(protocol, 115, guest_id))
13135 .await
13136 .unwrap();
13137 read_until_message(
13138 &mut replacement,
13139 &mut replacement_decoder,
13140 id::CAPABILITIES_REQ,
13141 )
13142 .await;
13143 loop {
13144 match events.recv().await {
13145 Some(Event::Device(DeviceEvent {
13146 session_generation: _,
13147 device_id: _,
13148 event: DeviceEventKind::Registered(registration),
13149 })) if registration.id.as_str() == guest_id => {
13150 break;
13151 }
13152 Some(_) => {}
13153 None => panic!("server stopped before replacement guest registration"),
13154 }
13155 }
13156 replacement
13157 .write_all(
13158 &ClientMessage::LineStatRequest { line_instance: 1 }
13159 .encode(protocol)
13160 .unwrap(),
13161 )
13162 .await
13163 .unwrap();
13164 let frames = read_until_message(
13165 &mut replacement,
13166 &mut replacement_decoder,
13167 id::LINE_STAT_DYNAMIC,
13168 )
13169 .await;
13170 assert!(frames.into_iter().any(|frame| matches!(
13171 ServerMessage::decode(frame, protocol),
13172 Ok(ServerMessage::LineStatus { number, display_name, .. })
13173 if number == "hotline" && display_name == "Guest B"
13174 )));
13175
13176 assert_eq!(handle.reconfigure_anonymous_hotline(None).await.unwrap(), 1);
13177 assert_eq!(
13178 tokio::time::timeout(Duration::from_secs(1), replacement.read(&mut closed))
13179 .await
13180 .unwrap()
13181 .unwrap(),
13182 0
13183 );
13184 assert_eq!(handle.reconfigure_anonymous_hotline(None).await.unwrap(), 0);
13185
13186 assert_eq!(
13187 handle
13188 .reconfigure_anonymous_hotline(Some(
13189 AnonymousHotlineDefinition::new("Guest C").unwrap(),
13190 ))
13191 .await
13192 .unwrap(),
13193 0
13194 );
13195 let mut promoted = TcpStream::connect(address).await.unwrap();
13196 let mut promoted_decoder = FrameDecoder::new();
13197 promoted
13198 .write_all(®ister_bytes_for_device(protocol, 115, guest_id))
13199 .await
13200 .unwrap();
13201 read_until_message(&mut promoted, &mut promoted_decoder, id::CAPABILITIES_REQ).await;
13202 loop {
13203 match events.recv().await {
13204 Some(Event::Device(DeviceEvent {
13205 session_generation: _,
13206 device_id: _,
13207 event: DeviceEventKind::Registered(registration),
13208 })) if registration.id.as_str() == guest_id => {
13209 break;
13210 }
13211 Some(_) => {}
13212 None => panic!("server stopped before promoted guest registration"),
13213 }
13214 }
13215 let result = handle
13216 .reconfigure_affected(
13217 [definition(), definition_for(guest_id)],
13218 [DeviceId::new(guest_id).unwrap()],
13219 )
13220 .await
13221 .unwrap();
13222 assert_eq!(result.added, [DeviceId::new(guest_id).unwrap()]);
13223 assert_eq!(
13224 tokio::time::timeout(Duration::from_secs(1), promoted.read(&mut closed))
13225 .await
13226 .unwrap()
13227 .unwrap(),
13228 0
13229 );
13230 let mut configured_guest = TcpStream::connect(address).await.unwrap();
13231 let mut configured_guest_decoder = FrameDecoder::new();
13232 configured_guest
13233 .write_all(®ister_bytes_for_device(protocol, 115, guest_id))
13234 .await
13235 .unwrap();
13236 read_until_message(
13237 &mut configured_guest,
13238 &mut configured_guest_decoder,
13239 id::CAPABILITIES_REQ,
13240 )
13241 .await;
13242 configured_guest
13243 .write_all(
13244 &ClientMessage::LineStatRequest { line_instance: 1 }
13245 .encode(protocol)
13246 .unwrap(),
13247 )
13248 .await
13249 .unwrap();
13250 let frames = read_until_message(
13251 &mut configured_guest,
13252 &mut configured_guest_decoder,
13253 id::LINE_STAT_DYNAMIC,
13254 )
13255 .await;
13256 assert!(frames.into_iter().any(|frame| matches!(
13257 ServerMessage::decode(frame, protocol),
13258 Ok(ServerMessage::LineStatus { number, .. }) if number == "1001"
13259 )));
13260
13261 handle.shutdown().await.unwrap();
13262 task.await.unwrap().unwrap();
13263 }
13264
13265 #[tokio::test]
13266 async fn duplicate_anonymous_registration_replaces_only_the_previous_guest_session() {
13267 let config = ServerConfig {
13268 bind: "127.0.0.1:0".parse().unwrap(),
13269 advertised_address: Ipv4Addr::LOCALHOST,
13270 anonymous_hotline: Some(AnonymousHotlineDefinition::new("Guest").unwrap()),
13271 ..ServerConfig::default()
13272 };
13273 let (server, handle, mut events) = Server::bind(config, []).await.unwrap();
13274 let address = server.local_addr().unwrap();
13275 let task = tokio::spawn(server.run());
13276 let protocol = ProtocolVersion::V22;
13277 let guest_id = "SEPFFEEDDCCBBAA";
13278 let mut first = TcpStream::connect(address).await.unwrap();
13279 let mut first_decoder = FrameDecoder::new();
13280 first
13281 .write_all(®ister_bytes_for_device(protocol, 115, guest_id))
13282 .await
13283 .unwrap();
13284 read_until_message(&mut first, &mut first_decoder, id::CAPABILITIES_REQ).await;
13285 assert!(matches!(
13286 events.recv().await,
13287 Some(Event::Device(DeviceEvent {
13288 session_generation: _,
13289 device_id: _,
13290 event: DeviceEventKind::Registered(_)
13291 }))
13292 ));
13293
13294 let mut second = TcpStream::connect(address).await.unwrap();
13295 let mut second_decoder = FrameDecoder::new();
13296 second
13297 .write_all(®ister_bytes_for_device(protocol, 115, guest_id))
13298 .await
13299 .unwrap();
13300 read_until_message(&mut second, &mut second_decoder, id::CAPABILITIES_REQ).await;
13301 assert!(matches!(
13302 events.recv().await,
13303 Some(Event::Device(DeviceEvent {
13304 session_generation: _,
13305 device_id: _,
13306 event: DeviceEventKind::Registered(_)
13307 }))
13308 ));
13309 let mut closed = [0_u8; 1];
13310 assert_eq!(
13311 tokio::time::timeout(Duration::from_secs(1), first.read(&mut closed))
13312 .await
13313 .unwrap()
13314 .unwrap(),
13315 0
13316 );
13317
13318 second
13319 .write_all(
13320 &ClientMessage::LineStatRequest { line_instance: 1 }
13321 .encode(protocol)
13322 .unwrap(),
13323 )
13324 .await
13325 .unwrap();
13326 read_until_message(&mut second, &mut second_decoder, id::LINE_STAT_DYNAMIC).await;
13327
13328 handle.shutdown().await.unwrap();
13329 task.await.unwrap().unwrap();
13330 }
13331
13332 #[tokio::test]
13333 async fn anonymous_hotline_disabled_rejects_unknown_without_affecting_configured_devices() {
13334 let config = ServerConfig {
13335 bind: "127.0.0.1:0".parse().unwrap(),
13336 advertised_address: Ipv4Addr::LOCALHOST,
13337 ..ServerConfig::default()
13338 };
13339 let (server, handle, _events) = Server::bind(config, [definition()]).await.unwrap();
13340 let address = server.local_addr().unwrap();
13341 let task = tokio::spawn(server.run());
13342 let mut phone = TcpStream::connect(address).await.unwrap();
13343 let mut decoder = FrameDecoder::new();
13344
13345 phone
13346 .write_all(®ister_bytes_for_device(
13347 ProtocolVersion::V22,
13348 115,
13349 "SEPFFEEDDCCBBAA",
13350 ))
13351 .await
13352 .unwrap();
13353 let frames = read_until_message(&mut phone, &mut decoder, id::REGISTER_REJECT).await;
13354 assert!(frames.into_iter().any(|frame| matches!(
13355 ServerMessage::decode(frame, ProtocolVersion::V17),
13356 Ok(ServerMessage::RegisterReject { reason }) if reason == "Device not configured"
13357 )));
13358
13359 handle.shutdown().await.unwrap();
13360 task.await.unwrap().unwrap();
13361 }
13362
13363 #[test]
13364 fn anonymous_hotline_definition_bounds_and_debug_are_destination_free() {
13365 assert!(AnonymousHotlineDefinition::new("").is_err());
13366 assert!(AnonymousHotlineDefinition::new("x".repeat(80)).is_err());
13367 assert!(AnonymousHotlineDefinition::new("guest\nline").is_err());
13368 let definition = AnonymousHotlineDefinition::new("Guest").unwrap();
13369 assert!(!format!("{definition:?}").contains("111"));
13370 }
13371
13372 #[tokio::test]
13373 async fn mutable_forwarding_and_feature_state_is_published_and_answered() {
13374 let protocol = ProtocolVersion::V22;
13375 let device_id = DeviceId::new("SEP001122334455").unwrap();
13376 let config = ServerConfig {
13377 bind: "127.0.0.1:0".parse().unwrap(),
13378 advertised_address: Ipv4Addr::LOCALHOST,
13379 ..ServerConfig::default()
13380 };
13381 let (server, handle, mut events) =
13382 Server::bind(config, [mixed_definition()]).await.unwrap();
13383 let address = server.local_addr().unwrap();
13384 let task = tokio::spawn(server.run());
13385 let mut phone = TcpStream::connect(address).await.unwrap();
13386 let mut decoder = FrameDecoder::new();
13387 phone.write_all(®ister_bytes(protocol)).await.unwrap();
13388 read_until_message(&mut phone, &mut decoder, id::REGISTER_ACK).await;
13389 assert!(matches!(
13390 events.recv().await,
13391 Some(Event::Device(DeviceEvent {
13392 session_generation: _,
13393 device_id: _,
13394 event: DeviceEventKind::Registered(_)
13395 }))
13396 ));
13397
13398 handle
13399 .send(Command::new(
13400 device_id.clone(),
13401 CommandAction::SetForwardStatus {
13402 line_instance: LineInstance(1),
13403 forward_all: Some("9000".into()),
13404 forward_busy: None,
13405 forward_no_answer: Some("9001".into()),
13406 },
13407 ))
13408 .await
13409 .unwrap();
13410 let frames = read_until_message(&mut phone, &mut decoder, id::FORWARD_STAT).await;
13411 assert!(frames.into_iter().any(|frame| matches!(
13412 ServerMessage::decode(frame, protocol),
13413 Ok(ServerMessage::ForwardStatus {
13414 line_instance: 1,
13415 ref forward_all,
13416 forward_busy: None,
13417 ref forward_no_answer,
13418 }) if forward_all.as_deref() == Some("9000")
13419 && forward_no_answer.as_deref() == Some("9001")
13420 )));
13421
13422 handle
13423 .send(Command::new(
13424 device_id.clone(),
13425 CommandAction::SetDoNotDisturbStatus {
13426 instance: LineInstance::new(1),
13427 mode: DoNotDisturbMode::Reject,
13428 button_mode: DoNotDisturbButtonMode::Cycle,
13429 },
13430 ))
13431 .await
13432 .unwrap();
13433 let frames = read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
13434 assert!(frames.iter().any(|frame| matches!(
13435 ServerMessage::decode(frame.clone(), protocol),
13436 Ok(ServerMessage::FeatureStatus {
13437 instance: 1,
13438 button_type: ButtonType::MultiblinkFeature,
13439 state: 0x020202,
13440 ..
13441 })
13442 )));
13443 assert!(frames.into_iter().any(|frame| matches!(
13444 ServerMessage::decode(frame, protocol),
13445 Ok(ServerMessage::SetLamp {
13446 stimulus: ButtonType::DoNotDisturb,
13447 instance: 1,
13448 mode: LampMode::On,
13449 })
13450 )));
13451
13452 phone
13453 .write_all(
13454 &[
13455 ClientMessage::ForwardStatusRequest { line_instance: 1 }
13456 .encode(protocol)
13457 .unwrap(),
13458 ClientMessage::FeatureStatusRequest {
13459 index: 1,
13460 capabilities: 0,
13461 }
13462 .encode(protocol)
13463 .unwrap(),
13464 ]
13465 .concat(),
13466 )
13467 .await
13468 .unwrap();
13469 let frames = read_until_message(&mut phone, &mut decoder, id::FEATURE_STAT).await;
13470 assert!(frames.iter().any(|frame| matches!(
13471 ServerMessage::decode(frame.clone(), protocol),
13472 Ok(ServerMessage::ForwardStatus { ref forward_all, .. })
13473 if forward_all.as_deref() == Some("9000")
13474 )));
13475 assert!(frames.into_iter().any(|frame| matches!(
13476 ServerMessage::decode(frame, protocol),
13477 Ok(ServerMessage::FeatureStatus {
13478 instance: 1,
13479 button_type: ButtonType::MultiblinkFeature,
13480 state: 0x020202,
13481 ..
13482 })
13483 )));
13484
13485 phone
13486 .write_all(
13487 &ClientMessage::Stimulus {
13488 stimulus: Stimulus::DoNotDisturb,
13489 instance: 1,
13490 call_reference: 0,
13491 status: 0,
13492 }
13493 .encode(protocol)
13494 .unwrap(),
13495 )
13496 .await
13497 .unwrap();
13498 assert!(matches!(
13499 events.recv().await,
13500 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::DoNotDisturbButton {
13501 instance: LineInstance(1),
13502 } })) if actual_device == device_id
13503 ));
13504 phone
13505 .write_all(
13506 &ClientMessage::Stimulus {
13507 stimulus: Stimulus::DoNotDisturb,
13508 instance: 99,
13509 call_reference: 0,
13510 status: 0,
13511 }
13512 .encode(protocol)
13513 .unwrap(),
13514 )
13515 .await
13516 .unwrap();
13517 assert!(
13518 tokio::time::timeout(Duration::from_millis(25), events.recv())
13519 .await
13520 .is_err()
13521 );
13522
13523 handle.shutdown().await.unwrap();
13524 task.await.unwrap().unwrap();
13525 }
13526
13527 #[tokio::test]
13528 async fn registered_phone_receives_configured_soft_key_sets_and_masks() {
13529 let mut device = definition();
13530 device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::NewCall, SoftKey::Redial]);
13531 let default = device.soft_keys.clone();
13532 device.soft_keys = SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|mode| {
13533 if mode == KeyMode::OffHook {
13534 (
13535 mode,
13536 vec![SoftKey::EndCall, SoftKey::Pickup, SoftKey::GroupPickup],
13537 )
13538 } else {
13539 (mode, default.actions(mode).to_vec())
13540 }
13541 }))
13542 .unwrap();
13543 let expected_profile = device.soft_keys.clone();
13544 let config = ServerConfig {
13545 bind: "127.0.0.1:0".parse().unwrap(),
13546 advertised_address: Ipv4Addr::LOCALHOST,
13547 ..ServerConfig::default()
13548 };
13549 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13550 let address = server.local_addr().unwrap();
13551 let task = tokio::spawn(server.run());
13552 let mut phone = TcpStream::connect(address).await.unwrap();
13553 let mut decoder = FrameDecoder::new();
13554 let protocol = ProtocolVersion::V22;
13555
13556 phone.write_all(®ister_bytes(protocol)).await.unwrap();
13557 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13558 assert!(matches!(
13559 events.recv().await,
13560 Some(Event::Device(DeviceEvent {
13561 session_generation: _,
13562 device_id: _,
13563 event: DeviceEventKind::Registered(_)
13564 }))
13565 ));
13566
13567 phone
13568 .write_all(
13569 &[
13570 ClientMessage::SoftKeyTemplateRequest
13571 .encode(protocol)
13572 .unwrap(),
13573 ClientMessage::SoftKeySetRequest.encode(protocol).unwrap(),
13574 ]
13575 .concat(),
13576 )
13577 .await
13578 .unwrap();
13579 let frames = read_until_message(&mut phone, &mut decoder, id::SOFT_KEY_SET_RES).await;
13580 assert!(frames.iter().any(|frame| matches!(
13581 ServerMessage::decode(frame.clone(), protocol),
13582 Ok(ServerMessage::SoftKeyTemplate { actions })
13583 if actions == expected_profile.template_actions()
13584 )));
13585 assert!(frames.iter().any(|frame| matches!(
13586 ServerMessage::decode(frame.clone(), protocol),
13587 Ok(ServerMessage::SoftKeySet { profile }) if profile == expected_profile
13588 )));
13589
13590 phone
13591 .write_all(
13592 &ClientMessage::SoftKeyEvent {
13593 event: SoftKey::NewCall.wire_value(),
13594 line_instance: 1,
13595 call_reference: 0,
13596 }
13597 .encode(protocol)
13598 .unwrap(),
13599 )
13600 .await
13601 .unwrap();
13602 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
13603 assert!(frames.iter().any(|frame| matches!(
13604 ServerMessage::decode(frame.clone(), protocol),
13605 Ok(ServerMessage::SelectSoftKeys {
13606 set: KeyMode::OffHook,
13607 valid_mask: 0b111,
13608 ..
13609 })
13610 )));
13611 assert!(matches!(
13612 events.recv().await,
13613 Some(Event::Device(DeviceEvent {
13614 session_generation: _,
13615 device_id: _,
13616 event: DeviceEventKind::OffHook { .. }
13617 }))
13618 ));
13619 assert!(matches!(
13620 events.recv().await,
13621 Some(Event::Device(DeviceEvent {
13622 session_generation: _,
13623 device_id: _,
13624 event: DeviceEventKind::SoftKey {
13625 soft_key: SoftKey::NewCall,
13626 ..
13627 }
13628 }))
13629 ));
13630
13631 handle.shutdown().await.unwrap();
13632 task.await.unwrap().unwrap();
13633 }
13634
13635 #[tokio::test]
13636 async fn generic_feature_button_emits_only_for_the_configured_instance() {
13637 let mut device = definition();
13638 device
13639 .buttons
13640 .push(ButtonDefinition::Feature(FeatureDefinition {
13641 instance: 1,
13642 label: "Night service".into(),
13643 feature: ButtonType::Feature,
13644 }));
13645 let config = ServerConfig {
13646 bind: "127.0.0.1:0".parse().unwrap(),
13647 advertised_address: Ipv4Addr::LOCALHOST,
13648 ..ServerConfig::default()
13649 };
13650 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13651 let address = server.local_addr().unwrap();
13652 let task = tokio::spawn(server.run());
13653 let mut phone = TcpStream::connect(address).await.unwrap();
13654 let mut decoder = FrameDecoder::new();
13655 let protocol = ProtocolVersion::V22;
13656
13657 phone.write_all(®ister_bytes(protocol)).await.unwrap();
13658 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13659 assert!(matches!(
13660 events.recv().await,
13661 Some(Event::Device(DeviceEvent {
13662 session_generation: _,
13663 device_id: _,
13664 event: DeviceEventKind::Registered(_)
13665 }))
13666 ));
13667
13668 phone
13669 .write_all(
13670 &ClientMessage::Stimulus {
13671 stimulus: Stimulus::Privacy,
13672 instance: 99,
13673 call_reference: 0,
13674 status: 0,
13675 }
13676 .encode(protocol)
13677 .unwrap(),
13678 )
13679 .await
13680 .unwrap();
13681 assert!(
13682 tokio::time::timeout(Duration::from_millis(50), events.recv())
13683 .await
13684 .is_err()
13685 );
13686
13687 phone
13688 .write_all(
13689 &ClientMessage::Stimulus {
13690 stimulus: Stimulus::Privacy,
13691 instance: 1,
13692 call_reference: 0,
13693 status: 0,
13694 }
13695 .encode(protocol)
13696 .unwrap(),
13697 )
13698 .await
13699 .unwrap();
13700 assert!(matches!(
13701 events.recv().await,
13702 Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::FeatureButton {
13703 instance: LineInstance(1),
13704 } })) if device_id == DeviceId::new("SEP001122334455").unwrap()
13705 ));
13706
13707 handle.shutdown().await.unwrap();
13708 task.await.unwrap().unwrap();
13709 }
13710
13711 #[test]
13712 fn mobility_candidate_rebuilds_every_slot_in_physical_button_order() {
13713 let mut configured = definition();
13714 configured
13715 .buttons
13716 .push(ButtonDefinition::Feature(FeatureDefinition {
13717 instance: 4,
13718 label: "Mobility A".into(),
13719 feature: ButtonType::Mobility,
13720 }));
13721 configured
13722 .buttons
13723 .push(ButtonDefinition::Feature(FeatureDefinition {
13724 instance: 5,
13725 label: "Mobility B".into(),
13726 feature: ButtonType::Mobility,
13727 }));
13728 let first = LineAppearance::new(
13729 2,
13730 LineDefinition {
13731 number: "9001".into(),
13732 display_name: "Roaming 9001".into(),
13733 },
13734 );
13735 let second = LineAppearance::new(
13736 3,
13737 LineDefinition {
13738 number: "9002".into(),
13739 display_name: "Roaming 9002".into(),
13740 },
13741 );
13742
13743 let first_map = HashMap::from([(4, first.clone())]);
13744 let with_first = mobility_device_candidate(&configured, &HashMap::new(), &first_map)
13745 .expect("first roaming appearance is valid");
13746 let both_map = HashMap::from([(5, second.clone()), (4, first)]);
13747 let with_both = mobility_device_candidate(&with_first, &first_map, &both_map)
13748 .expect("both roaming appearances are valid");
13749 let projected = with_both
13750 .buttons
13751 .iter()
13752 .filter_map(|button| match button {
13753 ButtonDefinition::Feature(feature) if feature.feature == ButtonType::Mobility => {
13754 Some(("mobility", feature.instance))
13755 }
13756 ButtonDefinition::Line(line) if line.instance > 1 => Some(("line", line.instance)),
13757 _ => None,
13758 })
13759 .collect::<Vec<_>>();
13760 assert_eq!(
13761 projected,
13762 vec![("mobility", 4), ("line", 2), ("mobility", 5), ("line", 3),]
13763 );
13764 assert!(matches!(
13765 line_status(&with_both, 2),
13766 Some(ServerMessage::LineStatus { number, .. }) if number == "9001"
13767 ));
13768 assert!(matches!(
13769 line_status(&with_both, 3),
13770 Some(ServerMessage::LineStatus { number, .. }) if number == "9002"
13771 ));
13772
13773 let second_map = HashMap::from([(5, second)]);
13774 let without_first = mobility_device_candidate(&with_both, &both_map, &second_map)
13775 .expect("removing one roaming appearance preserves the other");
13776 assert!(line_status(&without_first, 2).is_none());
13777 assert!(matches!(
13778 line_status(&without_first, 3),
13779 Some(ServerMessage::LineStatus { number, .. }) if number == "9002"
13780 ));
13781 }
13782
13783 #[tokio::test]
13784 async fn mobility_button_and_live_appearance_refresh_preserve_the_session_call() {
13785 let mut device = definition();
13786 device
13787 .buttons
13788 .push(ButtonDefinition::Feature(FeatureDefinition {
13789 instance: 4,
13790 label: "Mobility".into(),
13791 feature: ButtonType::Mobility,
13792 }));
13793 let config = ServerConfig {
13794 bind: "127.0.0.1:0".parse().unwrap(),
13795 advertised_address: Ipv4Addr::LOCALHOST,
13796 ..ServerConfig::default()
13797 };
13798 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13799 let address = server.local_addr().unwrap();
13800 let task = tokio::spawn(server.run());
13801 let mut phone = TcpStream::connect(address).await.unwrap();
13802 let mut decoder = FrameDecoder::new();
13803 let protocol = ProtocolVersion::V22;
13804 let device_id = DeviceId::new("SEP001122334455").unwrap();
13805
13806 phone.write_all(®ister_bytes(protocol)).await.unwrap();
13807 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13808 assert!(matches!(
13809 events.recv().await,
13810 Some(Event::Device(DeviceEvent {
13811 session_generation: _,
13812 device_id: _,
13813 event: DeviceEventKind::Registered(_)
13814 }))
13815 ));
13816 phone
13817 .write_all(
13818 &ClientMessage::Stimulus {
13819 stimulus: Stimulus::Mobility,
13820 instance: 4,
13821 call_reference: 0,
13822 status: 0,
13823 }
13824 .encode(protocol)
13825 .unwrap(),
13826 )
13827 .await
13828 .unwrap();
13829 assert!(matches!(
13830 events.recv().await,
13831 Some(Event::Device(DeviceEvent {
13832 session_generation: _,
13833 device_id: _,
13834 event: DeviceEventKind::MobilityButton {
13835 instance: LineInstance(4),
13836 ..
13837 }
13838 }))
13839 ));
13840
13841 let call_id = CallId(77);
13842 handle
13843 .send_confirmed(Command::new(
13844 device_id.clone(),
13845 CommandAction::BeginCall {
13846 line_instance: LineInstance(1),
13847 call_id,
13848 codec: Codec::Pcmu,
13849 },
13850 ))
13851 .await
13852 .unwrap();
13853 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
13854
13855 let roaming = LineAppearance::new(
13856 2,
13857 LineDefinition {
13858 number: "9001".into(),
13859 display_name: "Roaming 9001".into(),
13860 },
13861 );
13862 handle
13863 .send_confirmed(Command::new(
13864 device_id.clone(),
13865 CommandAction::SetMobilityAppearance {
13866 mobility_instance: LineInstance::new(4),
13867 appearance: Some(roaming.clone()),
13868 },
13869 ))
13870 .await
13871 .unwrap();
13872 let frames = read_until_message(&mut phone, &mut decoder, id::LINE_STAT_DYNAMIC).await;
13873 assert!(frames.iter().any(|frame| matches!(
13874 ServerMessage::decode(frame.clone(), protocol),
13875 Ok(ServerMessage::ButtonTemplate { ref buttons })
13876 if buttons.iter().any(|button| button.instance == 2 && button.button_type == ButtonType::Line)
13877 )));
13878 assert!(frames.iter().any(|frame| matches!(
13879 ServerMessage::decode(frame.clone(), protocol),
13880 Ok(ServerMessage::LineStatus { instance: 2, ref number, .. }) if number == "9001"
13881 )));
13882
13883 handle
13884 .send_confirmed(Command::new(
13885 device_id.clone(),
13886 CommandAction::SetMobilityAppearance {
13887 mobility_instance: LineInstance::new(4),
13888 appearance: None,
13889 },
13890 ))
13891 .await
13892 .unwrap();
13893 let frames = read_until_message(&mut phone, &mut decoder, id::LINE_STAT_DYNAMIC).await;
13894 assert!(frames.iter().any(|frame| matches!(
13895 ServerMessage::decode(frame.clone(), protocol),
13896 Ok(ServerMessage::LineStatus { instance: 2, ref number, .. }) if number.is_empty()
13897 )));
13898 handle
13899 .send_confirmed(Command::new(
13900 device_id,
13901 CommandAction::SetCallState {
13902 call_id,
13903 state: CallState::Connected,
13904 },
13905 ))
13906 .await
13907 .unwrap();
13908 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
13909 assert!(frames.iter().any(|frame| matches!(
13910 ServerMessage::decode(frame.clone(), protocol),
13911 Ok(ServerMessage::CallState {
13912 state: CallState::Connected,
13913 ..
13914 })
13915 )));
13916
13917 handle.shutdown().await.unwrap();
13918 task.await.unwrap().unwrap();
13919 }
13920
13921 #[tokio::test]
13922 async fn parking_button_menu_and_selection_are_typed_end_to_end() {
13923 let mut device = definition();
13924 device
13925 .buttons
13926 .push(ButtonDefinition::Feature(FeatureDefinition {
13927 instance: 4,
13928 label: "Parking".into(),
13929 feature: ButtonType::ParkingLot,
13930 }));
13931 let config = ServerConfig {
13932 bind: "127.0.0.1:0".parse().unwrap(),
13933 advertised_address: Ipv4Addr::LOCALHOST,
13934 ..ServerConfig::default()
13935 };
13936 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
13937 let address = server.local_addr().unwrap();
13938 let task = tokio::spawn(server.run());
13939 let mut phone = TcpStream::connect(address).await.unwrap();
13940 let mut decoder = FrameDecoder::new();
13941 let protocol = ProtocolVersion::V22;
13942 let device_id = DeviceId::new("SEP001122334455").unwrap();
13943
13944 phone.write_all(®ister_bytes(protocol)).await.unwrap();
13945 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
13946 assert!(matches!(
13947 events.recv().await,
13948 Some(Event::Device(DeviceEvent {
13949 session_generation: _,
13950 device_id: _,
13951 event: DeviceEventKind::Registered(_)
13952 }))
13953 ));
13954
13955 phone
13956 .write_all(
13957 &ClientMessage::Stimulus {
13958 stimulus: Stimulus::ParkingLot,
13959 instance: 4,
13960 call_reference: 0,
13961 status: 0,
13962 }
13963 .encode(protocol)
13964 .unwrap(),
13965 )
13966 .await
13967 .unwrap();
13968 assert!(matches!(
13969 events.recv().await,
13970 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ParkingLotButton {
13971 instance: LineInstance(4),
13972 call_id: None,
13973 line_instance: LineInstance(1),
13974 } })) if actual == device_id
13975 ));
13976
13977 handle
13978 .send(Command::new(
13979 device_id.clone(),
13980 CommandAction::ShowParkingMenu {
13981 instance: LineInstance::new(4),
13982 transaction_id: TransactionId(17),
13983 lot: "east & west".into(),
13984 calls: vec![ParkingMenuEntry {
13985 slot: 701,
13986 caller_name: "Taylor <T>".into(),
13987 caller_number: "2100".into(),
13988 connected_name: "Desk".into(),
13989 connected_number: "1001".into(),
13990 }],
13991 },
13992 ))
13993 .await
13994 .unwrap();
13995 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
13996 let message = frames
13997 .into_iter()
13998 .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
13999 .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14000 .unwrap();
14001 let ServerMessage::UserToDeviceDataV1(menu) = message else {
14002 panic!("expected parking menu application data");
14003 };
14004 assert_eq!(menu.application_id, PARKING_APPLICATION_ID);
14005 assert_eq!(menu.line_instance, 4);
14006 assert_eq!(menu.call_reference, 0);
14007 assert_eq!(menu.transaction_id, 17);
14008 let xml = String::from_utf8(menu.data).unwrap();
14009 assert!(xml.contains("Taylor <T>"));
14010 assert!(xml.contains("UserCallData:9090:4:0:17:"));
14011 assert!(xml.contains("retrieve/east%20%26%20west/701"));
14012
14013 phone
14014 .write_all(
14015 &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14016 application_id: PARKING_APPLICATION_ID,
14017 line_instance: 4,
14018 call_reference: 0,
14019 transaction_id: 17,
14020 sequence_flag: 0,
14021 display_priority: 0,
14022 conference_id: 0,
14023 application_instance_id: 4,
14024 routing: 0,
14025 data: b"retrieve/east%20%26%20west/701".to_vec(),
14026 })
14027 .encode(protocol)
14028 .unwrap(),
14029 )
14030 .await
14031 .unwrap();
14032 assert!(matches!(
14033 events.recv().await,
14034 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ParkingMenuSelection {
14035 lot,
14036 slot: 701,
14037 } })) if actual == device_id && lot == "east & west"
14038 ));
14039 let Some(Event::Device(DeviceEvent {
14040 session_generation: _,
14041 device_id: actual,
14042 event: DeviceEventKind::PhoneServiceResponse { response },
14043 })) = events.recv().await
14044 else {
14045 panic!("expected typed phone-service response");
14046 };
14047 assert_eq!(actual, device_id);
14048 assert_eq!(response.kind, PhoneServiceMessageKind::Data);
14049 assert_eq!(response.routing.application_id, ApplicationId::new(9090));
14050 assert_eq!(response.routing.line_instance, LineInstance::new(4));
14051 assert_eq!(response.routing.call_reference, CallReference::new(0));
14052 assert_eq!(response.routing.transaction_id, TransactionId::new(17));
14053 let PhoneServicePayload::Submission(submission) = response.payload else {
14054 panic!("expected typed menu submission");
14055 };
14056 assert_eq!(submission.route, ["retrieve", "east & west", "701"]);
14057 assert!(submission.values.is_empty());
14058
14059 handle.shutdown().await.unwrap();
14060 task.await.unwrap().unwrap();
14061 }
14062
14063 #[tokio::test]
14064 async fn conference_list_uses_protocol_family_and_routes_typed_actions() {
14065 for (protocol, family) in [
14066 (ProtocolVersion::V3, ConferenceMenuFamily::Menu),
14067 (ProtocolVersion::V22, ConferenceMenuFamily::IconMenu),
14068 ] {
14069 let config = ServerConfig {
14070 bind: "127.0.0.1:0".parse().unwrap(),
14071 advertised_address: Ipv4Addr::LOCALHOST,
14072 ..ServerConfig::default()
14073 };
14074 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14075 let address = server.local_addr().unwrap();
14076 let task = tokio::spawn(server.run());
14077 let mut phone = TcpStream::connect(address).await.unwrap();
14078 let mut decoder = FrameDecoder::new();
14079 let device_id = DeviceId::new("SEP001122334455").unwrap();
14080
14081 phone.write_all(®ister_bytes(protocol)).await.unwrap();
14082 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14083 assert!(matches!(
14084 events.recv().await,
14085 Some(Event::Device(DeviceEvent {
14086 session_generation: _,
14087 device_id: _,
14088 event: DeviceEventKind::Registered(_)
14089 }))
14090 ));
14091 handle
14092 .send(Command::new(
14093 device_id.clone(),
14094 CommandAction::BeginCall {
14095 line_instance: LineInstance(1),
14096 call_id: CallId(7001),
14097 codec: Codec::Pcma,
14098 },
14099 ))
14100 .await
14101 .unwrap();
14102 read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
14103
14104 handle
14105 .send(Command::new(
14106 device_id.clone(),
14107 CommandAction::ShowConferenceList {
14108 call_id: CallId(7001),
14109 conference_id: ConferenceId::new(44),
14110 participants: vec![ConferenceListEntry {
14111 participant_id: crate::ParticipantId::new(7),
14112 name: "Taylor <T>".into(),
14113 number: "2100".into(),
14114 moderator: false,
14115 muted: false,
14116 }],
14117 },
14118 ))
14119 .await
14120 .unwrap();
14121 let frames =
14122 read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14123 let message = frames
14124 .into_iter()
14125 .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
14126 .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14127 .unwrap();
14128 let ServerMessage::UserToDeviceDataV1(menu) = message else {
14129 panic!("expected conference-list application data");
14130 };
14131 assert_eq!(menu.application_id, ConferenceListAction::APPLICATION_ID);
14132 assert_eq!(menu.conference_id, 44);
14133 let document = ConferenceListDocument::from_xml(&menu.data, family).unwrap();
14134 assert_eq!(
14135 document.actions().collect::<Vec<_>>(),
14136 [
14137 ConferenceListAction::Participant {
14138 conference_id: ConferenceId::new(44),
14139 participant_id: crate::ParticipantId::new(7),
14140 },
14141 ConferenceListAction::End {
14142 conference_id: ConferenceId::new(44),
14143 },
14144 ]
14145 );
14146 assert!(
14147 String::from_utf8(menu.data)
14148 .unwrap()
14149 .contains("Taylor <T>")
14150 );
14151
14152 phone
14153 .write_all(
14154 &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14155 application_id: ConferenceListAction::APPLICATION_ID,
14156 line_instance: 1,
14157 call_reference: 7001,
14158 transaction_id: 44,
14159 sequence_flag: 0,
14160 display_priority: 0,
14161 conference_id: 44,
14162 application_instance_id: 1,
14163 routing: 0,
14164 data: b"conference/44/participant/7".to_vec(),
14165 })
14166 .encode(protocol)
14167 .unwrap(),
14168 )
14169 .await
14170 .unwrap();
14171 assert!(matches!(
14172 events.recv().await,
14173 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ConferenceListAction {
14174 action: ConferenceListAction::Participant {
14175 conference_id,
14176 participant_id,
14177 },
14178 } })) if actual == device_id
14179 && conference_id == ConferenceId::new(44)
14180 && participant_id == crate::ParticipantId::new(7)
14181 ));
14182 assert!(matches!(
14183 events.recv().await,
14184 Some(Event::Device(DeviceEvent {
14185 session_generation: _,
14186 device_id: _,
14187 event: DeviceEventKind::PhoneServiceResponse { .. }
14188 }))
14189 ));
14190
14191 handle
14192 .send(Command::new(
14193 device_id.clone(),
14194 CommandAction::ShowConferenceParticipantActions {
14195 call_id: CallId(7001),
14196 conference_id: ConferenceId::new(44),
14197 participant: ConferenceListEntry {
14198 participant_id: crate::ParticipantId::new(7),
14199 name: "Taylor <T>".into(),
14200 number: "2100".into(),
14201 moderator: false,
14202 muted: false,
14203 },
14204 removable: true,
14205 demotable: false,
14206 },
14207 ))
14208 .await
14209 .unwrap();
14210 let frames =
14211 read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14212 let message = frames
14213 .into_iter()
14214 .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
14215 .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14216 .unwrap();
14217 let ServerMessage::UserToDeviceDataV1(menu) = message else {
14218 panic!("expected conference-participant action menu");
14219 };
14220 let document =
14221 ConferenceParticipantActionsDocument::from_xml(&menu.data, family).unwrap();
14222 assert_eq!(
14223 document.actions().collect::<Vec<_>>(),
14224 [
14225 ConferenceListAction::Mute {
14226 conference_id: ConferenceId::new(44),
14227 participant_id: crate::ParticipantId::new(7),
14228 },
14229 ConferenceListAction::Remove {
14230 conference_id: ConferenceId::new(44),
14231 participant_id: crate::ParticipantId::new(7),
14232 },
14233 ConferenceListAction::Promote {
14234 conference_id: ConferenceId::new(44),
14235 participant_id: crate::ParticipantId::new(7),
14236 },
14237 ]
14238 );
14239
14240 phone
14241 .write_all(
14242 &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14243 application_id: ConferenceListAction::APPLICATION_ID,
14244 line_instance: 1,
14245 call_reference: 7001,
14246 transaction_id: 44,
14247 sequence_flag: 0,
14248 display_priority: 0,
14249 conference_id: 44,
14250 application_instance_id: 1,
14251 routing: 0,
14252 data: b"conference/44/participant/7/remove".to_vec(),
14253 })
14254 .encode(protocol)
14255 .unwrap(),
14256 )
14257 .await
14258 .unwrap();
14259 assert!(matches!(
14260 events.recv().await,
14261 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ConferenceListAction {
14262 action: ConferenceListAction::Remove {
14263 conference_id,
14264 participant_id,
14265 },
14266 } })) if actual == device_id
14267 && conference_id == ConferenceId::new(44)
14268 && participant_id == crate::ParticipantId::new(7)
14269 ));
14270 assert!(matches!(
14271 events.recv().await,
14272 Some(Event::Device(DeviceEvent {
14273 session_generation: _,
14274 device_id: _,
14275 event: DeviceEventKind::PhoneServiceResponse { .. }
14276 }))
14277 ));
14278 for (route, expected) in [
14279 (
14280 b"conference/44/participant/7/mute".as_slice(),
14281 ConferenceListAction::Mute {
14282 conference_id: ConferenceId::new(44),
14283 participant_id: crate::ParticipantId::new(7),
14284 },
14285 ),
14286 (
14287 b"conference/44/participant/7/unmute".as_slice(),
14288 ConferenceListAction::Unmute {
14289 conference_id: ConferenceId::new(44),
14290 participant_id: crate::ParticipantId::new(7),
14291 },
14292 ),
14293 (
14294 b"conference/44/participant/7/promote".as_slice(),
14295 ConferenceListAction::Promote {
14296 conference_id: ConferenceId::new(44),
14297 participant_id: crate::ParticipantId::new(7),
14298 },
14299 ),
14300 (
14301 b"conference/44/participant/7/demote".as_slice(),
14302 ConferenceListAction::Demote {
14303 conference_id: ConferenceId::new(44),
14304 participant_id: crate::ParticipantId::new(7),
14305 },
14306 ),
14307 (
14308 b"conference/44/end".as_slice(),
14309 ConferenceListAction::End {
14310 conference_id: ConferenceId::new(44),
14311 },
14312 ),
14313 ] {
14314 phone
14315 .write_all(
14316 &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14317 application_id: ConferenceListAction::APPLICATION_ID,
14318 line_instance: 1,
14319 call_reference: 7001,
14320 transaction_id: 44,
14321 sequence_flag: 0,
14322 display_priority: 0,
14323 conference_id: 44,
14324 application_instance_id: 1,
14325 routing: 0,
14326 data: route.to_vec(),
14327 })
14328 .encode(protocol)
14329 .unwrap(),
14330 )
14331 .await
14332 .unwrap();
14333 assert!(matches!(
14334 events.recv().await,
14335 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ConferenceListAction {
14336 action,
14337 } })) if actual == device_id && action == expected
14338 ));
14339 assert!(matches!(
14340 events.recv().await,
14341 Some(Event::Device(DeviceEvent {
14342 session_generation: _,
14343 device_id: _,
14344 event: DeviceEventKind::PhoneServiceResponse { .. }
14345 }))
14346 ));
14347 }
14348
14349 handle
14350 .send(Command::new(
14351 device_id.clone(),
14352 CommandAction::ShowConferenceParticipantActions {
14353 call_id: CallId(7001),
14354 conference_id: ConferenceId::new(44),
14355 participant: ConferenceListEntry {
14356 participant_id: crate::ParticipantId::new(7),
14357 name: "Taylor <T>".into(),
14358 number: "2100".into(),
14359 moderator: true,
14360 muted: false,
14361 },
14362 removable: false,
14363 demotable: true,
14364 },
14365 ))
14366 .await
14367 .unwrap();
14368 let frames =
14369 read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14370 let message = frames
14371 .into_iter()
14372 .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
14373 .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
14374 .unwrap();
14375 let ServerMessage::UserToDeviceDataV1(menu) = message else {
14376 panic!("expected moderator conference-participant action menu");
14377 };
14378 let document =
14379 ConferenceParticipantActionsDocument::from_xml(&menu.data, family).unwrap();
14380 assert_eq!(
14381 document.actions().collect::<Vec<_>>(),
14382 [ConferenceListAction::Demote {
14383 conference_id: ConferenceId::new(44),
14384 participant_id: crate::ParticipantId::new(7),
14385 }]
14386 );
14387
14388 handle.shutdown().await.unwrap();
14389 task.await.unwrap().unwrap();
14390 }
14391 }
14392
14393 #[tokio::test]
14394 async fn phone_service_responses_preserve_legacy_and_extended_routing() {
14395 let config = ServerConfig {
14396 bind: "127.0.0.1:0".parse().unwrap(),
14397 advertised_address: Ipv4Addr::LOCALHOST,
14398 ..ServerConfig::default()
14399 };
14400 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14401 let address = server.local_addr().unwrap();
14402 let task = tokio::spawn(server.run());
14403 let mut phone = TcpStream::connect(address).await.unwrap();
14404 let mut decoder = FrameDecoder::new();
14405 let protocol = ProtocolVersion::V22;
14406 let device_id = DeviceId::new("SEP001122334455").unwrap();
14407
14408 phone.write_all(®ister_bytes(protocol)).await.unwrap();
14409 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14410 assert!(matches!(
14411 events.recv().await,
14412 Some(Event::Device(DeviceEvent {
14413 session_generation: _,
14414 device_id: _,
14415 event: DeviceEventKind::Registered(_)
14416 }))
14417 ));
14418
14419 phone
14420 .write_all(
14421 &ClientMessage::DeviceToUserDataResponseV1(UserDataV1Message {
14422 application_id: 9084,
14423 line_instance: 2,
14424 call_reference: 42,
14425 transaction_id: 73,
14426 sequence_flag: 1,
14427 display_priority: 2,
14428 conference_id: 51,
14429 application_instance_id: 6,
14430 routing: 4,
14431 data: br#"<CiscoIPPhoneResponse><ResponseItem Status="0" Data="ok & ready" URL="Init:Services"/></CiscoIPPhoneResponse>"#.to_vec(),
14432 })
14433 .encode(protocol)
14434 .unwrap(),
14435 )
14436 .await
14437 .unwrap();
14438 let Some(Event::Device(DeviceEvent {
14439 session_generation: _,
14440 device_id: actual,
14441 event: DeviceEventKind::PhoneServiceResponse { response },
14442 })) = events.recv().await
14443 else {
14444 panic!("expected typed execute response");
14445 };
14446 assert_eq!(actual, device_id);
14447 assert_eq!(response.kind, PhoneServiceMessageKind::Response);
14448 assert_eq!(response.routing.application_id, ApplicationId::new(9084));
14449 assert_eq!(response.routing.line_instance, LineInstance::new(2));
14450 assert_eq!(response.routing.call_reference, CallReference::new(42));
14451 assert_eq!(response.routing.transaction_id, TransactionId::new(73));
14452 assert_eq!(
14453 response.extended,
14454 Some(PhoneServiceExtendedRouting {
14455 sequence_flag: 1,
14456 display_priority: 2,
14457 conference_id: 51,
14458 application_instance_id: 6,
14459 routing: 4,
14460 })
14461 );
14462 let PhoneServicePayload::ExecuteResponse(execute) = response.payload else {
14463 panic!("expected typed execute response payload");
14464 };
14465 assert_eq!(execute.items.len(), 1);
14466 assert_eq!(execute.items[0].status.get(), 0);
14467 assert_eq!(execute.items[0].data, "ok & ready");
14468 assert_eq!(execute.items[0].url, "Init:Services");
14469
14470 phone
14471 .write_all(
14472 &ClientMessage::DeviceToUserData(crate::message::UserDataMessage {
14473 application_id: 9083,
14474 line_instance: 1,
14475 call_reference: 43,
14476 transaction_id: 74,
14477 data: b"invite?NUMBER=555%2A12&NUMBER=555%2A13&NAME=Fran%C3%A7ois".to_vec(),
14478 })
14479 .encode(protocol)
14480 .unwrap(),
14481 )
14482 .await
14483 .unwrap();
14484 let Some(Event::Device(DeviceEvent {
14485 session_generation: _,
14486 device_id: _,
14487 event: DeviceEventKind::PhoneServiceResponse { response, .. },
14488 })) = events.recv().await
14489 else {
14490 panic!("expected typed input submission");
14491 };
14492 assert_eq!(response.extended, None);
14493 assert_eq!(response.routing.application_id, ApplicationId::new(9083));
14494 assert_eq!(response.routing.line_instance, LineInstance::new(1));
14495 assert_eq!(response.routing.call_reference, CallReference::new(43));
14496 assert_eq!(response.routing.transaction_id, TransactionId::new(74));
14497 let PhoneServicePayload::Submission(submission) = response.payload else {
14498 panic!("expected typed input submission payload");
14499 };
14500 assert_eq!(submission.route, ["invite"]);
14501 assert_eq!(
14502 submission.values_named("NUMBER").collect::<Vec<_>>(),
14503 ["555*12", "555*13"]
14504 );
14505 assert_eq!(
14506 submission.values_named("NAME").collect::<Vec<_>>(),
14507 ["François"]
14508 );
14509
14510 handle.shutdown().await.unwrap();
14511 task.await.unwrap().unwrap();
14512 }
14513
14514 #[tokio::test]
14515 async fn parking_selection_requires_the_pending_envelope_and_survives_malformed_data() {
14516 let config = ServerConfig {
14517 bind: "127.0.0.1:0".parse().unwrap(),
14518 advertised_address: Ipv4Addr::LOCALHOST,
14519 ..ServerConfig::default()
14520 };
14521 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14522 let address = server.local_addr().unwrap();
14523 let task = tokio::spawn(server.run());
14524 let mut phone = TcpStream::connect(address).await.unwrap();
14525 let mut decoder = FrameDecoder::new();
14526 let protocol = ProtocolVersion::V22;
14527 let device_id = DeviceId::new("SEP001122334455").unwrap();
14528
14529 phone.write_all(®ister_bytes(protocol)).await.unwrap();
14530 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14531 assert!(matches!(
14532 events.recv().await,
14533 Some(Event::Device(DeviceEvent {
14534 session_generation: _,
14535 device_id: _,
14536 event: DeviceEventKind::Registered(_)
14537 }))
14538 ));
14539 handle
14540 .send(Command::new(
14541 device_id.clone(),
14542 CommandAction::ShowParkingMenu {
14543 instance: LineInstance::new(4),
14544 transaction_id: TransactionId(17),
14545 lot: "main".into(),
14546 calls: vec![],
14547 },
14548 ))
14549 .await
14550 .unwrap();
14551 read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
14552
14553 let response = |application_id,
14554 line_instance,
14555 call_reference,
14556 transaction_id,
14557 instance,
14558 data: &[u8]| {
14559 ClientMessage::DeviceToUserDataV1(UserDataV1Message {
14560 application_id,
14561 line_instance,
14562 call_reference,
14563 transaction_id,
14564 sequence_flag: 0,
14565 display_priority: 0,
14566 conference_id: 0,
14567 application_instance_id: instance,
14568 routing: 0,
14569 data: data.to_vec(),
14570 })
14571 .encode(protocol)
14572 .unwrap()
14573 };
14574
14575 for (application_id, line_instance, call_reference, transaction_id, instance) in [
14576 (9083, 4, 0, 17, 4),
14577 (PARKING_APPLICATION_ID, 5, 0, 17, 4),
14578 (PARKING_APPLICATION_ID, 4, 9, 17, 4),
14579 (PARKING_APPLICATION_ID, 4, 0, 18, 4),
14580 (PARKING_APPLICATION_ID, 4, 0, 17, 5),
14581 ] {
14582 phone
14583 .write_all(&response(
14584 application_id,
14585 line_instance,
14586 call_reference,
14587 transaction_id,
14588 instance,
14589 b"retrieve/main/701",
14590 ))
14591 .await
14592 .unwrap();
14593 let Some(Event::Device(DeviceEvent {
14594 session_generation: _,
14595 device_id: _,
14596 event:
14597 DeviceEventKind::PhoneServiceResponse {
14598 response: routed, ..
14599 },
14600 })) = events.recv().await
14601 else {
14602 panic!("expected mismatched response to remain generically routed");
14603 };
14604 assert_eq!(routed.routing.application_id.get(), application_id);
14605 assert_eq!(routed.routing.line_instance.get(), line_instance);
14606 assert_eq!(routed.routing.call_reference.get(), call_reference);
14607 assert_eq!(routed.routing.transaction_id.get(), transaction_id);
14608 assert!(
14609 tokio::time::timeout(Duration::from_millis(25), events.recv())
14610 .await
14611 .is_err(),
14612 "mismatched envelope emitted a parking action"
14613 );
14614 }
14615
14616 phone
14617 .write_all(&response(
14618 PARKING_APPLICATION_ID,
14619 4,
14620 0,
14621 17,
14622 4,
14623 b"retrieve/secret%GG/701",
14624 ))
14625 .await
14626 .unwrap();
14627 let Some(Event::ProtocolWarning {
14628 message_id, error, ..
14629 }) = events.recv().await
14630 else {
14631 panic!("expected malformed service-data warning");
14632 };
14633 assert_eq!(message_id, id::DEVICE_TO_USER_DATA_V1);
14634 assert!(!error.contains("secret"));
14635
14636 phone
14637 .write_all(&response(
14638 PARKING_APPLICATION_ID,
14639 4,
14640 0,
14641 17,
14642 4,
14643 b"retrieve/main/701",
14644 ))
14645 .await
14646 .unwrap();
14647 assert!(matches!(
14648 events.recv().await,
14649 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual, event: DeviceEventKind::ParkingMenuSelection {
14650 lot,
14651 slot: 701,
14652 } })) if actual == device_id && lot == "main"
14653 ));
14654 assert!(matches!(
14655 events.recv().await,
14656 Some(Event::Device(DeviceEvent {
14657 session_generation: _,
14658 device_id: _,
14659 event: DeviceEventKind::PhoneServiceResponse { .. }
14660 }))
14661 ));
14662
14663 phone
14664 .write_all(&response(
14665 PARKING_APPLICATION_ID,
14666 4,
14667 0,
14668 17,
14669 4,
14670 b"retrieve/main/701",
14671 ))
14672 .await
14673 .unwrap();
14674 assert!(matches!(
14675 events.recv().await,
14676 Some(Event::Device(DeviceEvent {
14677 session_generation: _,
14678 device_id: _,
14679 event: DeviceEventKind::PhoneServiceResponse { .. }
14680 }))
14681 ));
14682 assert!(
14683 tokio::time::timeout(Duration::from_millis(25), events.recv())
14684 .await
14685 .is_err(),
14686 "replayed selection emitted a second parking action"
14687 );
14688
14689 handle.shutdown().await.unwrap();
14690 task.await.unwrap().unwrap();
14691 }
14692
14693 #[test]
14694 fn parking_menu_xml_is_typed_round_trippable_and_size_bounded() {
14695 let calls = [ParkingMenuEntry {
14696 slot: 701,
14697 caller_name: "Taylor <T> & Co".into(),
14698 caller_number: "2100".into(),
14699 connected_name: "Desk".into(),
14700 connected_number: "1001".into(),
14701 }];
14702 let xml = parking_menu_xml(4, 17, "east & west", &calls).unwrap();
14703 assert!(xml.contains("Taylor <T> & Co"));
14704 let decoded = CiscoIpPhoneMenu::from_xml_with_limit(xml.as_bytes(), 2_000).unwrap();
14705 assert_eq!(decoded.title.as_deref(), Some("Parked calls - east & west"));
14706 assert_eq!(decoded.items.len(), 1);
14707 assert_eq!(
14708 decoded.items[0].url.as_deref(),
14709 Some("UserCallData:9090:4:0:17:retrieve/east%20%26%20west/701")
14710 );
14711
14712 let oversized = [ParkingMenuEntry {
14713 caller_name: "x".repeat(2100),
14714 ..calls[0].clone()
14715 }];
14716 assert!(matches!(
14717 parking_menu_xml(4, 17, "main", &oversized),
14718 Err(ServerError::PhoneXml(PhoneXmlError::InvalidField {
14719 field: "menu item name",
14720 ..
14721 }))
14722 ));
14723 let byte_oversized = vec![
14724 ParkingMenuEntry {
14725 caller_name: "x".repeat(45),
14726 ..calls[0].clone()
14727 };
14728 PARKING_MENU_MAX_ITEMS
14729 ];
14730 assert!(matches!(
14731 parking_menu_xml(4, 17, "main", &byte_oversized),
14732 Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
14733 kind: "phone XML document",
14734 maximum: 2_000,
14735 ..
14736 }))
14737 ));
14738 assert!(matches!(
14739 parking_menu_xml(
14740 4,
14741 17,
14742 "main",
14743 &vec![calls[0].clone(); PARKING_MENU_MAX_ITEMS + 1]
14744 ),
14745 Err(ServerError::PhoneXml(error)) if error.to_string().contains("maximum is 32")
14746 ));
14747 assert!(
14748 CiscoIpPhoneMenu::from_xml_with_limit(b"<CiscoIPPhoneMenu><Title>broken", 2_000,)
14749 .is_err()
14750 );
14751
14752 #[derive(Debug)]
14753 struct FailingWriter;
14754
14755 impl std::fmt::Write for FailingWriter {
14756 fn write_str(&mut self, _value: &str) -> std::fmt::Result {
14757 Err(std::fmt::Error)
14758 }
14759 }
14760
14761 assert!(matches!(
14762 phone_xml::to_writer(FailingWriter, &decoded, 2_000),
14763 Err(PhoneXmlError::Write(_))
14764 ));
14765 }
14766
14767 #[tokio::test]
14768 async fn begin_call_creates_the_reserved_retrieval_identity() {
14769 let config = ServerConfig {
14770 bind: "127.0.0.1:0".parse().unwrap(),
14771 advertised_address: Ipv4Addr::LOCALHOST,
14772 ..ServerConfig::default()
14773 };
14774 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14775 let address = server.local_addr().unwrap();
14776 let task = tokio::spawn(server.run());
14777 let mut phone = TcpStream::connect(address).await.unwrap();
14778 let mut decoder = FrameDecoder::new();
14779 let protocol = ProtocolVersion::V22;
14780 let device_id = DeviceId::new("SEP001122334455").unwrap();
14781
14782 phone.write_all(®ister_bytes(protocol)).await.unwrap();
14783 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14784 assert!(matches!(
14785 events.recv().await,
14786 Some(Event::Device(DeviceEvent {
14787 session_generation: _,
14788 device_id: _,
14789 event: DeviceEventKind::Registered(_)
14790 }))
14791 ));
14792 handle
14793 .send(Command::new(
14794 device_id.clone(),
14795 CommandAction::BeginCall {
14796 line_instance: LineInstance(1),
14797 call_id: CallId(7001),
14798 codec: Codec::Pcma,
14799 },
14800 ))
14801 .await
14802 .unwrap();
14803 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
14804 assert!(frames.into_iter().any(|frame| matches!(
14805 ServerMessage::decode(frame, protocol),
14806 Ok(ServerMessage::CallState {
14807 state: CallState::OffHook,
14808 line_instance: 1,
14809 call_reference: 7001,
14810 })
14811 )));
14812
14813 let info = CallInfo {
14814 direction: crate::types::CallDirection::Inbound,
14815 calling_name: "Caller".into(),
14816 calling_number: "2100".into(),
14817 called_name: "Park 701".into(),
14818 called_number: "701".into(),
14819 original_called_name: "Reception".into(),
14820 original_called_number: "2000".into(),
14821 last_redirecting_name: "Front Desk".into(),
14822 last_redirecting_number: "2050".into(),
14823 original_redirect_reason: 2,
14824 last_redirect_reason: 4,
14825 party_restrictions: 0xf,
14826 };
14827 handle
14828 .send(Command::new(
14829 device_id,
14830 CommandAction::SetCallInfo {
14831 call_id: CallId(7001),
14832 info: info.clone(),
14833 },
14834 ))
14835 .await
14836 .unwrap();
14837 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_INFO_DYNAMIC).await;
14838 assert!(frames.into_iter().any(|frame| matches!(
14839 ServerMessage::decode(frame, protocol),
14840 Ok(ServerMessage::CallInfo {
14841 info: actual,
14842 line_instance: 1,
14843 call_reference: 7001,
14844 }) if actual == info
14845 )));
14846
14847 handle.shutdown().await.unwrap();
14848 task.await.unwrap().unwrap();
14849 }
14850
14851 #[tokio::test]
14852 async fn unavailable_soft_key_events_and_stimuli_preserve_on_hook_state() {
14853 let mut device = definition();
14854 device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::Redial]);
14855 let config = ServerConfig {
14856 bind: "127.0.0.1:0".parse().unwrap(),
14857 advertised_address: Ipv4Addr::LOCALHOST,
14858 ..ServerConfig::default()
14859 };
14860 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
14861 let address = server.local_addr().unwrap();
14862 let task = tokio::spawn(server.run());
14863 let mut phone = TcpStream::connect(address).await.unwrap();
14864 let mut decoder = FrameDecoder::new();
14865 let protocol = ProtocolVersion::V22;
14866
14867 phone.write_all(®ister_bytes(protocol)).await.unwrap();
14868 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14869 assert!(matches!(
14870 events.recv().await,
14871 Some(Event::Device(DeviceEvent {
14872 session_generation: _,
14873 device_id: _,
14874 event: DeviceEventKind::Registered(_)
14875 }))
14876 ));
14877
14878 phone
14879 .write_all(
14880 &[
14881 ClientMessage::SoftKeyEvent {
14882 event: SoftKey::NewCall.wire_value(),
14883 line_instance: 1,
14884 call_reference: 0,
14885 }
14886 .encode(protocol)
14887 .unwrap(),
14888 ClientMessage::Stimulus {
14889 stimulus: Stimulus::NewCall,
14890 instance: 1,
14891 call_reference: 0,
14892 status: 0,
14893 }
14894 .encode(protocol)
14895 .unwrap(),
14896 ]
14897 .concat(),
14898 )
14899 .await
14900 .unwrap();
14901 let mut buffer = [0_u8; 256];
14902 assert!(
14903 tokio::time::timeout(Duration::from_millis(50), phone.read(&mut buffer))
14904 .await
14905 .is_err(),
14906 "unavailable actions unexpectedly changed the handset UI"
14907 );
14908 assert!(
14909 tokio::time::timeout(Duration::from_millis(50), events.recv())
14910 .await
14911 .is_err(),
14912 "unavailable actions unexpectedly emitted an application event"
14913 );
14914
14915 phone
14916 .write_all(
14917 &ClientMessage::Stimulus {
14918 stimulus: Stimulus::Line,
14919 instance: 1,
14920 call_reference: 0,
14921 status: 0,
14922 }
14923 .encode(protocol)
14924 .unwrap(),
14925 )
14926 .await
14927 .unwrap();
14928 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
14929 assert!(matches!(
14930 events.recv().await,
14931 Some(Event::Device(DeviceEvent {
14932 session_generation: _,
14933 device_id: _,
14934 event: DeviceEventKind::OffHook {
14935 call_id: CallId(1),
14936 ..
14937 }
14938 }))
14939 ));
14940
14941 handle.shutdown().await.unwrap();
14942 task.await.unwrap().unwrap();
14943 }
14944
14945 #[tokio::test]
14946 async fn one_way_intercom_uses_restricted_keys_active_identity_and_microphone_frame() {
14947 let config = ServerConfig {
14948 bind: "127.0.0.1:0".parse().unwrap(),
14949 advertised_address: Ipv4Addr::LOCALHOST,
14950 ..ServerConfig::default()
14951 };
14952 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
14953 let address = server.local_addr().unwrap();
14954 let task = tokio::spawn(server.run());
14955 let mut phone = TcpStream::connect(address).await.unwrap();
14956 let mut decoder = FrameDecoder::new();
14957 let protocol = ProtocolVersion::V22;
14958
14959 phone.write_all(®ister_bytes(protocol)).await.unwrap();
14960 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
14961 assert!(matches!(
14962 events.recv().await,
14963 Some(Event::Device(DeviceEvent {
14964 session_generation: _,
14965 device_id: _,
14966 event: DeviceEventKind::Registered(_)
14967 }))
14968 ));
14969
14970 let device_id = DeviceId::new("SEP001122334455").unwrap();
14971 let call_id = CallId(7010);
14972 handle
14973 .send(Command::new(
14974 device_id.clone(),
14975 CommandAction::BeginCall {
14976 line_instance: LineInstance(1),
14977 call_id,
14978 codec: Codec::Pcma,
14979 },
14980 ))
14981 .await
14982 .unwrap();
14983 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
14984 handle
14985 .send(Command::new(
14986 device_id.clone(),
14987 CommandAction::SetCallState {
14988 call_id,
14989 state: CallState::IntercomOneWay,
14990 },
14991 ))
14992 .await
14993 .unwrap();
14994 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
14995 assert!(frames.iter().any(|frame| matches!(
14996 ServerMessage::decode(frame.clone(), protocol),
14997 Ok(ServerMessage::CallState {
14998 state: CallState::IntercomOneWay,
14999 line_instance: 1,
15000 call_reference: 7010,
15001 })
15002 )));
15003 assert!(frames.iter().any(|frame| matches!(
15004 ServerMessage::decode(frame.clone(), protocol),
15005 Ok(ServerMessage::SelectSoftKeys {
15006 line_instance: 1,
15007 call_reference: 7010,
15008 set: KeyMode::OffHook,
15009 valid_mask: 1,
15010 })
15011 )));
15012
15013 phone
15014 .write_all(
15015 &ClientMessage::SoftKeyEvent {
15016 event: SoftKey::NewCall.wire_value(),
15017 line_instance: 1,
15018 call_reference: 0,
15019 }
15020 .encode(protocol)
15021 .unwrap(),
15022 )
15023 .await
15024 .unwrap();
15025 assert!(
15026 tokio::time::timeout(Duration::from_millis(50), events.recv())
15027 .await
15028 .is_err()
15029 );
15030 phone
15031 .write_all(
15032 &ClientMessage::SoftKeyEvent {
15033 event: SoftKey::EndCall.wire_value(),
15034 line_instance: 1,
15035 call_reference: 0,
15036 }
15037 .encode(protocol)
15038 .unwrap(),
15039 )
15040 .await
15041 .unwrap();
15042 assert!(matches!(
15043 events.recv().await,
15044 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::SoftKey {
15045 call_id: Some(CallId(7010)),
15046 line_instance: LineInstance(1),
15047 soft_key: SoftKey::EndCall,
15048 } })) if actual_device == device_id
15049 ));
15050
15051 handle
15052 .send_confirmed(Command::new(
15053 device_id,
15054 CommandAction::SetMicrophoneMode { enabled: false },
15055 ))
15056 .await
15057 .unwrap();
15058 let frames = read_until_message(&mut phone, &mut decoder, id::SET_MICROPHONE_MODE).await;
15059 assert!(frames.into_iter().any(|frame| matches!(
15060 ServerMessage::decode(frame, protocol),
15061 Ok(ServerMessage::SetMicrophoneMode(MicrophoneMode::Off))
15062 )));
15063
15064 handle.shutdown().await.unwrap();
15065 task.await.unwrap().unwrap();
15066 }
15067
15068 #[tokio::test]
15069 async fn outbound_media_writes_receive_then_transmit_without_an_ack_boundary() {
15070 let device = definition();
15071 let device_id = device.id.clone();
15072 let config = ServerConfig {
15073 bind: "127.0.0.1:0".parse().unwrap(),
15074 advertised_address: Ipv4Addr::LOCALHOST,
15075 ..ServerConfig::default()
15076 };
15077 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15078 let address = server.local_addr().unwrap();
15079 let task = tokio::spawn(server.run());
15080 let mut phone = TcpStream::connect(address).await.unwrap();
15081 let mut decoder = FrameDecoder::new();
15082 let protocol = ProtocolVersion::V22;
15083
15084 phone.write_all(®ister_bytes(protocol)).await.unwrap();
15085 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15086 assert!(matches!(
15087 events.recv().await,
15088 Some(Event::Device(DeviceEvent {
15089 session_generation: _,
15090 device_id: _,
15091 event: DeviceEventKind::Registered(_)
15092 }))
15093 ));
15094 phone
15095 .write_all(
15096 &ClientMessage::Stimulus {
15097 stimulus: Stimulus::Line,
15098 instance: 1,
15099 call_reference: 0,
15100 status: 0,
15101 }
15102 .encode(protocol)
15103 .unwrap(),
15104 )
15105 .await
15106 .unwrap();
15107 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15108 assert!(matches!(
15109 events.recv().await,
15110 Some(Event::Device(DeviceEvent {
15111 session_generation: _,
15112 device_id: _,
15113 event: DeviceEventKind::OffHook { .. }
15114 }))
15115 ));
15116
15117 phone
15118 .write_all(
15119 &ClientMessage::KeypadButton {
15120 button: Digit::Number(2),
15121 line_instance: 1,
15122 call_reference: 1,
15123 wire_layout: None,
15124 }
15125 .encode(protocol)
15126 .unwrap(),
15127 )
15128 .await
15129 .unwrap();
15130 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15131 assert!(matches!(
15132 events.recv().await,
15133 Some(Event::Device(DeviceEvent {
15134 session_generation: _,
15135 device_id: _,
15136 event: DeviceEventKind::Digit { .. }
15137 }))
15138 ));
15139 phone
15140 .write_all(
15141 &ClientMessage::KeypadButton {
15142 button: Digit::Pound,
15143 line_instance: 1,
15144 call_reference: 1,
15145 wire_layout: None,
15146 }
15147 .encode(protocol)
15148 .unwrap(),
15149 )
15150 .await
15151 .unwrap();
15152 assert!(matches!(
15153 events.recv().await,
15154 Some(Event::Device(DeviceEvent {
15155 session_generation: _,
15156 device_id: _,
15157 event: DeviceEventKind::Digit {
15158 digit: Digit::Pound,
15159 ..
15160 }
15161 }))
15162 ));
15163 handle
15164 .send_confirmed(Command::new(
15165 device_id.clone(),
15166 CommandAction::CommitOutboundCall {
15167 call_id: CallId(1),
15168 info: CallInfo {
15169 direction: crate::CallDirection::Outbound,
15170 called_number: "2".into(),
15171 ..CallInfo::default()
15172 },
15173 },
15174 ))
15175 .await
15176 .unwrap();
15177 let prefix = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
15178 let stop_tone = prefix
15179 .iter()
15180 .position(|frame| frame.message_id == id::STOP_TONE)
15181 .expect("outbound route prefix omitted StopTone");
15182 let call_info = prefix
15183 .iter()
15184 .position(|frame| frame.message_id == id::CALL_INFO_DYNAMIC)
15185 .expect("outbound route prefix omitted CallInfo");
15186 let dialed_number = prefix
15187 .iter()
15188 .position(|frame| {
15189 matches!(
15190 ServerMessage::decode(frame.clone(), protocol),
15191 Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "2"
15192 )
15193 })
15194 .expect("outbound route prefix omitted DialedNumber");
15195 let proceed = prefix
15196 .iter()
15197 .position(|frame| {
15198 matches!(
15199 ServerMessage::decode(frame.clone(), protocol),
15200 Ok(ServerMessage::CallState {
15201 state: CallState::Proceed,
15202 ..
15203 })
15204 )
15205 })
15206 .expect("outbound media prefix omitted Proceed");
15207 assert!(stop_tone < call_info && call_info < dialed_number && dialed_number < proceed);
15208 assert!(prefix[..proceed].iter().all(|frame| {
15209 !matches!(
15210 ServerMessage::decode(frame.clone(), protocol),
15211 Ok(ServerMessage::CallState {
15212 state: CallState::OffHook,
15213 ..
15214 })
15215 ) && frame.message_id != id::ACTIVATE_CALL_PLANE
15216 }));
15217
15218 let outbound_info = CallInfo {
15219 direction: crate::CallDirection::Outbound,
15220 called_name: "Remote Party".into(),
15221 called_number: "2".into(),
15222 ..CallInfo::default()
15223 };
15224 handle
15225 .send_confirmed(Command::new(
15226 device_id.clone(),
15227 CommandAction::PresentOutboundProceeding {
15228 call_id: CallId(1),
15229 info: outbound_info.clone(),
15230 },
15231 ))
15232 .await
15233 .unwrap();
15234 let proceeding =
15235 read_until_message(&mut phone, &mut decoder, id::DISPLAY_DYNAMIC_PROMPT_STATUS).await;
15236 let proceeding_ids = proceeding
15237 .iter()
15238 .map(|frame| frame.message_id)
15239 .collect::<Vec<_>>();
15240 let stop = proceeding_ids
15241 .iter()
15242 .position(|message_id| *message_id == id::STOP_TONE)
15243 .unwrap();
15244 let state = proceeding_ids
15245 .iter()
15246 .position(|message_id| *message_id == id::CALL_STATE)
15247 .unwrap();
15248 let info = proceeding_ids
15249 .iter()
15250 .position(|message_id| *message_id == id::CALL_INFO_DYNAMIC)
15251 .unwrap();
15252 let prompt = proceeding_ids
15253 .iter()
15254 .position(|message_id| *message_id == id::DISPLAY_DYNAMIC_PROMPT_STATUS)
15255 .unwrap();
15256 assert!(stop < state && state < info && info < prompt);
15257
15258 handle
15259 .send_confirmed(Command::new(
15260 device_id.clone(),
15261 CommandAction::PresentOutboundRinging {
15262 call_id: CallId(1),
15263 info: outbound_info,
15264 },
15265 ))
15266 .await
15267 .unwrap();
15268 let ringing = read_until_message(&mut phone, &mut decoder, id::CALL_INFO_DYNAMIC).await;
15269 let ringing_ids = ringing
15270 .iter()
15271 .map(|frame| frame.message_id)
15272 .collect::<Vec<_>>();
15273 let state = ringing_ids
15274 .iter()
15275 .position(|message_id| *message_id == id::CALL_STATE)
15276 .unwrap();
15277 let prompt = ringing_ids
15278 .iter()
15279 .position(|message_id| *message_id == id::DISPLAY_DYNAMIC_PROMPT_STATUS)
15280 .unwrap();
15281 let tone = ringing_ids
15282 .iter()
15283 .position(|message_id| *message_id == id::START_TONE)
15284 .unwrap();
15285 let keys = ringing_ids
15286 .iter()
15287 .position(|message_id| *message_id == id::SELECT_SOFT_KEYS)
15288 .unwrap();
15289 let info = ringing_ids
15290 .iter()
15291 .position(|message_id| *message_id == id::CALL_INFO_DYNAMIC)
15292 .unwrap();
15293 assert!(state < prompt && prompt < tone && tone < keys && keys < info);
15294 assert_eq!(
15295 ringing
15296 .iter()
15297 .filter(|frame| frame.message_id == id::DISPLAY_DYNAMIC_PROMPT_STATUS)
15298 .count(),
15299 1,
15300 "outbound ringing flashed an intermediate prompt"
15301 );
15302
15303 let endpoint = MediaEndpoint {
15304 address: "198.51.100.20".parse().unwrap(),
15305 rtp_port: 6000,
15306 rtcp_port: 6001,
15307 codec: Codec::Pcma,
15308 packet_ms: 20,
15309 max_frames_per_packet: 1,
15310 telephone_event_payload: 0,
15311 };
15312 handle
15313 .send_confirmed(Command::new(
15314 device_id.clone(),
15315 CommandAction::OpenOutboundMedia {
15316 call_id: CallId(1),
15317 source: None,
15318 endpoint,
15319 codec: Codec::Pcma,
15320 packet_ms: 20,
15321 max_frames_per_packet: 1,
15322 dtmf_mode: DtmfMode::Auto,
15323 audio_processing: AudioProcessingPolicy::default(),
15324 traffic_class: MediaTrafficClass::default(),
15325 },
15326 ))
15327 .await
15328 .unwrap();
15329 let frames =
15330 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15331 let receive = frames
15332 .iter()
15333 .position(|frame| frame.message_id == id::OPEN_RECEIVE_CHANNEL)
15334 .expect("coupled transaction omitted OpenReceiveChannel");
15335 let transmit = frames
15336 .iter()
15337 .position(|frame| frame.message_id == id::START_MEDIA_TRANSMISSION)
15338 .expect("coupled transaction omitted StartMediaTransmission");
15339 let first_request_party = coupled_media_request_party(&frames, protocol);
15340 assert_eq!(transmit, receive + 1);
15341 assert!(matches!(
15342 ServerMessage::decode(frames[receive].clone(), protocol).unwrap(),
15343 ServerMessage::OpenReceiveChannel {
15344 source_address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
15345 source_port: 0,
15346 codec: Codec::Pcma,
15347 ..
15348 }
15349 ));
15350 assert!(matches!(
15351 ServerMessage::decode(frames[transmit].clone(), protocol).unwrap(),
15352 ServerMessage::StartMediaTransmission {
15353 endpoint: actual,
15354 ..
15355 } if actual.address == endpoint.address
15356 && actual.rtp_port == endpoint.rtp_port
15357 && actual.codec == endpoint.codec
15358 ));
15359
15360 let receive_peer = MediaEndpoint {
15361 address: "192.0.2.44".parse().unwrap(),
15362 rtp_port: 4000,
15363 rtcp_port: 4001,
15364 codec: Codec::Pcma,
15365 packet_ms: 20,
15366 max_frames_per_packet: 1,
15367 telephone_event_payload: 0,
15368 };
15369 phone
15370 .write_all(
15371 &ClientMessage::OpenReceiveChannelAck {
15372 status: MediaStatus::Ok,
15373 address: receive_peer.address,
15374 port: receive_peer.rtp_port,
15375 call_reference: 1,
15376 passthrough_party_id: first_request_party,
15377 }
15378 .encode(protocol)
15379 .unwrap(),
15380 )
15381 .await
15382 .unwrap();
15383 assert!(matches!(
15384 events.recv().await,
15385 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::ReceiveChannelOpened {
15386 call_id: CallId(1),
15387 status: MediaStatus::Ok,
15388 endpoint: actual,
15389 ..
15390 } })) if actual == receive_peer
15391 ));
15392 assert!(matches!(
15393 events.recv().await,
15394 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelImplied {
15395 call_id: CallId(1),
15396 endpoint: actual,
15397 ..
15398 } })) if actual == endpoint
15399 ));
15400
15401 phone
15402 .write_all(
15403 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15404 conference_id: 1,
15405 passthrough_party_id: first_request_party,
15406 call_reference: 1,
15407 status: MediaStatus::Ok,
15408 address: endpoint.address,
15409 port: endpoint.rtp_port,
15410 wire: None,
15411 })
15412 .encode(protocol)
15413 .unwrap(),
15414 )
15415 .await
15416 .unwrap();
15417 assert!(
15418 tokio::time::timeout(Duration::from_millis(50), events.recv())
15419 .await
15420 .is_err(),
15421 "late explicit transmit acknowledgement re-settled coupled media"
15422 );
15423
15424 handle
15425 .send(Command::new(
15426 device_id.clone(),
15427 CommandAction::OpenOutboundMedia {
15428 call_id: CallId(1),
15429 source: None,
15430 endpoint,
15431 codec: Codec::Pcma,
15432 packet_ms: 20,
15433 max_frames_per_packet: 1,
15434 dtmf_mode: DtmfMode::Auto,
15435 audio_processing: AudioProcessingPolicy::default(),
15436 traffic_class: MediaTrafficClass::default(),
15437 },
15438 ))
15439 .await
15440 .unwrap();
15441 let frames =
15442 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15443 let second_request_party = coupled_media_request_party(&frames, protocol);
15444 assert_ne!(second_request_party, first_request_party);
15445 phone
15446 .write_all(
15447 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15448 conference_id: 1,
15449 passthrough_party_id: first_request_party,
15450 call_reference: 1,
15451 status: MediaStatus::Ok,
15452 address: endpoint.address,
15453 port: endpoint.rtp_port,
15454 wire: None,
15455 })
15456 .encode(protocol)
15457 .unwrap(),
15458 )
15459 .await
15460 .unwrap();
15461 assert!(
15462 tokio::time::timeout(Duration::from_millis(50), events.recv())
15463 .await
15464 .is_err(),
15465 "a prior media generation settled the reopened transmit request"
15466 );
15467 phone
15468 .write_all(
15469 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15470 conference_id: 1,
15471 passthrough_party_id: second_request_party,
15472 call_reference: 1,
15473 status: MediaStatus::Ok,
15474 address: endpoint.address,
15475 port: endpoint.rtp_port,
15476 wire: None,
15477 })
15478 .encode(protocol)
15479 .unwrap(),
15480 )
15481 .await
15482 .unwrap();
15483 assert!(matches!(
15484 events.recv().await,
15485 Some(Event::Device(DeviceEvent {
15486 session_generation: _,
15487 device_id: _,
15488 event: DeviceEventKind::TransmitChannelStarted {
15489 call_id: CallId(1),
15490 status: MediaStatus::Ok,
15491 ..
15492 }
15493 }))
15494 ));
15495 phone
15496 .write_all(
15497 &ClientMessage::OpenReceiveChannelAck {
15498 status: MediaStatus::Ok,
15499 address: receive_peer.address,
15500 port: receive_peer.rtp_port,
15501 call_reference: 1,
15502 passthrough_party_id: second_request_party,
15503 }
15504 .encode(protocol)
15505 .unwrap(),
15506 )
15507 .await
15508 .unwrap();
15509 assert!(matches!(
15510 events.recv().await,
15511 Some(Event::Device(DeviceEvent {
15512 session_generation: _,
15513 device_id: _,
15514 event: DeviceEventKind::ReceiveChannelOpened {
15515 call_id: CallId(1),
15516 status: MediaStatus::Ok,
15517 ..
15518 }
15519 }))
15520 ));
15521 assert!(
15522 tokio::time::timeout(Duration::from_millis(50), events.recv())
15523 .await
15524 .is_err(),
15525 "receive acknowledgement duplicated an explicitly settled transmit event"
15526 );
15527
15528 handle
15529 .send(Command::new(
15530 device_id.clone(),
15531 CommandAction::OpenOutboundMedia {
15532 call_id: CallId(1),
15533 source: None,
15534 endpoint,
15535 codec: Codec::Pcma,
15536 packet_ms: 20,
15537 max_frames_per_packet: 1,
15538 dtmf_mode: DtmfMode::Auto,
15539 audio_processing: AudioProcessingPolicy::default(),
15540 traffic_class: MediaTrafficClass::default(),
15541 },
15542 ))
15543 .await
15544 .unwrap();
15545 let frames =
15546 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15547 let third_request_party = coupled_media_request_party(&frames, protocol);
15548 assert_ne!(third_request_party, second_request_party);
15549 phone
15550 .write_all(
15551 &ClientMessage::OpenReceiveChannelAck {
15552 status: MediaStatus::UnspecifiedError,
15553 address: receive_peer.address,
15554 port: receive_peer.rtp_port,
15555 call_reference: 1,
15556 passthrough_party_id: third_request_party,
15557 }
15558 .encode(protocol)
15559 .unwrap(),
15560 )
15561 .await
15562 .unwrap();
15563 assert!(matches!(
15564 events.recv().await,
15565 Some(Event::Device(DeviceEvent {
15566 session_generation: _,
15567 device_id: _,
15568 event: DeviceEventKind::ReceiveChannelOpened {
15569 call_id: CallId(1),
15570 status: MediaStatus::UnspecifiedError,
15571 ..
15572 }
15573 }))
15574 ));
15575 assert!(
15576 tokio::time::timeout(Duration::from_millis(50), events.recv())
15577 .await
15578 .is_err(),
15579 "failed coupled receive emitted a transmit-success event"
15580 );
15581 phone
15582 .write_all(
15583 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15584 conference_id: 1,
15585 passthrough_party_id: third_request_party,
15586 call_reference: 1,
15587 status: MediaStatus::Ok,
15588 address: endpoint.address,
15589 port: endpoint.rtp_port,
15590 wire: None,
15591 })
15592 .encode(protocol)
15593 .unwrap(),
15594 )
15595 .await
15596 .unwrap();
15597 assert!(
15598 tokio::time::timeout(Duration::from_millis(50), events.recv())
15599 .await
15600 .is_err(),
15601 "late transmit acknowledgement resurrected a failed coupled transaction"
15602 );
15603
15604 handle
15605 .send(Command::new(
15606 device_id,
15607 CommandAction::OpenOutboundMedia {
15608 call_id: CallId(1),
15609 source: None,
15610 endpoint,
15611 codec: Codec::Pcma,
15612 packet_ms: 20,
15613 max_frames_per_packet: 1,
15614 dtmf_mode: DtmfMode::Auto,
15615 audio_processing: AudioProcessingPolicy::default(),
15616 traffic_class: MediaTrafficClass::default(),
15617 },
15618 ))
15619 .await
15620 .unwrap();
15621 let frames =
15622 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15623 let fourth_request_party = coupled_media_request_party(&frames, protocol);
15624 assert_ne!(fourth_request_party, third_request_party);
15625 phone
15626 .write_all(
15627 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
15628 conference_id: 1,
15629 passthrough_party_id: fourth_request_party,
15630 call_reference: 1,
15631 status: MediaStatus::UnspecifiedError,
15632 address: endpoint.address,
15633 port: endpoint.rtp_port,
15634 wire: None,
15635 })
15636 .encode(protocol)
15637 .unwrap(),
15638 )
15639 .await
15640 .unwrap();
15641 assert!(matches!(
15642 events.recv().await,
15643 Some(Event::Device(DeviceEvent {
15644 session_generation: _,
15645 device_id: _,
15646 event: DeviceEventKind::TransmitChannelStarted {
15647 call_id: CallId(1),
15648 status: MediaStatus::UnspecifiedError,
15649 ..
15650 }
15651 }))
15652 ));
15653 phone
15654 .write_all(
15655 &ClientMessage::OpenReceiveChannelAck {
15656 status: MediaStatus::Ok,
15657 address: receive_peer.address,
15658 port: receive_peer.rtp_port,
15659 call_reference: 1,
15660 passthrough_party_id: fourth_request_party,
15661 }
15662 .encode(protocol)
15663 .unwrap(),
15664 )
15665 .await
15666 .unwrap();
15667 assert!(
15668 tokio::time::timeout(Duration::from_millis(50), events.recv())
15669 .await
15670 .is_err(),
15671 "late receive acknowledgement resurrected a failed coupled transaction"
15672 );
15673
15674 handle.shutdown().await.unwrap();
15675 task.await.unwrap().unwrap();
15676 }
15677
15678 #[tokio::test]
15679 async fn invalid_coupled_media_is_rejected_without_disconnect() {
15680 let device = definition();
15681 let device_id = device.id.clone();
15682 let config = ServerConfig {
15683 bind: "127.0.0.1:0".parse().unwrap(),
15684 advertised_address: Ipv4Addr::LOCALHOST,
15685 ..ServerConfig::default()
15686 };
15687 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15688 let address = server.local_addr().unwrap();
15689 let task = tokio::spawn(server.run());
15690 let mut phone = TcpStream::connect(address).await.unwrap();
15691 let mut decoder = FrameDecoder::new();
15692 let protocol = ProtocolVersion::V22;
15693
15694 phone.write_all(®ister_bytes(protocol)).await.unwrap();
15695 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15696 assert!(matches!(
15697 events.recv().await,
15698 Some(Event::Device(DeviceEvent {
15699 session_generation: _,
15700 device_id: _,
15701 event: DeviceEventKind::Registered(_)
15702 }))
15703 ));
15704 phone
15705 .write_all(
15706 &ClientMessage::Stimulus {
15707 stimulus: Stimulus::Line,
15708 instance: 1,
15709 call_reference: 0,
15710 status: 0,
15711 }
15712 .encode(protocol)
15713 .unwrap(),
15714 )
15715 .await
15716 .unwrap();
15717 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15718 assert!(matches!(
15719 events.recv().await,
15720 Some(Event::Device(DeviceEvent {
15721 session_generation: _,
15722 device_id: _,
15723 event: DeviceEventKind::OffHook { .. }
15724 }))
15725 ));
15726
15727 let endpoint = MediaEndpoint {
15728 address: "198.51.100.20".parse().unwrap(),
15729 rtp_port: 6000,
15730 rtcp_port: 6001,
15731 codec: Codec::Pcma,
15732 packet_ms: 20,
15733 max_frames_per_packet: 1,
15734 telephone_event_payload: 0,
15735 };
15736 assert!(matches!(
15737 handle
15738 .send_confirmed(Command::new(device_id.clone(), CommandAction::OpenOutboundMedia {
15739 call_id: CallId(1),
15740 source: None,
15741 endpoint,
15742 codec: Codec::Pcma,
15743 packet_ms: 20,
15744 max_frames_per_packet: 1,
15745 dtmf_mode: DtmfMode::Auto,
15746 audio_processing: AudioProcessingPolicy::default(),
15747 traffic_class: MediaTrafficClass::default(),
15748 }))
15749 .await,
15750 Err(ServerError::CommandWrite(message))
15751 if message.contains("cannot open coupled outbound media while in state OffHook")
15752 ));
15753 assert!(!task.is_finished());
15754
15755 handle
15756 .send_confirmed(Command::new(
15757 device_id,
15758 CommandAction::SetCallState {
15759 call_id: CallId(1),
15760 state: CallState::Proceed,
15761 },
15762 ))
15763 .await
15764 .unwrap();
15765 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15766 assert!(frames.iter().any(|frame| matches!(
15767 ServerMessage::decode(frame.clone(), protocol),
15768 Ok(ServerMessage::CallState {
15769 state: CallState::Proceed,
15770 ..
15771 })
15772 )));
15773
15774 handle.shutdown().await.unwrap();
15775 task.await.unwrap().unwrap();
15776 }
15777
15778 #[tokio::test]
15779 async fn stale_public_command_does_not_stop_the_listener() {
15780 let device = definition();
15781 let config = ServerConfig {
15782 bind: "127.0.0.1:0".parse().unwrap(),
15783 advertised_address: Ipv4Addr::LOCALHOST,
15784 ..ServerConfig::default()
15785 };
15786 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15787 let address = server.local_addr().unwrap();
15788 let task = tokio::spawn(server.run());
15789
15790 handle
15791 .send(Command::new(
15792 DeviceId::new("SEPFFFFFFFFFFFF").unwrap(),
15793 CommandAction::SetMwi {
15794 line_instance: LineInstance(1),
15795 enabled: true,
15796 },
15797 ))
15798 .await
15799 .unwrap();
15800 tokio::task::yield_now().await;
15801 assert!(!task.is_finished());
15802
15803 let protocol = ProtocolVersion::V22;
15804 let mut phone = TcpStream::connect(address).await.unwrap();
15805 let mut decoder = FrameDecoder::new();
15806 phone.write_all(®ister_bytes(protocol)).await.unwrap();
15807 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15808 assert!(matches!(
15809 events.recv().await,
15810 Some(Event::Device(DeviceEvent {
15811 session_generation: _,
15812 device_id: _,
15813 event: DeviceEventKind::Registered(_)
15814 }))
15815 ));
15816
15817 handle.shutdown().await.unwrap();
15818 task.await.unwrap().unwrap();
15819 }
15820
15821 #[tokio::test]
15822 async fn configured_dtmf_mode_selects_rtp_or_signaling_without_duplicate_digits() {
15823 let device = definition();
15824 let device_id = device.id.clone();
15825 let config = ServerConfig {
15826 bind: "127.0.0.1:0".parse().unwrap(),
15827 advertised_address: Ipv4Addr::LOCALHOST,
15828 ..ServerConfig::default()
15829 };
15830 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
15831 let address = server.local_addr().unwrap();
15832 let task = tokio::spawn(server.run());
15833 let mut phone = TcpStream::connect(address).await.unwrap();
15834 let mut decoder = FrameDecoder::new();
15835 let protocol = ProtocolVersion::V22;
15836
15837 phone
15838 .write_all(®ister_bytes_with_features(
15839 protocol,
15840 PhoneFeatures::RFC2833,
15841 ))
15842 .await
15843 .unwrap();
15844 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
15845 assert!(matches!(
15846 events.recv().await,
15847 Some(Event::Device(DeviceEvent {
15848 session_generation: _,
15849 device_id: _,
15850 event: DeviceEventKind::Registered(_)
15851 }))
15852 ));
15853
15854 phone
15855 .write_all(
15856 &ClientMessage::Stimulus {
15857 stimulus: Stimulus::Line,
15858 instance: 1,
15859 call_reference: 0,
15860 status: 0,
15861 }
15862 .encode(protocol)
15863 .unwrap(),
15864 )
15865 .await
15866 .unwrap();
15867 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15868 assert!(matches!(
15869 events.recv().await,
15870 Some(Event::Device(DeviceEvent {
15871 session_generation: _,
15872 device_id: _,
15873 event: DeviceEventKind::OffHook {
15874 call_id: CallId(1),
15875 ..
15876 }
15877 }))
15878 ));
15879
15880 handle
15881 .send(Command::new(
15882 device_id.clone(),
15883 CommandAction::SetCallState {
15884 call_id: CallId(1),
15885 state: CallState::Connected,
15886 },
15887 ))
15888 .await
15889 .unwrap();
15890 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
15891 handle
15892 .send(Command::new(
15893 device_id.clone(),
15894 CommandAction::OpenReceiveChannel {
15895 call_id: CallId(1),
15896 source: Some(MediaEndpoint {
15897 address: "192.0.2.1".parse().unwrap(),
15898 rtp_port: 4000,
15899 rtcp_port: 4001,
15900 codec: Codec::Pcmu,
15901 packet_ms: 20,
15902 max_frames_per_packet: 1,
15903 telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
15904 }),
15905 codec: Codec::Pcmu,
15906 packet_ms: 20,
15907 max_frames_per_packet: 1,
15908 dtmf_mode: DtmfMode::Auto,
15909 audio_processing: AudioProcessingPolicy {
15910 echo_cancellation: crate::EchoCancellation::Off,
15911 silence_suppression: crate::SilenceSuppression::On,
15912 },
15913 },
15914 ))
15915 .await
15916 .unwrap();
15917 let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
15918 assert_eq!(
15919 frames
15920 .iter()
15921 .filter(|frame| frame.message_id == id::SUBSCRIBE_DTMF_PAYLOAD_REQ)
15922 .count(),
15923 0,
15924 "RFC2833 is negotiated in the media messages, not with an unsolicited subscription"
15925 );
15926 handle
15927 .send(Command::new(
15928 device_id.clone(),
15929 CommandAction::StopMedia { call_id: CallId(1) },
15930 ))
15931 .await
15932 .unwrap();
15933 let frame = frames
15934 .into_iter()
15935 .find(|frame| frame.message_id == id::OPEN_RECEIVE_CHANNEL)
15936 .unwrap();
15937 assert!(matches!(
15938 ServerMessage::decode(frame, protocol).unwrap(),
15939 ServerMessage::OpenReceiveChannel {
15940 echo_cancellation: crate::EchoCancellation::Off,
15941 telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
15942 source_address,
15943 source_port: 4000,
15944 ..
15945 } if source_address == "192.0.2.1".parse::<std::net::IpAddr>().unwrap()
15946 ));
15947 handle
15948 .send(Command::new(
15949 device_id.clone(),
15950 CommandAction::StartMedia {
15951 call_id: CallId(1),
15952 endpoint: MediaEndpoint {
15953 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
15954 rtp_port: 4000,
15955 rtcp_port: 4001,
15956 codec: Codec::Pcmu,
15957 packet_ms: 20,
15958 max_frames_per_packet: 1,
15959 telephone_event_payload: 0,
15960 },
15961 dtmf_mode: DtmfMode::Auto,
15962 audio_processing: AudioProcessingPolicy {
15963 echo_cancellation: crate::EchoCancellation::Off,
15964 silence_suppression: crate::SilenceSuppression::On,
15965 },
15966 traffic_class: MediaTrafficClass::default(),
15967 },
15968 ))
15969 .await
15970 .unwrap();
15971 let frames =
15972 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
15973 assert!(
15974 frames
15975 .iter()
15976 .all(|frame| frame.message_id != id::SUBSCRIBE_DTMF_PAYLOAD_REQ),
15977 "starting the second media direction resubscribed RFC2833"
15978 );
15979 let start_media_party = start_media_request_party(&frames, protocol);
15980 let frame = frames
15981 .into_iter()
15982 .find(|frame| frame.message_id == id::START_MEDIA_TRANSMISSION)
15983 .unwrap();
15984 assert!(matches!(
15985 ServerMessage::decode(frame, protocol).unwrap(),
15986 ServerMessage::StartMediaTransmission {
15987 silence_suppression: crate::SilenceSuppression::On,
15988 endpoint: MediaEndpoint {
15989 telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
15990 ..
15991 },
15992 ..
15993 }
15994 ));
15995
15996 phone
15997 .write_all(
15998 &ClientMessage::KeypadButton {
15999 button: Digit::Number(4),
16000 line_instance: 1,
16001 call_reference: 1,
16002 wire_layout: None,
16003 }
16004 .encode(protocol)
16005 .unwrap(),
16006 )
16007 .await
16008 .unwrap();
16009 assert!(matches!(
16010 events.recv().await,
16011 Some(Event::Device(DeviceEvent {
16012 session_generation: _,
16013 device_id: _,
16014 event: DeviceEventKind::Digit {
16015 call_id: CallId(1),
16016 digit: Digit::Number(4),
16017 ..
16018 }
16019 }))
16020 ));
16021
16022 phone
16023 .write_all(
16024 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16025 conference_id: 99,
16026 passthrough_party_id: start_media_party,
16027 call_reference: 1,
16028 status: MediaStatus::Ok,
16029 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
16030 port: 4998,
16031 wire: None,
16032 })
16033 .encode(protocol)
16034 .unwrap(),
16035 )
16036 .await
16037 .unwrap();
16038 assert!(
16039 tokio::time::timeout(Duration::from_millis(50), events.recv())
16040 .await
16041 .is_err(),
16042 "mismatched conference identifier was correlated to a call"
16043 );
16044
16045 phone
16046 .write_all(
16047 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16048 conference_id: 1,
16049 passthrough_party_id: start_media_party.saturating_add(1),
16050 call_reference: 1,
16051 status: MediaStatus::Ok,
16052 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
16053 port: 4999,
16054 wire: None,
16055 })
16056 .encode(protocol)
16057 .unwrap(),
16058 )
16059 .await
16060 .unwrap();
16061 assert!(
16062 tokio::time::timeout(Duration::from_millis(50), events.recv())
16063 .await
16064 .is_err(),
16065 "mismatched media identifiers were correlated to a call"
16066 );
16067
16068 phone
16069 .write_all(
16070 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16071 conference_id: 0,
16072 passthrough_party_id: start_media_party,
16073 call_reference: 1,
16074 status: MediaStatus::Ok,
16075 address: "192.168.10.20".parse().unwrap(),
16076 port: 4000,
16077 wire: None,
16078 })
16079 .encode(protocol)
16080 .unwrap(),
16081 )
16082 .await
16083 .unwrap();
16084 assert!(matches!(
16085 events.recv().await,
16086 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelStarted {
16087 call_id: CallId(1),
16088 status: MediaStatus::Ok,
16089 endpoint: MediaEndpoint {
16090 address,
16091 rtp_port: 4000,
16092 telephone_event_payload: RFC2833_TELEPHONE_EVENT_PAYLOAD,
16093 ..
16094 },
16095 ..
16096 } })) if address == "192.168.10.20".parse::<IpAddr>().unwrap()
16097 ));
16098
16099 phone
16100 .write_all(
16101 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16102 conference_id: 1,
16103 passthrough_party_id: start_media_party,
16104 call_reference: 1,
16105 status: MediaStatus::Ok,
16106 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
16107 port: 4000,
16108 wire: None,
16109 })
16110 .encode(protocol)
16111 .unwrap(),
16112 )
16113 .await
16114 .unwrap();
16115 assert!(
16116 tokio::time::timeout(Duration::from_millis(50), events.recv())
16117 .await
16118 .is_err(),
16119 "duplicate transmit acknowledgement emitted a second event"
16120 );
16121
16122 let failed_address = "192.168.10.20".parse().unwrap();
16123 phone
16124 .write_all(
16125 &ClientMessage::MediaTransmissionFailure {
16126 conference_id: 99,
16127 passthrough_party_id: start_media_party,
16128 address: failed_address,
16129 port: 4000,
16130 call_reference: 1,
16131 status: MediaStatus::UnspecifiedError,
16132 }
16133 .encode(protocol)
16134 .unwrap(),
16135 )
16136 .await
16137 .unwrap();
16138 assert!(
16139 tokio::time::timeout(Duration::from_millis(50), events.recv())
16140 .await
16141 .is_err(),
16142 "mismatched conference identifier emitted a media failure"
16143 );
16144 let failure = ClientMessage::MediaTransmissionFailure {
16145 conference_id: 1,
16146 passthrough_party_id: start_media_party,
16147 address: failed_address,
16148 port: 4000,
16149 call_reference: 1,
16150 status: MediaStatus::UnspecifiedError,
16151 };
16152 phone
16153 .write_all(&failure.encode(protocol).unwrap())
16154 .await
16155 .unwrap();
16156 assert!(matches!(
16157 events.recv().await,
16158 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::MediaTransmissionFailed {
16159 call_id: CallId(1),
16160 status: MediaStatus::UnspecifiedError,
16161 endpoint: MediaEndpoint {
16162 address,
16163 rtp_port: 4000,
16164 ..
16165 },
16166 ..
16167 } })) if address == failed_address
16168 ));
16169 phone
16170 .write_all(&failure.encode(protocol).unwrap())
16171 .await
16172 .unwrap();
16173 assert!(
16174 tokio::time::timeout(Duration::from_millis(50), events.recv())
16175 .await
16176 .is_err(),
16177 "duplicate media failure emitted a second event"
16178 );
16179
16180 let recovery_address = IpAddr::V4(Ipv4Addr::LOCALHOST);
16181 handle
16182 .send(Command::new(
16183 device_id.clone(),
16184 CommandAction::StartMedia {
16185 call_id: CallId(1),
16186 endpoint: MediaEndpoint {
16187 address: recovery_address,
16188 rtp_port: 5000,
16189 rtcp_port: 5001,
16190 codec: Codec::Pcmu,
16191 packet_ms: 20,
16192 max_frames_per_packet: 1,
16193 telephone_event_payload: 0,
16194 },
16195 dtmf_mode: DtmfMode::Auto,
16196 audio_processing: AudioProcessingPolicy::default(),
16197 traffic_class: MediaTrafficClass::default(),
16198 },
16199 ))
16200 .await
16201 .unwrap();
16202 let frames =
16203 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
16204 let recovery_media_party = start_media_request_party(&frames, protocol);
16205 assert_ne!(recovery_media_party, start_media_party);
16206 phone
16207 .write_all(
16208 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
16209 conference_id: 1,
16210 passthrough_party_id: recovery_media_party,
16211 call_reference: 1,
16212 status: MediaStatus::Ok,
16213 address: recovery_address,
16214 port: 5000,
16215 wire: None,
16216 })
16217 .encode(protocol)
16218 .unwrap(),
16219 )
16220 .await
16221 .unwrap();
16222 assert!(matches!(
16223 events.recv().await,
16224 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelStarted {
16225 call_id: CallId(1),
16226 status: MediaStatus::Ok,
16227 endpoint: MediaEndpoint {
16228 address,
16229 rtp_port: 5000,
16230 ..
16231 },
16232 ..
16233 } })) if address == recovery_address
16234 ));
16235
16236 phone
16237 .write_all(
16238 &ClientMessage::KeypadButton {
16239 button: Digit::Number(5),
16240 line_instance: 1,
16241 call_reference: 1,
16242 wire_layout: None,
16243 }
16244 .encode(protocol)
16245 .unwrap(),
16246 )
16247 .await
16248 .unwrap();
16249 assert!(
16250 tokio::time::timeout(Duration::from_millis(50), events.recv())
16251 .await
16252 .is_err(),
16253 "acknowledged RTP DTMF also emitted a signaling digit"
16254 );
16255
16256 handle
16257 .send(Command::new(
16258 device_id.clone(),
16259 CommandAction::OpenReceiveChannel {
16260 call_id: CallId(1),
16261 source: Some(MediaEndpoint {
16262 address: "192.0.2.1".parse().unwrap(),
16263 rtp_port: 4000,
16264 rtcp_port: 4001,
16265 codec: Codec::Pcmu,
16266 packet_ms: 20,
16267 max_frames_per_packet: 1,
16268 telephone_event_payload: 0,
16269 }),
16270 codec: Codec::Pcmu,
16271 packet_ms: 20,
16272 max_frames_per_packet: 1,
16273 dtmf_mode: DtmfMode::Skinny,
16274 audio_processing: AudioProcessingPolicy::default(),
16275 },
16276 ))
16277 .await
16278 .unwrap();
16279 let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
16280 assert_eq!(
16281 frames
16282 .iter()
16283 .filter(|frame| frame.message_id == id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ)
16284 .count(),
16285 0,
16286 "changing one media direction unsubscribed the remaining RFC2833 stream"
16287 );
16288 let frame = frames
16289 .into_iter()
16290 .find(|frame| frame.message_id == id::OPEN_RECEIVE_CHANNEL)
16291 .unwrap();
16292 assert!(matches!(
16293 ServerMessage::decode(frame, protocol).unwrap(),
16294 ServerMessage::OpenReceiveChannel {
16295 telephone_event_payload: 0,
16296 ..
16297 }
16298 ));
16299 phone
16300 .write_all(
16301 &ClientMessage::KeypadButton {
16302 button: Digit::Number(6),
16303 line_instance: 1,
16304 call_reference: 1,
16305 wire_layout: None,
16306 }
16307 .encode(protocol)
16308 .unwrap(),
16309 )
16310 .await
16311 .unwrap();
16312 assert!(
16313 tokio::time::timeout(Duration::from_millis(50), events.recv())
16314 .await
16315 .is_err(),
16316 "the remaining RTP direction also emitted a signaling digit"
16317 );
16318
16319 handle
16320 .send(Command::new(
16321 device_id.clone(),
16322 CommandAction::StopMedia { call_id: CallId(1) },
16323 ))
16324 .await
16325 .unwrap();
16326 let frames =
16327 read_until_message(&mut phone, &mut decoder, id::STOP_MEDIA_TRANSMISSION).await;
16328 assert!(
16329 frames
16330 .iter()
16331 .all(|frame| frame.message_id != id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ)
16332 );
16333 handle
16334 .send(Command::new(
16335 device_id.clone(),
16336 CommandAction::StopMedia { call_id: CallId(1) },
16337 ))
16338 .await
16339 .unwrap();
16340 phone
16341 .write_all(
16342 &ClientMessage::KeypadButton {
16343 button: Digit::Number(7),
16344 line_instance: 1,
16345 call_reference: 1,
16346 wire_layout: None,
16347 }
16348 .encode(protocol)
16349 .unwrap(),
16350 )
16351 .await
16352 .unwrap();
16353 assert!(matches!(
16354 events.recv().await,
16355 Some(Event::Device(DeviceEvent {
16356 session_generation: _,
16357 device_id: _,
16358 event: DeviceEventKind::Digit {
16359 call_id: CallId(1),
16360 digit: Digit::Number(7),
16361 ..
16362 }
16363 }))
16364 ));
16365
16366 handle
16367 .send(Command::new(
16368 device_id.clone(),
16369 CommandAction::CloseReceiveChannel { call_id: CallId(1) },
16370 ))
16371 .await
16372 .unwrap();
16373 let frames = read_until_message(&mut phone, &mut decoder, id::CLOSE_RECEIVE_CHANNEL).await;
16374 assert_eq!(
16375 frames
16376 .iter()
16377 .filter(|frame| frame.message_id == id::CLOSE_RECEIVE_CHANNEL)
16378 .count(),
16379 1
16380 );
16381 handle
16382 .send(Command::new(
16383 device_id.clone(),
16384 CommandAction::CloseReceiveChannel { call_id: CallId(1) },
16385 ))
16386 .await
16387 .unwrap();
16388 handle
16389 .send(Command::new(
16390 device_id.clone(),
16391 CommandAction::CloseCall { call_id: CallId(1) },
16392 ))
16393 .await
16394 .unwrap();
16395 let frames = read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
16396 assert!(frames.iter().all(|frame| !matches!(
16397 frame.message_id,
16398 id::STOP_MEDIA_TRANSMISSION | id::CLOSE_RECEIVE_CHANNEL
16399 )));
16400
16401 handle.shutdown().await.unwrap();
16402 task.await.unwrap().unwrap();
16403 }
16404
16405 #[test]
16406 fn handset_acknowledgement_deadlines_are_bounded_ordered_and_exactly_once() {
16407 let now = Instant::now();
16408 let mut first = session_call(20);
16409 first.media.receive.state = MediaChannelState::Opening;
16410 first.media.receive.deadline = Some(now);
16411 first.media.transmit.state = MediaChannelState::Opening;
16412 first.media.transmit.deadline = Some(now + Duration::from_millis(1));
16413 first.media.coupled_transmit_endpoint = Some(MediaEndpoint {
16414 address: "198.51.100.20".parse().unwrap(),
16415 rtp_port: 6000,
16416 rtcp_port: 6001,
16417 codec: Codec::Pcmu,
16418 packet_ms: 20,
16419 max_frames_per_packet: 1,
16420 telephone_event_payload: 0,
16421 });
16422 let mut second = session_call(10);
16423 second.media.transmit.state = MediaChannelState::Opening;
16424 second.media.transmit.deadline = Some(now);
16425 let mut calls = HashMap::from([(first.call_id, first), (second.call_id, second)]);
16426
16427 assert_eq!(
16428 expire_handset_acknowledgements(&mut calls, now),
16429 [
16430 (CallId(10), HandsetAcknowledgement::StartMediaTransmission,),
16431 (CallId(20), HandsetAcknowledgement::OpenReceiveChannel),
16432 ]
16433 );
16434 assert_eq!(
16435 calls[&CallId(10)].media.transmit.state,
16436 MediaChannelState::Closed
16437 );
16438 assert_eq!(
16439 calls[&CallId(20)].media.receive.state,
16440 MediaChannelState::Closed
16441 );
16442 assert_eq!(
16443 calls[&CallId(20)].media.transmit.state,
16444 MediaChannelState::Closed
16445 );
16446 assert!(calls[&CallId(20)].media.coupled_transmit_endpoint.is_none());
16447 assert!(expire_handset_acknowledgements(&mut calls, now).is_empty());
16448 assert!(
16449 expire_handset_acknowledgements(&mut calls, now + Duration::from_millis(1)).is_empty()
16450 );
16451 assert!(
16452 expire_handset_acknowledgements(&mut calls, now + Duration::from_secs(1)).is_empty()
16453 );
16454 }
16455
16456 #[tokio::test]
16457 async fn unknown_device_type_receives_the_configured_generic_layout() {
16458 let device = mixed_definition();
16459 let expected = button_template(&device);
16460 let config = ServerConfig {
16461 bind: "127.0.0.1:0".parse().unwrap(),
16462 advertised_address: Ipv4Addr::LOCALHOST,
16463 ..ServerConfig::default()
16464 };
16465 let (server, handle, _events) = Server::bind(config, [device]).await.unwrap();
16466 let address = server.local_addr().unwrap();
16467 let task = tokio::spawn(server.run());
16468 let mut phone = TcpStream::connect(address).await.unwrap();
16469 let mut decoder = FrameDecoder::new();
16470
16471 phone
16472 .write_all(®ister_bytes_for_device_type(
16473 ProtocolVersion::V22,
16474 0xffff_fffe,
16475 ))
16476 .await
16477 .unwrap();
16478 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16479 phone
16480 .write_all(
16481 &ClientMessage::ButtonTemplateRequest
16482 .encode(ProtocolVersion::V22)
16483 .unwrap(),
16484 )
16485 .await
16486 .unwrap();
16487 let frames = read_until_message(&mut phone, &mut decoder, id::BUTTON_TEMPLATE).await;
16488 let frame = frames
16489 .into_iter()
16490 .find(|frame| frame.message_id == id::BUTTON_TEMPLATE)
16491 .unwrap();
16492 assert_eq!(
16493 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
16494 ServerMessage::ButtonTemplate { buttons: expected }
16495 );
16496
16497 handle.shutdown().await.unwrap();
16498 task.await.unwrap().unwrap();
16499 }
16500
16501 #[tokio::test]
16502 async fn on_hook_enbloc_creates_one_addressable_call_before_atomic_routing() {
16503 let config = ServerConfig {
16504 bind: "127.0.0.1:0".parse().unwrap(),
16505 advertised_address: Ipv4Addr::LOCALHOST,
16506 ..ServerConfig::default()
16507 };
16508 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
16509 let address = server.local_addr().unwrap();
16510 let task = tokio::spawn(server.run());
16511 let mut phone = TcpStream::connect(address).await.unwrap();
16512 let mut decoder = FrameDecoder::new();
16513 let protocol = ProtocolVersion::V22;
16514
16515 phone.write_all(®ister_bytes(protocol)).await.unwrap();
16516 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16517 assert!(matches!(
16518 events.recv().await,
16519 Some(Event::Device(DeviceEvent {
16520 session_generation: _,
16521 device_id: _,
16522 event: DeviceEventKind::Registered(_)
16523 }))
16524 ));
16525
16526 phone
16527 .write_all(
16528 &ClientMessage::EnblocCall {
16529 called_party: "8675309".into(),
16530 line_instance: 1,
16531 }
16532 .encode(protocol)
16533 .unwrap(),
16534 )
16535 .await
16536 .unwrap();
16537 let initial = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16538 assert!(initial.iter().any(|frame| {
16539 matches!(
16540 ServerMessage::decode(frame.clone(), protocol),
16541 Ok(ServerMessage::CallState {
16542 state: CallState::OffHook,
16543 ..
16544 })
16545 )
16546 }));
16547 assert!(
16548 initial
16549 .iter()
16550 .all(|frame| frame.message_id != id::DIALED_NUMBER)
16551 );
16552
16553 let call_id = match events.recv().await {
16554 Some(Event::Device(DeviceEvent {
16555 session_generation: _,
16556 device_id: _,
16557 event:
16558 DeviceEventKind::OffHook {
16559 call_id,
16560 line_instance: LineInstance(1),
16561 ..
16562 },
16563 })) => call_id,
16564 event => {
16565 panic!("expected addressable off-hook call before en-bloc routing, got {event:?}")
16566 }
16567 };
16568 assert!(matches!(
16569 events.recv().await,
16570 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall {
16571 call_id: routed_call_id,
16572 line_instance: LineInstance(1),
16573 ref number,
16574 ..
16575 } })) if routed_call_id == call_id && number == "8675309"
16576 ));
16577 handle
16578 .send_confirmed(Command::new(
16579 DeviceId::new("SEP001122334455").unwrap(),
16580 CommandAction::CommitOutboundCall {
16581 call_id,
16582 info: CallInfo {
16583 direction: crate::CallDirection::Outbound,
16584 called_number: "8675309".into(),
16585 ..CallInfo::default()
16586 },
16587 },
16588 ))
16589 .await
16590 .unwrap();
16591 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16592 assert_eq!(
16593 frames
16594 .iter()
16595 .filter(|frame| frame.message_id == id::DIALED_NUMBER)
16596 .count(),
16597 1
16598 );
16599 assert!(frames.iter().any(|frame| matches!(
16600 ServerMessage::decode(frame.clone(), protocol),
16601 Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "8675309"
16602 )));
16603
16604 handle.shutdown().await.unwrap();
16605 task.await.unwrap().unwrap();
16606 }
16607
16608 #[tokio::test]
16609 async fn redial_reuses_the_last_completed_number_on_the_selected_line() {
16610 let config = ServerConfig {
16611 bind: "127.0.0.1:0".parse().unwrap(),
16612 advertised_address: Ipv4Addr::LOCALHOST,
16613 ..ServerConfig::default()
16614 };
16615 let mut device = definition();
16616 device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::Redial, SoftKey::NewCall]);
16617 let ButtonDefinition::Line(line) = &mut device.buttons[0] else {
16618 panic!("test station lost its line button");
16619 };
16620 line.initial_tone = Tone::RecallDial;
16621 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
16622 let address = server.local_addr().unwrap();
16623 let task = tokio::spawn(server.run());
16624 let mut phone = TcpStream::connect(address).await.unwrap();
16625 let mut decoder = FrameDecoder::new();
16626 let protocol = ProtocolVersion::V22;
16627
16628 phone.write_all(®ister_bytes(protocol)).await.unwrap();
16629 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16630 assert!(matches!(
16631 events.recv().await,
16632 Some(Event::Device(DeviceEvent {
16633 session_generation: _,
16634 device_id: _,
16635 event: DeviceEventKind::Registered(_)
16636 }))
16637 ));
16638
16639 phone
16640 .write_all(
16641 &ClientMessage::OffHook {
16642 line_instance: 1,
16643 call_reference: 0,
16644 }
16645 .encode(protocol)
16646 .unwrap(),
16647 )
16648 .await
16649 .unwrap();
16650 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16651 let speaker = frames
16652 .iter()
16653 .position(|frame| {
16654 matches!(
16655 ServerMessage::decode(frame.clone(), protocol),
16656 Ok(ServerMessage::SetSpeakerMode(SpeakerMode::On))
16657 )
16658 })
16659 .expect("physical OffHook did not enable the speaker");
16660 let line_lamp = frames
16661 .iter()
16662 .position(|frame| {
16663 matches!(
16664 ServerMessage::decode(frame.clone(), protocol),
16665 Ok(ServerMessage::SetLamp {
16666 stimulus: ButtonType::Line,
16667 mode: LampMode::On,
16668 ..
16669 })
16670 )
16671 })
16672 .expect("physical OffHook did not enable the line lamp");
16673 let off_hook = frames
16674 .iter()
16675 .position(|frame| {
16676 matches!(
16677 ServerMessage::decode(frame.clone(), protocol),
16678 Ok(ServerMessage::CallState {
16679 state: CallState::OffHook,
16680 ..
16681 })
16682 )
16683 })
16684 .expect("physical OffHook did not publish OffHook");
16685 let activate = frames
16686 .iter()
16687 .position(|frame| frame.message_id == id::ACTIVATE_CALL_PLANE)
16688 .expect("physical OffHook did not activate the call plane");
16689 let prompt = frames
16690 .iter()
16691 .position(|frame| {
16692 matches!(
16693 ServerMessage::decode(frame.clone(), protocol),
16694 Ok(ServerMessage::DisplayPrompt { ref text, .. }) if text == "Enter number"
16695 )
16696 })
16697 .expect("physical OffHook did not prompt for digits");
16698 let dial_tone = frames
16699 .iter()
16700 .position(|frame| {
16701 matches!(
16702 ServerMessage::decode(frame.clone(), protocol),
16703 Ok(ServerMessage::StartTone {
16704 tone: Tone::RecallDial,
16705 ..
16706 })
16707 )
16708 })
16709 .expect("physical OffHook did not start dial tone");
16710 let soft_keys = frames
16711 .iter()
16712 .position(|frame| frame.message_id == id::SELECT_SOFT_KEYS)
16713 .expect("physical OffHook did not select off-hook keys");
16714 assert!(
16715 speaker < line_lamp
16716 && line_lamp < off_hook
16717 && off_hook < activate
16718 && activate < prompt
16719 && prompt < dial_tone
16720 && dial_tone < soft_keys
16721 );
16722 let call_reference = frames
16723 .iter()
16724 .find_map(
16725 |frame| match ServerMessage::decode(frame.clone(), protocol) {
16726 Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
16727 _ => None,
16728 },
16729 )
16730 .unwrap();
16731 let first_call_id = match events.recv().await {
16732 Some(Event::Device(DeviceEvent {
16733 session_generation: _,
16734 device_id: _,
16735 event: DeviceEventKind::OffHook { call_id, .. },
16736 })) => call_id,
16737 event => panic!("unexpected first redial OffHook event: {event:?}"),
16738 };
16739
16740 phone
16741 .write_all(
16742 &ClientMessage::EnblocCall {
16743 called_party: "5551212".into(),
16744 line_instance: 1,
16745 }
16746 .encode(protocol)
16747 .unwrap(),
16748 )
16749 .await
16750 .unwrap();
16751 assert!(matches!(
16752 events.recv().await,
16753 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall { ref number, .. } })) if number == "5551212"
16754 ));
16755 handle
16756 .send_confirmed(Command::new(
16757 DeviceId::new("SEP001122334455").unwrap(),
16758 CommandAction::CommitOutboundCall {
16759 call_id: first_call_id,
16760 info: CallInfo {
16761 direction: crate::CallDirection::Outbound,
16762 called_number: "5551212".into(),
16763 ..CallInfo::default()
16764 },
16765 },
16766 ))
16767 .await
16768 .unwrap();
16769 read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16770
16771 phone
16772 .write_all(
16773 &ClientMessage::OnHook {
16774 line_instance: 1,
16775 call_reference,
16776 }
16777 .encode(protocol)
16778 .unwrap(),
16779 )
16780 .await
16781 .unwrap();
16782 read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
16783 assert!(matches!(
16784 events.recv().await,
16785 Some(Event::Device(DeviceEvent {
16786 session_generation: _,
16787 device_id: _,
16788 event: DeviceEventKind::OnHook { .. }
16789 }))
16790 ));
16791
16792 phone
16793 .write_all(
16794 &ClientMessage::SoftKeyEvent {
16795 event: SoftKey::Redial.wire_value(),
16796 line_instance: 1,
16797 call_reference: 0,
16798 }
16799 .encode(protocol)
16800 .unwrap(),
16801 )
16802 .await
16803 .unwrap();
16804 let initial = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16805 let redial_call_reference =
16806 initial.iter().find_map(
16807 |frame| match ServerMessage::decode(frame.clone(), protocol) {
16808 Ok(ServerMessage::CallState {
16809 state: CallState::OffHook,
16810 call_reference,
16811 ..
16812 }) => Some(call_reference),
16813 _ => None,
16814 },
16815 );
16816 let redial_call_id = match events.recv().await {
16817 Some(Event::Device(DeviceEvent {
16818 session_generation: _,
16819 device_id: _,
16820 event: DeviceEventKind::OffHook { call_id, .. },
16821 })) => call_id,
16822 event => panic!("unexpected redial OffHook event: {event:?}"),
16823 };
16824 assert!(matches!(
16825 events.recv().await,
16826 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall { ref number, .. } })) if number == "5551212"
16827 ));
16828 handle
16829 .send_confirmed(Command::new(
16830 DeviceId::new("SEP001122334455").unwrap(),
16831 CommandAction::CommitOutboundCall {
16832 call_id: redial_call_id,
16833 info: CallInfo {
16834 direction: crate::CallDirection::Outbound,
16835 called_number: "5551212".into(),
16836 ..CallInfo::default()
16837 },
16838 },
16839 ))
16840 .await
16841 .unwrap();
16842 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16843 assert!(frames.into_iter().any(|frame| matches!(
16844 ServerMessage::decode(frame, protocol),
16845 Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "5551212"
16846 )));
16847
16848 phone
16849 .write_all(
16850 &ClientMessage::OnHook {
16851 line_instance: 1,
16852 call_reference: redial_call_reference.unwrap(),
16853 }
16854 .encode(protocol)
16855 .unwrap(),
16856 )
16857 .await
16858 .unwrap();
16859 read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
16860 assert!(matches!(
16861 events.recv().await,
16862 Some(Event::Device(DeviceEvent {
16863 session_generation: _,
16864 device_id: _,
16865 event: DeviceEventKind::OnHook { .. }
16866 }))
16867 ));
16868
16869 phone
16870 .write_all(
16871 &ClientMessage::Stimulus {
16872 stimulus: Stimulus::LastNumberRedial,
16873 instance: 1,
16874 call_reference: 0,
16875 status: 0,
16876 }
16877 .encode(protocol)
16878 .unwrap(),
16879 )
16880 .await
16881 .unwrap();
16882 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
16883 let stimulus_call_id = match events.recv().await {
16884 Some(Event::Device(DeviceEvent {
16885 session_generation: _,
16886 device_id: _,
16887 event: DeviceEventKind::OffHook { call_id, .. },
16888 })) => call_id,
16889 event => panic!("unexpected stimulus-redial OffHook event: {event:?}"),
16890 };
16891 assert!(matches!(
16892 events.recv().await,
16893 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::EnblocCall { ref number, .. } })) if number == "5551212"
16894 ));
16895 handle
16896 .send_confirmed(Command::new(
16897 DeviceId::new("SEP001122334455").unwrap(),
16898 CommandAction::CommitOutboundCall {
16899 call_id: stimulus_call_id,
16900 info: CallInfo {
16901 direction: crate::CallDirection::Outbound,
16902 called_number: "5551212".into(),
16903 ..CallInfo::default()
16904 },
16905 },
16906 ))
16907 .await
16908 .unwrap();
16909 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
16910 assert!(frames.into_iter().any(|frame| matches!(
16911 ServerMessage::decode(frame, protocol),
16912 Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "5551212"
16913 )));
16914
16915 handle.shutdown().await.unwrap();
16916 task.await.unwrap().unwrap();
16917 }
16918
16919 #[tokio::test]
16920 async fn configured_redial_menu_uses_typed_native_action_with_legacy_fallback_policy() {
16921 assert!(!placed_calls_menu_supported(ProtocolVersion::V3));
16922 assert!(placed_calls_menu_supported(ProtocolVersion::V8));
16923 assert!(placed_calls_menu_supported(ProtocolVersion::V22));
16924
16925 let config = ServerConfig {
16926 bind: "127.0.0.1:0".parse().unwrap(),
16927 advertised_address: Ipv4Addr::LOCALHOST,
16928 ..ServerConfig::default()
16929 };
16930 let mut device = definition();
16931 device.soft_keys = profile_with(KeyMode::OnHook, vec![SoftKey::Redial]);
16932 device.ui.placed_calls_redial_menu = true;
16933 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
16934 let address = server.local_addr().unwrap();
16935 let task = tokio::spawn(server.run());
16936 let mut phone = TcpStream::connect(address).await.unwrap();
16937 let mut decoder = FrameDecoder::new();
16938 let protocol = ProtocolVersion::V22;
16939
16940 phone.write_all(®ister_bytes(protocol)).await.unwrap();
16941 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
16942 assert!(matches!(
16943 events.recv().await,
16944 Some(Event::Device(DeviceEvent {
16945 session_generation: _,
16946 device_id: _,
16947 event: DeviceEventKind::Registered(_)
16948 }))
16949 ));
16950
16951 phone
16952 .write_all(
16953 &ClientMessage::SoftKeyEvent {
16954 event: SoftKey::Redial.wire_value(),
16955 line_instance: 1,
16956 call_reference: 0,
16957 }
16958 .encode(protocol)
16959 .unwrap(),
16960 )
16961 .await
16962 .unwrap();
16963 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
16964 let message = frames
16965 .into_iter()
16966 .find_map(|frame| match ServerMessage::decode(frame, protocol) {
16967 Ok(ServerMessage::UserToDeviceDataV1(message)) => Some(message),
16968 _ => None,
16969 })
16970 .expect("placed-calls execute envelope");
16971 let document = CiscoIpPhoneExecute::from_xml(&message.data).unwrap();
16972 assert_eq!(
16973 document,
16974 CiscoIpPhoneExecute::new(vec![
16975 CiscoIpPhoneExecuteItem::new("Application:PlacedCalls").unwrap()
16976 ])
16977 .unwrap()
16978 );
16979 assert_eq!(message.line_instance, 1);
16980 assert_eq!(message.call_reference, 0);
16981 assert!(
16982 tokio::time::timeout(Duration::from_millis(50), events.recv())
16983 .await
16984 .is_err(),
16985 "opening the native placed-calls menu must not create or route a call"
16986 );
16987
16988 handle.shutdown().await.unwrap();
16989 task.await.unwrap().unwrap();
16990 }
16991
16992 #[tokio::test]
16993 async fn new_call_key_and_stimulus_support_dial_and_backspace() {
16994 let config = ServerConfig {
16995 bind: "127.0.0.1:0".parse().unwrap(),
16996 advertised_address: Ipv4Addr::LOCALHOST,
16997 ..ServerConfig::default()
16998 };
16999 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
17000 let address = server.local_addr().unwrap();
17001 let task = tokio::spawn(server.run());
17002 let mut phone = TcpStream::connect(address).await.unwrap();
17003 let mut decoder = FrameDecoder::new();
17004 let protocol = ProtocolVersion::V22;
17005
17006 phone.write_all(®ister_bytes(protocol)).await.unwrap();
17007 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17008 assert!(matches!(
17009 events.recv().await,
17010 Some(Event::Device(DeviceEvent {
17011 session_generation: _,
17012 device_id: _,
17013 event: DeviceEventKind::Registered(_)
17014 }))
17015 ));
17016
17017 phone
17018 .write_all(
17019 &ClientMessage::SoftKeyEvent {
17020 event: SoftKey::NewCall.wire_value(),
17021 line_instance: 1,
17022 call_reference: 0,
17023 }
17024 .encode(protocol)
17025 .unwrap(),
17026 )
17027 .await
17028 .unwrap();
17029 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17030 let call_reference = frames
17031 .into_iter()
17032 .find_map(|frame| match ServerMessage::decode(frame, protocol) {
17033 Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
17034 _ => None,
17035 })
17036 .unwrap();
17037 let new_call_id = match events.recv().await {
17038 Some(Event::Device(DeviceEvent {
17039 session_generation: _,
17040 device_id: _,
17041 event: DeviceEventKind::OffHook { call_id, .. },
17042 })) => call_id,
17043 event => panic!("unexpected new-call event: {event:?}"),
17044 };
17045 assert!(matches!(
17046 events.recv().await,
17047 Some(Event::Device(DeviceEvent {
17048 session_generation: _,
17049 device_id: _,
17050 event: DeviceEventKind::SoftKey {
17051 call_id: Some(_),
17052 soft_key: SoftKey::NewCall,
17053 ..
17054 }
17055 }))
17056 ));
17057
17058 for (index, digit) in [Digit::Number(1), Digit::Number(2)].into_iter().enumerate() {
17059 phone
17060 .write_all(
17061 &ClientMessage::KeypadButton {
17062 button: digit,
17063 line_instance: 1,
17064 call_reference,
17065 wire_layout: None,
17066 }
17067 .encode(protocol)
17068 .unwrap(),
17069 )
17070 .await
17071 .unwrap();
17072 if index == 0 {
17073 let frames =
17074 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17075 assert!(frames.iter().any(|frame| frame.message_id == id::STOP_TONE));
17076 assert!(
17077 frames
17078 .iter()
17079 .all(|frame| frame.message_id != id::DIALED_NUMBER)
17080 );
17081 }
17082 assert!(matches!(
17083 events.recv().await,
17084 Some(Event::Device(DeviceEvent {
17085 session_generation: _,
17086 device_id: _,
17087 event: DeviceEventKind::Digit { .. }
17088 }))
17089 ));
17090 if index == 1 {
17091 assert!(
17092 tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
17093 .await
17094 .is_err(),
17095 "a repeated digit emitted redundant station UI"
17096 );
17097 }
17098 }
17099
17100 phone
17101 .write_all(
17102 &ClientMessage::SoftKeyEvent {
17103 event: SoftKey::Backspace.wire_value(),
17104 line_instance: 1,
17105 call_reference,
17106 }
17107 .encode(protocol)
17108 .unwrap(),
17109 )
17110 .await
17111 .unwrap();
17112 let frames = read_until_message(&mut phone, &mut decoder, id::BACKSPACE_RESPONSE).await;
17113 assert!(
17114 frames
17115 .iter()
17116 .all(|frame| frame.message_id != id::DIALED_NUMBER)
17117 );
17118 assert!(matches!(
17119 events.recv().await,
17120 Some(Event::Device(DeviceEvent {
17121 session_generation: _,
17122 device_id: _,
17123 event: DeviceEventKind::SoftKey {
17124 soft_key: SoftKey::Backspace,
17125 ..
17126 }
17127 }))
17128 ));
17129
17130 phone
17131 .write_all(
17132 &ClientMessage::SoftKeyEvent {
17133 event: SoftKey::Dial.wire_value(),
17134 line_instance: 1,
17135 call_reference,
17136 }
17137 .encode(protocol)
17138 .unwrap(),
17139 )
17140 .await
17141 .unwrap();
17142 assert!(matches!(
17143 events.recv().await,
17144 Some(Event::Device(DeviceEvent {
17145 session_generation: _,
17146 device_id: _,
17147 event: DeviceEventKind::SoftKey {
17148 soft_key: SoftKey::Dial,
17149 ..
17150 }
17151 }))
17152 ));
17153
17154 handle
17155 .send(Command::new(
17156 DeviceId::new("SEP001122334455").unwrap(),
17157 CommandAction::CommitOutboundCall {
17158 call_id: new_call_id,
17159 info: CallInfo {
17160 direction: crate::CallDirection::Outbound,
17161 called_number: "1".into(),
17162 ..CallInfo::default()
17163 },
17164 },
17165 ))
17166 .await
17167 .unwrap();
17168 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
17169 let stop_tone = frames
17170 .iter()
17171 .position(|frame| frame.message_id == id::STOP_TONE)
17172 .expect("dial commit did not stop tone");
17173 let dialed_number = frames
17174 .iter()
17175 .position(|frame| {
17176 matches!(
17177 ServerMessage::decode(frame.clone(), protocol),
17178 Ok(ServerMessage::DialedNumber { ref number, .. }) if number == "1"
17179 )
17180 })
17181 .expect("dial commit did not publish the complete number");
17182 let proceed = frames
17183 .iter()
17184 .position(|frame| {
17185 matches!(
17186 ServerMessage::decode(frame.clone(), protocol),
17187 Ok(ServerMessage::CallState {
17188 state: CallState::Proceed,
17189 ..
17190 })
17191 )
17192 })
17193 .expect("dial commit did not publish Proceed");
17194 assert!(stop_tone < dialed_number && dialed_number < proceed);
17195 assert_eq!(
17196 frames
17197 .iter()
17198 .filter(|frame| frame.message_id == id::DIALED_NUMBER)
17199 .count(),
17200 1
17201 );
17202
17203 phone
17204 .write_all(
17205 &ClientMessage::OnHook {
17206 line_instance: 1,
17207 call_reference,
17208 }
17209 .encode(protocol)
17210 .unwrap(),
17211 )
17212 .await
17213 .unwrap();
17214 read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
17215 assert!(matches!(
17216 events.recv().await,
17217 Some(Event::Device(DeviceEvent {
17218 session_generation: _,
17219 device_id: _,
17220 event: DeviceEventKind::OnHook { .. }
17221 }))
17222 ));
17223
17224 phone
17225 .write_all(
17226 &ClientMessage::Stimulus {
17227 stimulus: Stimulus::NewCall,
17228 instance: 1,
17229 call_reference: 0,
17230 status: 0,
17231 }
17232 .encode(protocol)
17233 .unwrap(),
17234 )
17235 .await
17236 .unwrap();
17237 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17238 assert!(matches!(
17239 events.recv().await,
17240 Some(Event::Device(DeviceEvent {
17241 session_generation: _,
17242 device_id: _,
17243 event: DeviceEventKind::OffHook { .. }
17244 }))
17245 ));
17246 assert!(matches!(
17247 events.recv().await,
17248 Some(Event::Device(DeviceEvent {
17249 session_generation: _,
17250 device_id: _,
17251 event: DeviceEventKind::SoftKey {
17252 call_id: Some(_),
17253 soft_key: SoftKey::NewCall,
17254 ..
17255 }
17256 }))
17257 ));
17258
17259 handle.shutdown().await.unwrap();
17260 task.await.unwrap().unwrap();
17261 }
17262
17263 #[tokio::test]
17264 async fn pickup_key_and_stimulus_create_an_addressable_call_before_dispatch() {
17265 let config = ServerConfig {
17266 bind: "127.0.0.1:0".parse().unwrap(),
17267 advertised_address: Ipv4Addr::LOCALHOST,
17268 ..ServerConfig::default()
17269 };
17270 let mut device = definition();
17271 device.soft_keys =
17272 profile_with(KeyMode::OnHook, vec![SoftKey::Pickup, SoftKey::GroupPickup]);
17273 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17274 let address = server.local_addr().unwrap();
17275 let task = tokio::spawn(server.run());
17276 let mut phone = TcpStream::connect(address).await.unwrap();
17277 let mut decoder = FrameDecoder::new();
17278 let protocol = ProtocolVersion::V22;
17279
17280 phone.write_all(®ister_bytes(protocol)).await.unwrap();
17281 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17282 assert!(matches!(
17283 events.recv().await,
17284 Some(Event::Device(DeviceEvent {
17285 session_generation: _,
17286 device_id: _,
17287 event: DeviceEventKind::Registered(_)
17288 }))
17289 ));
17290
17291 phone
17292 .write_all(
17293 &ClientMessage::SoftKeyEvent {
17294 event: SoftKey::Pickup.wire_value(),
17295 line_instance: 1,
17296 call_reference: 0,
17297 }
17298 .encode(protocol)
17299 .unwrap(),
17300 )
17301 .await
17302 .unwrap();
17303 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17304 let call_reference = frames
17305 .into_iter()
17306 .find_map(|frame| match ServerMessage::decode(frame, protocol) {
17307 Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
17308 _ => None,
17309 })
17310 .unwrap();
17311 let call_id = match events.recv().await {
17312 Some(Event::Device(DeviceEvent {
17313 session_generation: _,
17314 device_id: _,
17315 event: DeviceEventKind::OffHook { call_id, .. },
17316 })) => call_id,
17317 event => panic!("expected pickup OffHook event, got {event:?}"),
17318 };
17319 assert!(matches!(
17320 events.recv().await,
17321 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17322 call_id: Some(event_call_id),
17323 soft_key: SoftKey::Pickup,
17324 ..
17325 } })) if event_call_id == call_id
17326 ));
17327
17328 phone
17329 .write_all(
17330 &ClientMessage::OnHook {
17331 line_instance: 1,
17332 call_reference,
17333 }
17334 .encode(protocol)
17335 .unwrap(),
17336 )
17337 .await
17338 .unwrap();
17339 read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
17340 assert!(matches!(
17341 events.recv().await,
17342 Some(Event::Device(DeviceEvent {
17343 session_generation: _,
17344 device_id: _,
17345 event: DeviceEventKind::OnHook { .. }
17346 }))
17347 ));
17348
17349 phone
17350 .write_all(
17351 &ClientMessage::Stimulus {
17352 stimulus: Stimulus::GroupCallPickup,
17353 instance: 1,
17354 call_reference: 0,
17355 status: 0,
17356 }
17357 .encode(protocol)
17358 .unwrap(),
17359 )
17360 .await
17361 .unwrap();
17362 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17363 let call_id = match events.recv().await {
17364 Some(Event::Device(DeviceEvent {
17365 session_generation: _,
17366 device_id: _,
17367 event: DeviceEventKind::OffHook { call_id, .. },
17368 })) => call_id,
17369 event => panic!("expected group-pickup OffHook event, got {event:?}"),
17370 };
17371 assert!(matches!(
17372 events.recv().await,
17373 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17374 call_id: Some(event_call_id),
17375 soft_key: SoftKey::GroupPickup,
17376 ..
17377 } })) if event_call_id == call_id
17378 ));
17379
17380 handle.shutdown().await.unwrap();
17381 task.await.unwrap().unwrap();
17382 }
17383
17384 #[tokio::test]
17385 async fn configured_voicemail_button_creates_an_exact_line_call_before_routing() {
17386 let config = ServerConfig {
17387 bind: "127.0.0.1:0".parse().unwrap(),
17388 advertised_address: Ipv4Addr::LOCALHOST,
17389 ..ServerConfig::default()
17390 };
17391 let mut device = definition();
17392 device
17393 .buttons
17394 .push(ButtonDefinition::Feature(FeatureDefinition {
17395 instance: 1,
17396 label: "Messages".into(),
17397 feature: ButtonType::Voicemail,
17398 }));
17399 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17400 let address = server.local_addr().unwrap();
17401 let task = tokio::spawn(server.run());
17402 let mut phone = TcpStream::connect(address).await.unwrap();
17403 let mut decoder = FrameDecoder::new();
17404 let protocol = ProtocolVersion::V22;
17405
17406 phone.write_all(®ister_bytes(protocol)).await.unwrap();
17407 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17408 assert!(matches!(
17409 events.recv().await,
17410 Some(Event::Device(DeviceEvent {
17411 session_generation: _,
17412 device_id: _,
17413 event: DeviceEventKind::Registered(_)
17414 }))
17415 ));
17416 phone
17417 .write_all(
17418 &ClientMessage::Stimulus {
17419 stimulus: Stimulus::Voicemail,
17420 instance: 1,
17421 call_reference: 0,
17422 status: 0,
17423 }
17424 .encode(protocol)
17425 .unwrap(),
17426 )
17427 .await
17428 .unwrap();
17429 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17430 let call_id = match events.recv().await {
17431 Some(Event::Device(DeviceEvent {
17432 session_generation: _,
17433 device_id: _,
17434 event:
17435 DeviceEventKind::OffHook {
17436 call_id,
17437 line_instance: LineInstance(1),
17438 ..
17439 },
17440 })) => call_id,
17441 event => panic!("expected voicemail OffHook event, got {event:?}"),
17442 };
17443 assert!(matches!(
17444 events.recv().await,
17445 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::VoicemailButton {
17446 call_id: routed_call,
17447 line_instance: LineInstance(1),
17448 ..
17449 } })) if routed_call == call_id
17450 ));
17451
17452 handle.shutdown().await.unwrap();
17453 task.await.unwrap().unwrap();
17454 }
17455
17456 #[tokio::test]
17457 async fn meetme_key_and_stimulus_reserve_a_distinct_addressable_call() {
17458 let config = ServerConfig {
17459 bind: "127.0.0.1:0".parse().unwrap(),
17460 advertised_address: Ipv4Addr::LOCALHOST,
17461 ..ServerConfig::default()
17462 };
17463 let mut device = definition();
17464 device.soft_keys = SoftKeyProfile::new(
17465 KeyMode::ALL_KNOWN
17466 .iter()
17467 .copied()
17468 .map(|mode| (mode, vec![SoftKey::MeetMe])),
17469 )
17470 .unwrap();
17471 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17472 let address = server.local_addr().unwrap();
17473 let task = tokio::spawn(server.run());
17474 let mut phone = TcpStream::connect(address).await.unwrap();
17475 let mut decoder = FrameDecoder::new();
17476 let protocol = ProtocolVersion::V22;
17477
17478 phone.write_all(®ister_bytes(protocol)).await.unwrap();
17479 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17480 assert!(matches!(
17481 events.recv().await,
17482 Some(Event::Device(DeviceEvent {
17483 session_generation: _,
17484 device_id: _,
17485 event: DeviceEventKind::Registered(_)
17486 }))
17487 ));
17488
17489 phone
17490 .write_all(
17491 &ClientMessage::SoftKeyEvent {
17492 event: SoftKey::MeetMe.wire_value(),
17493 line_instance: 1,
17494 call_reference: 0,
17495 }
17496 .encode(protocol)
17497 .unwrap(),
17498 )
17499 .await
17500 .unwrap();
17501 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17502 let first_reference = frames
17503 .into_iter()
17504 .find_map(|frame| match ServerMessage::decode(frame, protocol) {
17505 Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
17506 _ => None,
17507 })
17508 .unwrap();
17509 let first_call = match events.recv().await {
17510 Some(Event::Device(DeviceEvent {
17511 session_generation: _,
17512 device_id: _,
17513 event: DeviceEventKind::OffHook { call_id, .. },
17514 })) => call_id,
17515 event => panic!("expected conference-destination OffHook event, got {event:?}"),
17516 };
17517 assert!(matches!(
17518 events.recv().await,
17519 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17520 call_id: Some(call_id),
17521 soft_key: SoftKey::MeetMe,
17522 ..
17523 } })) if call_id == first_call
17524 ));
17525
17526 handle
17527 .send(Command::new(
17528 DeviceId::new("SEP001122334455").unwrap(),
17529 CommandAction::SetCallState {
17530 call_id: first_call,
17531 state: CallState::Connected,
17532 },
17533 ))
17534 .await
17535 .unwrap();
17536 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17537 phone
17538 .write_all(
17539 &ClientMessage::Stimulus {
17540 stimulus: Stimulus::MeetMeConference,
17541 instance: 1,
17542 call_reference: first_reference,
17543 status: 0,
17544 }
17545 .encode(protocol)
17546 .unwrap(),
17547 )
17548 .await
17549 .unwrap();
17550 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17551 let second_call = match events.recv().await {
17552 Some(Event::Device(DeviceEvent {
17553 session_generation: _,
17554 device_id: _,
17555 event: DeviceEventKind::OffHook { call_id, .. },
17556 })) => call_id,
17557 event => panic!("expected a new conference-destination OffHook event, got {event:?}"),
17558 };
17559 assert_ne!(second_call, first_call);
17560 assert!(matches!(
17561 events.recv().await,
17562 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
17563 call_id: Some(call_id),
17564 soft_key: SoftKey::MeetMe,
17565 ..
17566 } })) if call_id == second_call
17567 ));
17568
17569 handle.shutdown().await.unwrap();
17570 task.await.unwrap().unwrap();
17571 }
17572
17573 #[tokio::test]
17574 async fn registered_handset_routes_every_configured_conference_control_with_exact_call() {
17575 let config = ServerConfig {
17576 bind: "127.0.0.1:0".parse().unwrap(),
17577 advertised_address: Ipv4Addr::LOCALHOST,
17578 ..ServerConfig::default()
17579 };
17580 let conference_keys = vec![
17581 SoftKey::Conference,
17582 SoftKey::Join,
17583 SoftKey::ConferenceList,
17584 SoftKey::Select,
17585 SoftKey::Hold,
17586 SoftKey::Resume,
17587 SoftKey::EndCall,
17588 ];
17589 let mut device = definition();
17590 device.soft_keys = SoftKeyProfile::new(
17591 KeyMode::ALL_KNOWN
17592 .iter()
17593 .copied()
17594 .map(|mode| (mode, conference_keys.clone())),
17595 )
17596 .unwrap();
17597 let (server, handle, mut events) = Server::bind(config, [device]).await.unwrap();
17598 let address = server.local_addr().unwrap();
17599 let task = tokio::spawn(server.run());
17600 let mut phone = TcpStream::connect(address).await.unwrap();
17601 let mut decoder = FrameDecoder::new();
17602 let protocol = ProtocolVersion::V22;
17603 let device_id = DeviceId::new("SEP001122334455").unwrap();
17604 let call_id = CallId(7001);
17605
17606 phone.write_all(®ister_bytes(protocol)).await.unwrap();
17607 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17608 assert!(matches!(
17609 events.recv().await,
17610 Some(Event::Device(DeviceEvent {
17611 session_generation: _,
17612 device_id: _,
17613 event: DeviceEventKind::Registered(_)
17614 }))
17615 ));
17616 handle
17617 .send(Command::new(
17618 device_id.clone(),
17619 CommandAction::BeginCall {
17620 line_instance: LineInstance(1),
17621 call_id,
17622 codec: Codec::Pcma,
17623 },
17624 ))
17625 .await
17626 .unwrap();
17627 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17628 handle
17629 .send(Command::new(
17630 device_id.clone(),
17631 CommandAction::SetCallState {
17632 call_id,
17633 state: CallState::Connected,
17634 },
17635 ))
17636 .await
17637 .unwrap();
17638 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
17639
17640 for soft_key in conference_keys {
17641 phone
17642 .write_all(
17643 &ClientMessage::SoftKeyEvent {
17644 event: soft_key.wire_value(),
17645 line_instance: 1,
17646 call_reference: 7001,
17647 }
17648 .encode(protocol)
17649 .unwrap(),
17650 )
17651 .await
17652 .unwrap();
17653 assert!(matches!(
17654 events.recv().await,
17655 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::SoftKey {
17656 line_instance: LineInstance(1),
17657 call_id: Some(actual_call),
17658 soft_key: actual_key,
17659 } })) if actual_device == device_id
17660 && actual_call == call_id
17661 && actual_key == soft_key
17662 ));
17663 }
17664
17665 for (stimulus, soft_key) in [
17666 (Stimulus::Conference, SoftKey::Conference),
17667 (Stimulus::ConferenceList, SoftKey::ConferenceList),
17668 ] {
17669 phone
17670 .write_all(
17671 &ClientMessage::Stimulus {
17672 stimulus,
17673 instance: 1,
17674 call_reference: 7001,
17675 status: 0,
17676 }
17677 .encode(protocol)
17678 .unwrap(),
17679 )
17680 .await
17681 .unwrap();
17682 assert!(matches!(
17683 events.recv().await,
17684 Some(Event::Device(DeviceEvent { session_generation: _, device_id: actual_device, event: DeviceEventKind::SoftKey {
17685 line_instance: LineInstance(1),
17686 call_id: Some(actual_call),
17687 soft_key: actual_key,
17688 } })) if actual_device == device_id
17689 && actual_call == call_id
17690 && actual_key == soft_key
17691 ));
17692 }
17693
17694 handle.shutdown().await.unwrap();
17695 task.await.unwrap().unwrap();
17696 }
17697
17698 #[tokio::test]
17699 async fn legacy_phone_receives_static_button_status_layouts() {
17700 let config = ServerConfig {
17701 bind: "127.0.0.1:0".parse().unwrap(),
17702 advertised_address: Ipv4Addr::LOCALHOST,
17703 ..ServerConfig::default()
17704 };
17705 let (server, handle, _events) = Server::bind(config, [mixed_definition()]).await.unwrap();
17706 let address = server.local_addr().unwrap();
17707 let task = tokio::spawn(server.run());
17708 let mut phone = TcpStream::connect(address).await.unwrap();
17709 let mut decoder = FrameDecoder::new();
17710
17711 phone
17712 .write_all(®ister_bytes(ProtocolVersion::V3))
17713 .await
17714 .unwrap();
17715 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
17716 let requests = [
17717 ClientMessage::SpeedDialStatusRequest {
17718 speed_dial_instance: 1,
17719 }
17720 .encode(ProtocolVersion::V3)
17721 .unwrap(),
17722 ClientMessage::FeatureStatusRequest {
17723 index: 1,
17724 capabilities: 0,
17725 }
17726 .encode(ProtocolVersion::V3)
17727 .unwrap(),
17728 ClientMessage::ServiceUrlStatusRequest { index: 1 }
17729 .encode(ProtocolVersion::V3)
17730 .unwrap(),
17731 ]
17732 .concat();
17733 phone.write_all(&requests).await.unwrap();
17734 let frames = read_until_message(&mut phone, &mut decoder, id::SERVICE_URL_STAT).await;
17735
17736 for (message_id, expected) in [
17737 (
17738 id::SPEED_DIAL_STAT,
17739 ServerMessage::SpeedDialStatus {
17740 instance: 1,
17741 number: "2001".into(),
17742 display_name: "Reception".into(),
17743 },
17744 ),
17745 (
17746 id::FEATURE_STAT,
17747 ServerMessage::FeatureStatus {
17748 instance: 1,
17749 button_type: ButtonType::DoNotDisturb,
17750 label: "DND".into(),
17751 state: 0,
17752 },
17753 ),
17754 (
17755 id::SERVICE_URL_STAT,
17756 ServerMessage::ServiceUrlStatus {
17757 index: 1,
17758 url: "http://services.invalid/directory".into(),
17759 label: "Directory".into(),
17760 extension_text: String::new(),
17761 },
17762 ),
17763 ] {
17764 let frame = frames
17765 .iter()
17766 .find(|frame| frame.message_id == message_id)
17767 .cloned()
17768 .unwrap();
17769 assert_eq!(
17770 ServerMessage::decode(frame, ProtocolVersion::V3).unwrap(),
17771 expected
17772 );
17773 }
17774
17775 handle.shutdown().await.unwrap();
17776 task.await.unwrap().unwrap();
17777 }
17778
17779 fn register_bytes(protocol: ProtocolVersion) -> Vec<u8> {
17780 register_bytes_for_device_type(protocol, 115)
17781 }
17782
17783 fn register_bytes_with_features(protocol: ProtocolVersion, features: PhoneFeatures) -> Vec<u8> {
17784 register_bytes_for_device_with_features(protocol, 115, "SEP001122334455", features)
17785 }
17786
17787 fn register_bytes_for_device_type(protocol: ProtocolVersion, device_type: u32) -> Vec<u8> {
17788 register_bytes_for_device(protocol, device_type, "SEP001122334455")
17789 }
17790
17791 fn register_bytes_for_device(
17792 protocol: ProtocolVersion,
17793 device_type: u32,
17794 device_id: &str,
17795 ) -> Vec<u8> {
17796 register_bytes_for_device_with_features(
17797 protocol,
17798 device_type,
17799 device_id,
17800 PhoneFeatures::empty(),
17801 )
17802 }
17803
17804 fn register_bytes_for_device_with_features(
17805 protocol: ProtocolVersion,
17806 device_type: u32,
17807 device_id: &str,
17808 features: PhoneFeatures,
17809 ) -> Vec<u8> {
17810 let mut payload = vec![0_u8; 124];
17811 let device_id = device_id.as_bytes();
17812 assert!(device_id.len() <= 16);
17813 payload[..device_id.len()].copy_from_slice(device_id);
17814 payload[24..28].copy_from_slice(&[127, 0, 0, 1]);
17815 payload[28..32].copy_from_slice(&device_type.to_le_bytes());
17816 payload[40..44].copy_from_slice(&(protocol.wire() | features.bits()).to_le_bytes());
17817 payload[92..101].copy_from_slice(b"SCCP42.9-");
17818 Frame::new(0, id::REGISTER, payload).encode().unwrap()
17819 }
17820
17821 fn capability_update_bytes(
17822 protocol: ProtocolVersion,
17823 audio_codec: Codec,
17824 video_codec: Codec,
17825 marker: u32,
17826 ) -> Vec<u8> {
17827 const AUDIO_OFFSET: usize = 312;
17828 const VIDEO_OFFSET: usize = 600;
17829
17830 fn put(payload: &mut [u8], offset: usize, value: u32) {
17831 payload[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
17832 }
17833
17834 let mut payload = vec![0; 2_380];
17835 put(&mut payload, 0, 1);
17836 put(&mut payload, 4, 1);
17837 put(&mut payload, AUDIO_OFFSET, audio_codec.wire_value());
17838 put(&mut payload, AUDIO_OFFSET + 4, marker);
17839 payload[AUDIO_OFFSET + 8..AUDIO_OFFSET + 16]
17840 .copy_from_slice(&marker.to_le_bytes().repeat(2));
17841
17842 put(&mut payload, VIDEO_OFFSET, video_codec.wire_value());
17843 put(
17844 &mut payload,
17845 VIDEO_OFFSET + 4,
17846 (ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT).bits(),
17847 );
17848 put(&mut payload, VIDEO_OFFSET + 8, 1);
17849 for (index, value) in [marker, 5, 4_000, 128, 2, 7].into_iter().enumerate() {
17850 put(&mut payload, VIDEO_OFFSET + 12 + index * 4, value);
17851 }
17852 put(
17853 &mut payload,
17854 VIDEO_OFFSET + 108,
17855 u32::from(EncryptionCapability::Capable),
17856 );
17857 for (index, value) in [66, 31, 120, 240, 360, marker].into_iter().enumerate() {
17858 put(&mut payload, VIDEO_OFFSET + 112 + index * 4, value);
17859 }
17860 put(
17861 &mut payload,
17862 VIDEO_OFFSET + 136,
17863 u32::from(IpAddressType::Ipv4AndIpv6),
17864 );
17865 Frame::new(protocol.wire(), id::UPDATE_CAPABILITIES_V3, payload)
17866 .encode()
17867 .unwrap()
17868 }
17869
17870 #[tokio::test]
17871 async fn capability_snapshots_replace_atomically_and_remain_session_scoped() {
17872 let config = ServerConfig {
17873 bind: "127.0.0.1:0".parse().unwrap(),
17874 advertised_address: Ipv4Addr::LOCALHOST,
17875 ..ServerConfig::default()
17876 };
17877 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
17878 let address = server.local_addr().unwrap();
17879 let task = tokio::spawn(server.run());
17880 let protocol = ProtocolVersion::V22;
17881
17882 let mut first_phone = TcpStream::connect(address).await.unwrap();
17883 let mut first_decoder = FrameDecoder::new();
17884 first_phone
17885 .write_all(®ister_bytes(protocol))
17886 .await
17887 .unwrap();
17888 read_until_message(&mut first_phone, &mut first_decoder, id::CAPABILITIES_REQ).await;
17889 let first_generation = match events.recv().await {
17890 Some(Event::Device(DeviceEvent {
17891 session_generation,
17892 event: DeviceEventKind::Registered(_),
17893 ..
17894 })) => session_generation,
17895 event => panic!("expected first registration, got {event:?}"),
17896 };
17897
17898 first_phone
17899 .write_all(&capability_update_bytes(
17900 protocol,
17901 Codec::Pcmu,
17902 Codec::H264,
17903 11,
17904 ))
17905 .await
17906 .unwrap();
17907 let first_capabilities = match events.recv().await {
17908 Some(Event::Device(DeviceEvent {
17909 session_generation,
17910 event: DeviceEventKind::Capabilities { capabilities },
17911 ..
17912 })) => {
17913 assert_eq!(session_generation, first_generation);
17914 capabilities
17915 }
17916 event => panic!("expected first capability update, got {event:?}"),
17917 };
17918 assert_eq!(first_capabilities.audio()[0].codec, Codec::Pcmu);
17919 assert_eq!(first_capabilities.video()[0].codec, Codec::H264);
17920 assert_eq!(
17921 first_capabilities.video()[0].direction,
17922 ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT
17923 );
17924 assert_eq!(
17925 first_capabilities.video()[0].encryption_capability,
17926 Some(EncryptionCapability::Capable)
17927 );
17928 assert_eq!(
17929 first_capabilities.video()[0].address_type,
17930 Some(IpAddressType::Ipv4AndIpv6)
17931 );
17932 assert_eq!(first_capabilities.video()[0].codec_parameters[5], 11);
17933
17934 first_phone
17935 .write_all(&capability_update_bytes(
17936 protocol,
17937 Codec::G72264k,
17938 Codec::H263,
17939 22,
17940 ))
17941 .await
17942 .unwrap();
17943 let replacement_capabilities = match events.recv().await {
17944 Some(Event::Device(DeviceEvent {
17945 session_generation,
17946 event: DeviceEventKind::Capabilities { capabilities },
17947 ..
17948 })) => {
17949 assert_eq!(session_generation, first_generation);
17950 capabilities
17951 }
17952 event => panic!("expected replacement capability update, got {event:?}"),
17953 };
17954 assert_eq!(replacement_capabilities.audio().len(), 1);
17955 assert_eq!(replacement_capabilities.audio()[0].codec, Codec::G72264k);
17956 assert_eq!(replacement_capabilities.video().len(), 1);
17957 assert_eq!(replacement_capabilities.video()[0].codec, Codec::H263);
17958 assert_eq!(replacement_capabilities.video()[0].codec_parameters[5], 22);
17959 assert_eq!(first_capabilities.video()[0].codec, Codec::H264);
17960
17961 let mut second_phone = TcpStream::connect(address).await.unwrap();
17962 let mut second_decoder = FrameDecoder::new();
17963 second_phone
17964 .write_all(®ister_bytes(protocol))
17965 .await
17966 .unwrap();
17967 read_until_message(&mut second_phone, &mut second_decoder, id::CAPABILITIES_REQ).await;
17968 let second_generation = match events.recv().await {
17969 Some(Event::Device(DeviceEvent {
17970 session_generation,
17971 event: DeviceEventKind::Registered(_),
17972 ..
17973 })) => session_generation,
17974 event => panic!("expected replacement registration, got {event:?}"),
17975 };
17976 assert!(second_generation > first_generation);
17977 assert!(
17978 tokio::time::timeout(Duration::from_millis(25), events.recv())
17979 .await
17980 .is_err(),
17981 "replaced session emitted a late disconnect"
17982 );
17983
17984 second_phone
17985 .write_all(
17986 &ClientMessage::CapabilitiesResponse(vec![MediaCapability {
17987 codec: Codec::Pcma,
17988 max_frames_per_packet: 2,
17989 codec_parameters: [0; 8],
17990 }])
17991 .encode(protocol)
17992 .unwrap(),
17993 )
17994 .await
17995 .unwrap();
17996 match events.recv().await {
17997 Some(Event::Device(DeviceEvent {
17998 session_generation,
17999 event: DeviceEventKind::Capabilities { capabilities },
18000 ..
18001 })) => {
18002 assert_eq!(session_generation, second_generation);
18003 assert_eq!(capabilities.audio()[0].codec, Codec::Pcma);
18004 assert!(capabilities.video().is_empty());
18005 }
18006 event => panic!("expected reconnect capability response, got {event:?}"),
18007 }
18008 assert_ne!(first_generation, second_generation);
18009
18010 handle.shutdown().await.unwrap();
18011 task.await.unwrap().unwrap();
18012 }
18013
18014 async fn read_until_message(
18015 phone: &mut dyn StationIo,
18016 decoder: &mut FrameDecoder,
18017 message_id: u32,
18018 ) -> Vec<Frame> {
18019 let mut frames = Vec::new();
18020 let mut buffer = [0_u8; 2048];
18021 while !frames
18022 .iter()
18023 .any(|frame: &Frame| frame.message_id == message_id)
18024 {
18025 let count = tokio::time::timeout(Duration::from_secs(1), phone.read(&mut buffer))
18026 .await
18027 .expect("timed out waiting for SCCP response")
18028 .expect("could not read SCCP response");
18029 assert_ne!(count, 0, "SCCP session closed while waiting for response");
18030 frames.extend(decoder.push(&buffer[..count]).unwrap());
18031 }
18032 frames
18033 }
18034
18035 async fn read_until_server_message(
18036 phone: &mut dyn StationIo,
18037 decoder: &mut FrameDecoder,
18038 protocol: ProtocolVersion,
18039 predicate: impl Fn(&ServerMessage) -> bool,
18040 ) -> Vec<ServerMessage> {
18041 let mut messages = Vec::new();
18042 let mut buffer = [0_u8; 2048];
18043 while !messages.iter().any(&predicate) {
18044 let count = tokio::time::timeout(Duration::from_secs(1), phone.read(&mut buffer))
18045 .await
18046 .expect("timed out waiting for SCCP response")
18047 .expect("could not read SCCP response");
18048 assert_ne!(count, 0, "SCCP session closed while waiting for response");
18049 messages.extend(
18050 decoder
18051 .push(&buffer[..count])
18052 .unwrap()
18053 .into_iter()
18054 .map(|frame| ServerMessage::decode(frame, protocol).unwrap()),
18055 );
18056 }
18057 messages
18058 }
18059
18060 fn open_receive_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
18061 frames
18062 .iter()
18063 .find_map(
18064 |frame| match ServerMessage::decode(frame.clone(), protocol).ok()? {
18065 ServerMessage::OpenReceiveChannel {
18066 passthrough_party_id,
18067 ..
18068 } => Some(passthrough_party_id),
18069 _ => None,
18070 },
18071 )
18072 .expect("transaction omitted OpenReceiveChannel")
18073 }
18074
18075 fn start_media_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
18076 frames
18077 .iter()
18078 .find_map(
18079 |frame| match ServerMessage::decode(frame.clone(), protocol).ok()? {
18080 ServerMessage::StartMediaTransmission {
18081 passthrough_party_id,
18082 ..
18083 } => Some(passthrough_party_id),
18084 _ => None,
18085 },
18086 )
18087 .expect("transaction omitted StartMediaTransmission")
18088 }
18089
18090 fn coupled_media_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
18091 let receive = open_receive_request_party(frames, protocol);
18092 let transmit = start_media_request_party(frames, protocol);
18093 assert_ne!(receive, 0);
18094 assert_eq!(receive, transmit, "coupled request identities diverged");
18095 receive
18096 }
18097
18098 fn test_connection_statistics(
18099 directory_number: &str,
18100 call_reference: u32,
18101 ) -> ConnectionStatistics {
18102 ConnectionStatistics {
18103 directory_number: directory_number.into(),
18104 call_reference,
18105 processing: StatisticsProcessing::Clear,
18106 packets_sent: 120,
18107 octets_sent: 9_600,
18108 packets_received: 118,
18109 octets_received: 9_440,
18110 packets_lost: 2,
18111 jitter_millis: 6,
18112 latency_millis: 17,
18113 quality: crate::ConnectionQualityStatistics::new(b"MLQK=4.4".to_vec()).unwrap(),
18114 }
18115 }
18116
18117 #[tokio::test]
18118 async fn hangup_statistics_are_exactly_correlated_retained_and_not_replayed() {
18119 let config = ServerConfig {
18120 bind: "127.0.0.1:0".parse().unwrap(),
18121 advertised_address: Ipv4Addr::LOCALHOST,
18122 ..ServerConfig::default()
18123 };
18124 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
18125 let address = server.local_addr().unwrap();
18126 let task = tokio::spawn(server.run());
18127 let mut phone = TcpStream::connect(address).await.unwrap();
18128 let mut decoder = FrameDecoder::new();
18129 let protocol = ProtocolVersion::V22;
18130 let device_id = DeviceId::new("SEP001122334455").unwrap();
18131 let call_id = CallId(7001);
18132
18133 phone.write_all(®ister_bytes(protocol)).await.unwrap();
18134 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
18135 assert!(matches!(
18136 events.recv().await,
18137 Some(Event::Device(DeviceEvent {
18138 session_generation: _,
18139 device_id: _,
18140 event: DeviceEventKind::Registered(_)
18141 }))
18142 ));
18143 handle
18144 .send(Command::new(
18145 device_id.clone(),
18146 CommandAction::BeginCall {
18147 line_instance: LineInstance(1),
18148 call_id,
18149 codec: Codec::Pcma,
18150 },
18151 ))
18152 .await
18153 .unwrap();
18154 read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
18155 handle
18156 .send(Command::new(
18157 device_id.clone(),
18158 CommandAction::SetCallInfo {
18159 call_id,
18160 info: CallInfo {
18161 direction: crate::types::CallDirection::Outbound,
18162 called_number: "2002".into(),
18163 ..CallInfo::default()
18164 },
18165 },
18166 ))
18167 .await
18168 .unwrap();
18169 handle
18170 .send(Command::new(
18171 device_id.clone(),
18172 CommandAction::OpenReceiveChannel {
18173 call_id,
18174 source: Some(MediaEndpoint {
18175 address: "192.0.2.1".parse().unwrap(),
18176 rtp_port: 5000,
18177 rtcp_port: 5001,
18178 codec: Codec::Pcma,
18179 packet_ms: 30,
18180 max_frames_per_packet: 2,
18181 telephone_event_payload: 0,
18182 }),
18183 codec: Codec::Pcma,
18184 packet_ms: 30,
18185 max_frames_per_packet: 2,
18186 dtmf_mode: DtmfMode::Skinny,
18187 audio_processing: AudioProcessingPolicy::default(),
18188 },
18189 ))
18190 .await
18191 .unwrap();
18192 let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
18193 let receive_media_party = open_receive_request_party(&frames, protocol);
18194 let receive_peer = MediaEndpoint {
18195 address: "192.0.2.10".parse().unwrap(),
18196 rtp_port: 4000,
18197 rtcp_port: 4001,
18198 codec: Codec::Pcma,
18199 packet_ms: 30,
18200 max_frames_per_packet: 2,
18201 telephone_event_payload: 0,
18202 };
18203 phone
18204 .write_all(
18205 &ClientMessage::OpenReceiveChannelAck {
18206 status: MediaStatus::Ok,
18207 address: receive_peer.address,
18208 port: receive_peer.rtp_port,
18209 call_reference: 7001,
18210 passthrough_party_id: receive_media_party,
18211 }
18212 .encode(protocol)
18213 .unwrap(),
18214 )
18215 .await
18216 .unwrap();
18217 assert!(matches!(
18218 events.recv().await,
18219 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::ReceiveChannelOpened { endpoint, .. } })) if endpoint == receive_peer
18220 ));
18221 handle
18222 .send(Command::new(
18223 device_id.clone(),
18224 CommandAction::StartMedia {
18225 call_id,
18226 endpoint: MediaEndpoint {
18227 address: "198.51.100.20".parse().unwrap(),
18228 rtp_port: 6000,
18229 rtcp_port: 6001,
18230 codec: Codec::Pcma,
18231 packet_ms: 30,
18232 max_frames_per_packet: 2,
18233 telephone_event_payload: 0,
18234 },
18235 dtmf_mode: DtmfMode::Skinny,
18236 audio_processing: AudioProcessingPolicy::default(),
18237 traffic_class: MediaTrafficClass::default(),
18238 },
18239 ))
18240 .await
18241 .unwrap();
18242 let frames =
18243 read_until_message(&mut phone, &mut decoder, id::START_MEDIA_TRANSMISSION).await;
18244 let transmit_media_party = start_media_request_party(&frames, protocol);
18245 let transmit_peer = MediaEndpoint {
18246 address: "2001:db8::20".parse().unwrap(),
18247 rtp_port: 5000,
18248 rtcp_port: 5001,
18249 codec: Codec::Pcma,
18250 packet_ms: 30,
18251 max_frames_per_packet: 2,
18252 telephone_event_payload: 0,
18253 };
18254 phone
18255 .write_all(
18256 &ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
18257 conference_id: 7001,
18258 passthrough_party_id: transmit_media_party,
18259 call_reference: 7001,
18260 status: MediaStatus::Ok,
18261 address: transmit_peer.address,
18262 port: transmit_peer.rtp_port,
18263 wire: None,
18264 })
18265 .encode(protocol)
18266 .unwrap(),
18267 )
18268 .await
18269 .unwrap();
18270 assert!(matches!(
18271 events.recv().await,
18272 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::TransmitChannelStarted { endpoint, .. } })) if endpoint == transmit_peer
18273 ));
18274 handle
18275 .send(Command::new(
18276 device_id.clone(),
18277 CommandAction::CloseReceiveChannel { call_id },
18278 ))
18279 .await
18280 .unwrap();
18281 handle
18282 .send(Command::new(
18283 device_id.clone(),
18284 CommandAction::StopMedia { call_id },
18285 ))
18286 .await
18287 .unwrap();
18288 handle
18289 .send(Command::new(
18290 device_id.clone(),
18291 CommandAction::CloseCall { call_id },
18292 ))
18293 .await
18294 .unwrap();
18295 let frames =
18296 read_until_message(&mut phone, &mut decoder, id::CONNECTION_STATISTICS_REQ).await;
18297 let trailing = if frames
18298 .iter()
18299 .any(|frame| frame.message_id == id::SET_RINGER)
18300 {
18301 Vec::new()
18302 } else {
18303 read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await
18304 };
18305 assert_eq!(
18306 frames
18307 .iter()
18308 .chain(&trailing)
18309 .filter(|frame| frame.message_id == id::STOP_MEDIA_TRANSMISSION)
18310 .count(),
18311 1
18312 );
18313 assert_eq!(
18314 frames
18315 .iter()
18316 .chain(&trailing)
18317 .filter(|frame| frame.message_id == id::CLOSE_RECEIVE_CHANNEL)
18318 .count(),
18319 1
18320 );
18321 let close_receive = frames
18322 .iter()
18323 .position(|frame| frame.message_id == id::CLOSE_RECEIVE_CHANNEL)
18324 .expect("hangup did not close receive media");
18325 let stop_media = frames
18326 .iter()
18327 .position(|frame| frame.message_id == id::STOP_MEDIA_TRANSMISSION)
18328 .expect("hangup did not stop transmit media");
18329 let on_hook = frames
18330 .iter()
18331 .position(|frame| {
18332 matches!(
18333 ServerMessage::decode(frame.clone(), protocol),
18334 Ok(ServerMessage::CallState {
18335 state: CallState::OnHook,
18336 ..
18337 })
18338 )
18339 })
18340 .expect("hangup did not publish OnHook");
18341 let statistics = frames
18342 .iter()
18343 .position(|frame| frame.message_id == id::CONNECTION_STATISTICS_REQ)
18344 .expect("hangup did not request connection statistics");
18345 assert!(close_receive < stop_media && stop_media < on_hook && on_hook < statistics);
18346 assert!(
18347 frames
18348 .iter()
18349 .all(|frame| frame.message_id != id::CALL_HISTORY_DISPOSITION)
18350 );
18351 assert!(frames.iter().any(|frame| matches!(
18352 ServerMessage::decode(frame.clone(), protocol),
18353 Ok(ServerMessage::ConnectionStatisticsRequest {
18354 directory_number,
18355 call_reference: 7001,
18356 processing: StatisticsProcessing::Clear,
18357 }) if directory_number == "2002"
18358 )));
18359
18360 phone
18361 .write_all(
18362 &ClientMessage::ConnectionStatisticsResponse(test_connection_statistics(
18363 "wrong", 7001,
18364 ))
18365 .encode(protocol)
18366 .unwrap(),
18367 )
18368 .await
18369 .unwrap();
18370 assert!(
18371 tokio::time::timeout(Duration::from_millis(25), events.recv())
18372 .await
18373 .is_err()
18374 );
18375
18376 let mut unknown_processing = test_connection_statistics("2002", 7001);
18377 unknown_processing.processing = StatisticsProcessing::Unknown(9);
18378 phone
18379 .write_all(
18380 &ClientMessage::ConnectionStatisticsResponse(unknown_processing)
18381 .encode(protocol)
18382 .unwrap(),
18383 )
18384 .await
18385 .unwrap();
18386 assert!(
18387 tokio::time::timeout(Duration::from_millis(25), events.recv())
18388 .await
18389 .is_err()
18390 );
18391
18392 let expected = test_connection_statistics("2002", 7001);
18393 phone
18394 .write_all(
18395 &ClientMessage::ConnectionStatisticsResponse(expected.clone())
18396 .encode(protocol)
18397 .unwrap(),
18398 )
18399 .await
18400 .unwrap();
18401 let Some(Event::Device(DeviceEvent {
18402 session_generation: _,
18403 device_id: actual_device,
18404 event: DeviceEventKind::ConnectionStatisticsCollected { snapshot },
18405 })) = events.recv().await
18406 else {
18407 panic!("expected a correlated statistics event");
18408 };
18409 assert_eq!(actual_device, device_id);
18410 assert_eq!(snapshot.call_id, call_id);
18411 assert_eq!(snapshot.line_instance, LineInstance::new(1));
18412 assert_eq!(snapshot.codec, Codec::Pcma);
18413 assert_eq!(snapshot.packet_ms, 30);
18414 assert_eq!(snapshot.max_frames_per_packet, 2);
18415 assert_eq!(snapshot.receive_peer, Some(receive_peer));
18416 assert_eq!(snapshot.transmit_peer, Some(transmit_peer));
18417 assert_eq!(snapshot.packets_sent, expected.packets_sent);
18418 assert_eq!(snapshot.octets_sent, expected.octets_sent);
18419 assert_eq!(snapshot.packets_received, expected.packets_received);
18420 assert_eq!(snapshot.octets_received, expected.octets_received);
18421 assert_eq!(snapshot.packets_lost, expected.packets_lost);
18422 assert_eq!(snapshot.jitter_millis, expected.jitter_millis);
18423 assert_eq!(snapshot.latency_millis, expected.latency_millis);
18424 assert_eq!(
18425 snapshot.quality_byte_count,
18426 expected.quality.as_bytes().len()
18427 );
18428 let debug = format!("{snapshot:?}");
18429 assert!(!debug.contains("2002"));
18430 assert!(!debug.contains("MLQK"));
18431 assert_eq!(
18432 handle.latest_media_statistics(&device_id),
18433 Some(snapshot.clone())
18434 );
18435 assert_eq!(
18436 handle.media_statistics(),
18437 vec![(device_id.clone(), snapshot.clone())]
18438 );
18439
18440 phone
18441 .write_all(
18442 &ClientMessage::ConnectionStatisticsResponse(expected)
18443 .encode(protocol)
18444 .unwrap(),
18445 )
18446 .await
18447 .unwrap();
18448 assert!(
18449 tokio::time::timeout(Duration::from_millis(25), events.recv())
18450 .await
18451 .is_err(),
18452 "a duplicate response emitted a second event"
18453 );
18454
18455 handle.shutdown().await.unwrap();
18456 task.await.unwrap().unwrap();
18457 }
18458
18459 #[test]
18460 fn expired_statistics_requests_are_pruned_at_the_deadline() {
18461 let now = Instant::now();
18462 let mut pending = HashMap::from([(
18463 42,
18464 PendingConnectionStatistics {
18465 session_generation: SessionGeneration::new(1).unwrap(),
18466 request_generation: 2,
18467 call_id: CallId(3),
18468 line_instance: 1,
18469 codec: Codec::Pcmu,
18470 packet_ms: 20,
18471 max_frames_per_packet: 1,
18472 receive_peer: None,
18473 transmit_peer: None,
18474 directory_number: "2002".into(),
18475 processing: StatisticsProcessing::Clear,
18476 expires_at: now,
18477 },
18478 )]);
18479 prune_connection_statistics(&mut pending, now);
18480 assert!(pending.is_empty());
18481 }
18482
18483 #[test]
18484 fn statistics_directory_follows_the_call_direction() {
18485 let inbound = CallInfo {
18486 direction: crate::types::CallDirection::Inbound,
18487 calling_number: "inbound-peer".into(),
18488 called_number: "local-line".into(),
18489 ..CallInfo::default()
18490 };
18491 let outbound = CallInfo {
18492 direction: crate::types::CallDirection::Outbound,
18493 calling_number: "local-line".into(),
18494 called_number: "outbound-peer".into(),
18495 ..CallInfo::default()
18496 };
18497 assert_eq!(statistics_directory_for_call_info(&inbound), "inbound-peer");
18498 assert_eq!(
18499 statistics_directory_for_call_info(&outbound),
18500 "outbound-peer"
18501 );
18502 }
18503
18504 #[test]
18505 fn replacement_calls_and_media_requests_never_reuse_identifiers() {
18506 let device = definition();
18507 let mut state = SessionState {
18508 registration: DeviceRegistration {
18509 id: device.id.clone(),
18510 peer: "127.0.0.1:2000".parse().unwrap(),
18511 transport: StationTransport::Clear,
18512 reported_address: Some(Ipv4Addr::LOCALHOST),
18513 reported_ipv6_address: None,
18514 device_type: DeviceType::Cisco7962,
18515 protocol: ProtocolVersion::V22,
18516 firmware: "test".into(),
18517 },
18518 device,
18519 features: PhoneFeatures::empty(),
18520 generation: SessionGeneration::new(1).unwrap(),
18521 calls_by_id: HashMap::new(),
18522 calls_by_wire: HashMap::new(),
18523 media_capabilities: StationMediaCapabilities::default(),
18524 next_media_token: MediaRequestToken::new(1),
18525 next_multicast_generation: 0,
18526 multicast: HashMap::new(),
18527 pending_connection_statistics: HashMap::new(),
18528 statistics_references: HashSet::from([42]),
18529 cancelled_calls: HashSet::new(),
18530 last_number_by_line: HashMap::new(),
18531 forwarding_by_line: HashMap::new(),
18532 feature_states: HashMap::new(),
18533 mwi_by_line: HashMap::new(),
18534 mobility_appearances: HashMap::new(),
18535 active_key_mode: KeyMode::OnHook,
18536 active_call_id: None,
18537 pending_parking_menu: None,
18538 persistent_status_message: false,
18539 headset_enabled: false,
18540 media_path_states: HashMap::new(),
18541 pending_media_path_release: None,
18542 };
18543 let replacement = insert_call(&mut state, CallId(42), 1, Codec::Pcmu, CallState::OffHook);
18544 assert_eq!(replacement.wire_reference, 43);
18545 assert_eq!(state.calls_by_wire.get(&42), None);
18546 assert_eq!(state.calls_by_wire.get(&43), Some(&CallId(42)));
18547
18548 state.next_media_token = MediaRequestToken::new(u32::MAX);
18549 let final_identity = allocate_media_request_identity(&mut state, CallId(42)).unwrap();
18550 assert_eq!(final_identity.token().get(), u32::MAX);
18551 assert!(state.next_media_token.is_none());
18552 let generation_after_final_token = state.calls_by_id[&CallId(42)].media.generation;
18553 assert!(matches!(
18554 allocate_media_request_identity(&mut state, CallId(42)),
18555 Err(ServerError::MediaRequestIdentityExhausted)
18556 ));
18557 assert_eq!(
18558 state.calls_by_id[&CallId(42)].media.generation,
18559 generation_after_final_token,
18560 "failed allocation mutated the call generation"
18561 );
18562
18563 state.next_media_token = MediaRequestToken::new(7);
18564 state
18565 .calls_by_id
18566 .get_mut(&CallId(42))
18567 .unwrap()
18568 .media
18569 .generation = u64::MAX;
18570 assert!(matches!(
18571 allocate_media_request_identity(&mut state, CallId(42)),
18572 Err(ServerError::MediaRequestIdentityExhausted)
18573 ));
18574 assert_eq!(state.next_media_token.unwrap().get(), 7);
18575 }
18576
18577 #[test]
18578 fn omitted_call_reference_uses_active_then_configured_answer_order() {
18579 let device = definition();
18580 let mut state = SessionState {
18581 registration: DeviceRegistration {
18582 id: device.id.clone(),
18583 peer: "127.0.0.1:2000".parse().unwrap(),
18584 transport: StationTransport::Clear,
18585 reported_address: Some(Ipv4Addr::LOCALHOST),
18586 reported_ipv6_address: None,
18587 device_type: DeviceType::Cisco7962,
18588 protocol: ProtocolVersion::V22,
18589 firmware: "test".into(),
18590 },
18591 device,
18592 features: PhoneFeatures::empty(),
18593 generation: SessionGeneration::new(1).unwrap(),
18594 calls_by_id: HashMap::new(),
18595 calls_by_wire: HashMap::new(),
18596 media_capabilities: StationMediaCapabilities::default(),
18597 next_media_token: MediaRequestToken::new(1),
18598 next_multicast_generation: 0,
18599 multicast: HashMap::new(),
18600 pending_connection_statistics: HashMap::new(),
18601 statistics_references: HashSet::new(),
18602 cancelled_calls: HashSet::new(),
18603 last_number_by_line: HashMap::new(),
18604 forwarding_by_line: HashMap::new(),
18605 feature_states: HashMap::new(),
18606 mwi_by_line: HashMap::new(),
18607 mobility_appearances: HashMap::new(),
18608 active_key_mode: KeyMode::RingIn,
18609 active_call_id: None,
18610 pending_parking_menu: None,
18611 persistent_status_message: false,
18612 headset_enabled: false,
18613 media_path_states: HashMap::new(),
18614 pending_media_path_release: None,
18615 };
18616 let first = insert_call(
18617 &mut state,
18618 CallId(10),
18619 1,
18620 Codec::Pcmu,
18621 CallState::CallWaiting,
18622 );
18623 let last = insert_call(&mut state, CallId(20), 2, Codec::Pcma, CallState::RingIn);
18624
18625 assert_eq!(
18626 find_answer_call(&state, 0, 0, CallSelectionOrder::OldestFirst)
18627 .map(|call| call.call_id),
18628 Some(CallId(10))
18629 );
18630 assert_eq!(
18631 find_answer_call(&state, 0, 0, CallSelectionOrder::LastFirst).map(|call| call.call_id),
18632 Some(CallId(20))
18633 );
18634 assert_eq!(
18635 find_answer_call(
18636 &state,
18637 first.wire_reference,
18638 1,
18639 CallSelectionOrder::LastFirst,
18640 )
18641 .map(|call| call.call_id),
18642 Some(CallId(10))
18643 );
18644 assert!(
18645 find_answer_call(
18646 &state,
18647 last.wire_reference,
18648 1,
18649 CallSelectionOrder::LastFirst,
18650 )
18651 .is_none()
18652 );
18653 assert_eq!(
18654 find_answer_call(&state, 0, 1, CallSelectionOrder::LastFirst).map(|call| call.call_id),
18655 Some(CallId(10))
18656 );
18657
18658 state.active_call_id = Some(last.call_id);
18659 assert_eq!(
18660 find_call(&state, 0).map(|call| call.call_id),
18661 Some(CallId(20))
18662 );
18663 assert_eq!(
18664 find_answer_call(&state, 0, 0, CallSelectionOrder::OldestFirst)
18665 .map(|call| call.call_id),
18666 Some(CallId(20))
18667 );
18668 remove_call(&mut state, CallId(20));
18669 assert_eq!(state.active_call_id, None);
18670 assert_eq!(
18671 find_call(&state, 0).map(|call| call.call_id),
18672 Some(CallId(10))
18673 );
18674 }
18675
18676 #[test]
18677 fn distinct_and_urgent_ring_modes_preserve_exact_waiting_semantics() {
18678 assert_eq!(
18679 incoming_ringer(Some(IncomingRing::default()), CallState::RingIn),
18680 Some(IncomingRing {
18681 mode: RingerMode::Inside,
18682 duration: RingDuration::Normal,
18683 })
18684 );
18685 assert_eq!(
18686 incoming_ringer(
18687 Some(IncomingRing {
18688 mode: RingerMode::Bellcore4,
18689 duration: RingDuration::Normal,
18690 }),
18691 CallState::RingIn,
18692 ),
18693 Some(IncomingRing {
18694 mode: RingerMode::Bellcore4,
18695 duration: RingDuration::Normal,
18696 })
18697 );
18698 assert_eq!(
18699 incoming_ringer(
18700 Some(IncomingRing {
18701 mode: RingerMode::Bellcore4,
18702 duration: RingDuration::Normal,
18703 }),
18704 CallState::CallWaiting,
18705 ),
18706 Some(IncomingRing {
18707 mode: RingerMode::Silent,
18708 duration: RingDuration::Single,
18709 })
18710 );
18711 assert_eq!(
18712 incoming_ringer(
18713 Some(IncomingRing {
18714 mode: RingerMode::Urgent,
18715 duration: RingDuration::Normal,
18716 }),
18717 CallState::CallWaiting,
18718 ),
18719 Some(IncomingRing {
18720 mode: RingerMode::Urgent,
18721 duration: RingDuration::Single,
18722 })
18723 );
18724 assert_eq!(incoming_ringer(None, CallState::CallWaiting), None);
18725 }
18726
18727 #[test]
18728 fn answer_order_reload_updates_the_shared_policy_without_replacing_sessions() {
18729 let (command_tx, _command_rx) = mpsc::channel(1);
18730 let order = Arc::new(RwLock::new(CallSelectionOrder::OldestFirst));
18731 let handle = ServerHandle {
18732 command_tx,
18733 next_call_id: Arc::new(AtomicU64::new(1)),
18734 latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
18735 call_answer_order: Arc::clone(&order),
18736 };
18737 handle.set_call_answer_order(CallSelectionOrder::LastFirst);
18738 assert_eq!(
18739 *order.read().expect("test answer-order lock poisoned"),
18740 CallSelectionOrder::LastFirst
18741 );
18742 }
18743
18744 #[test]
18745 fn calendar_conversion_is_stable() {
18746 assert_eq!(civil_from_days(0), (1970, 1, 1));
18747 assert_eq!(civil_from_days(19_358), (2023, 1, 1));
18748 assert_eq!(
18749 time_date_message_at(UNIX_EPOCH + Duration::from_secs(23 * 3_600 + 45 * 60), 30),
18750 ServerMessage::TimeDate {
18751 year: 1970,
18752 month: 1,
18753 weekday: 6,
18754 day: 2,
18755 hour: 0,
18756 minute: 15,
18757 second: 0,
18758 milliseconds: 0,
18759 unix_seconds: 87_300,
18760 }
18761 );
18762 }
18763
18764 #[test]
18765 fn mwi_policy_projects_configured_cadence_and_on_call_visibility() {
18766 let hidden_on_call = crate::types::StationUiPolicy {
18767 mwi_lamp_mode: LampMode::Flash,
18768 mwi_on_call: false,
18769 ..Default::default()
18770 };
18771 assert_eq!(
18772 projected_mwi_lamp(hidden_on_call, false, true),
18773 LampMode::Flash
18774 );
18775 assert_eq!(
18776 projected_mwi_lamp(hidden_on_call, true, true),
18777 LampMode::Off
18778 );
18779 assert_eq!(
18780 projected_mwi_lamp(hidden_on_call, false, false),
18781 LampMode::Off
18782 );
18783
18784 let visible_on_call = crate::types::StationUiPolicy {
18785 mwi_lamp_mode: LampMode::Blink,
18786 mwi_on_call: true,
18787 ..Default::default()
18788 };
18789 assert_eq!(
18790 projected_mwi_lamp(visible_on_call, true, true),
18791 LampMode::Blink
18792 );
18793 }
18794
18795 #[test]
18796 fn call_history_distinguishes_answered_missed_and_elsewhere_answered() {
18797 assert_eq!(
18798 updated_history_disposition(CallHistoryDisposition::Missed, CallState::Connected),
18799 CallHistoryDisposition::Received
18800 );
18801 assert_eq!(
18802 updated_history_disposition(CallHistoryDisposition::Missed, CallState::OnHook),
18803 CallHistoryDisposition::Missed
18804 );
18805 assert_eq!(
18806 updated_history_disposition(CallHistoryDisposition::Missed, CallState::RemoteMultiline,),
18807 CallHistoryDisposition::Ignore
18808 );
18809 assert_eq!(
18810 updated_history_disposition(CallHistoryDisposition::Placed, CallState::Connected),
18811 CallHistoryDisposition::Placed
18812 );
18813 }
18814
18815 #[test]
18816 fn reconfiguration_classifies_added_changed_removed_and_unchanged_devices() {
18817 let unchanged = definition_for("SEP001122334455");
18818 let mut changed = definition_for("SEP112233445566");
18819 let removed = definition_for("SEP223344556677");
18820 let added = definition_for("SEP334455667788");
18821 let current = HashMap::from([
18822 (unchanged.id.clone(), unchanged.clone()),
18823 (changed.id.clone(), changed.clone()),
18824 (removed.id.clone(), removed),
18825 ]);
18826 changed.description = "Changed station".into();
18827 let next = HashMap::from([
18828 (unchanged.id.clone(), unchanged),
18829 (changed.id.clone(), changed),
18830 (added.id.clone(), added),
18831 ]);
18832
18833 assert_eq!(
18834 reconfigure_result(¤t, &next, &HashSet::new()),
18835 ReconfigureResult {
18836 added: vec![DeviceId::new("SEP334455667788").unwrap()],
18837 changed: vec![DeviceId::new("SEP112233445566").unwrap()],
18838 removed: vec![DeviceId::new("SEP223344556677").unwrap()],
18839 }
18840 );
18841 assert!(reconfigure_result(¤t, ¤t, &HashSet::new()).is_unchanged());
18842 assert_eq!(
18843 reconfigure_result(
18844 ¤t,
18845 ¤t,
18846 &HashSet::from([DeviceId::new("SEP001122334455").unwrap()]),
18847 )
18848 .changed,
18849 vec![DeviceId::new("SEP001122334455").unwrap()]
18850 );
18851 }
18852
18853 #[tokio::test]
18854 async fn reconfiguration_preserves_unchanged_session_calls_and_rolls_back_invalid_candidates() {
18855 let original = definition();
18856 let config = ServerConfig {
18857 bind: "127.0.0.1:0".parse().unwrap(),
18858 advertised_address: Ipv4Addr::LOCALHOST,
18859 ..ServerConfig::default()
18860 };
18861 let (server, handle, mut events) = Server::bind(config, [original.clone()]).await.unwrap();
18862 let address = server.local_addr().unwrap();
18863 let task = tokio::spawn(server.run());
18864 let mut phone = TcpStream::connect(address).await.unwrap();
18865 let mut decoder = FrameDecoder::new();
18866 let protocol = ProtocolVersion::V22;
18867
18868 phone.write_all(®ister_bytes(protocol)).await.unwrap();
18869 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
18870 assert!(matches!(
18871 events.recv().await,
18872 Some(Event::Device(DeviceEvent {
18873 session_generation: _,
18874 device_id: _,
18875 event: DeviceEventKind::Registered(_)
18876 }))
18877 ));
18878 phone
18879 .write_all(
18880 &ClientMessage::OffHook {
18881 line_instance: 1,
18882 call_reference: 0,
18883 }
18884 .encode(protocol)
18885 .unwrap(),
18886 )
18887 .await
18888 .unwrap();
18889 let call_id = match events.recv().await {
18890 Some(Event::Device(DeviceEvent {
18891 session_generation: _,
18892 device_id: _,
18893 event: DeviceEventKind::OffHook { call_id, .. },
18894 })) => call_id,
18895 event => panic!("unexpected event: {event:?}"),
18896 };
18897
18898 let added = definition_for("SEP112233445566");
18899 assert_eq!(
18900 handle
18901 .reconfigure([original.clone(), added.clone()])
18902 .await
18903 .unwrap(),
18904 ReconfigureResult {
18905 added: vec![added.id],
18906 ..ReconfigureResult::default()
18907 }
18908 );
18909 assert!(
18910 tokio::time::timeout(Duration::from_millis(50), events.recv())
18911 .await
18912 .is_err()
18913 );
18914
18915 let mut invalid = original.clone();
18916 let ButtonDefinition::Line(line) = &mut invalid.buttons[0] else {
18917 panic!("expected line button");
18918 };
18919 line.instance = 0;
18920 assert!(handle.reconfigure([invalid]).await.is_err());
18921
18922 phone
18923 .write_all(
18924 &ClientMessage::KeypadButton {
18925 button: Digit::Number(7),
18926 line_instance: 1,
18927 call_reference: 0,
18928 wire_layout: None,
18929 }
18930 .encode(protocol)
18931 .unwrap(),
18932 )
18933 .await
18934 .unwrap();
18935 assert!(matches!(
18936 events.recv().await,
18937 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Digit {
18938 call_id: same_call,
18939 digit: Digit::Number(7),
18940 ..
18941 } })) if same_call == call_id
18942 ));
18943
18944 let mut changed = original;
18945 changed.description = "Changed station".into();
18946 let report = handle.reconfigure([changed]).await.unwrap();
18947 assert_eq!(
18948 report.changed,
18949 vec![DeviceId::new("SEP001122334455").unwrap()]
18950 );
18951 assert_eq!(
18952 report.removed,
18953 vec![DeviceId::new("SEP112233445566").unwrap()]
18954 );
18955 assert!(matches!(
18956 tokio::time::timeout(Duration::from_secs(1), events.recv()).await,
18957 Ok(Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::Disconnected {} })))
18958 if device_id == DeviceId::new("SEP001122334455").unwrap()
18959 ));
18960
18961 handle.shutdown().await.unwrap();
18962 task.await.unwrap().unwrap();
18963 }
18964
18965 #[tokio::test]
18966 async fn concurrent_registration_and_disconnect_storm_retires_every_session_once() {
18967 const PHONE_COUNT: usize = 48;
18968 let device_ids = (0..PHONE_COUNT)
18969 .map(|index| format!("SEP{index:012X}"))
18970 .collect::<Vec<_>>();
18971 let definitions = device_ids
18972 .iter()
18973 .map(|device_id| definition_for(device_id))
18974 .collect::<Vec<_>>();
18975 let config = ServerConfig {
18976 bind: "127.0.0.1:0".parse().unwrap(),
18977 advertised_address: Ipv4Addr::LOCALHOST,
18978 ..ServerConfig::default()
18979 };
18980 let (server, handle, mut events) = Server::bind(config, definitions).await.unwrap();
18981 let address = server.local_addr().unwrap();
18982 let server_task = tokio::spawn(server.run());
18983 let barrier = Arc::new(tokio::sync::Barrier::new(PHONE_COUNT));
18984 let mut registrations = tokio::task::JoinSet::new();
18985 for device_id in &device_ids {
18986 let barrier = Arc::clone(&barrier);
18987 let device_id = device_id.clone();
18988 registrations.spawn(async move {
18989 let mut phone = TcpStream::connect(address).await.unwrap();
18990 let mut decoder = FrameDecoder::new();
18991 barrier.wait().await;
18992 phone
18993 .write_all(®ister_bytes_for_device(
18994 ProtocolVersion::V22,
18995 115,
18996 &device_id,
18997 ))
18998 .await
18999 .unwrap();
19000 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
19001 (device_id, phone)
19002 });
19003 }
19004 let mut phones = Vec::with_capacity(PHONE_COUNT);
19005 while let Some(result) =
19006 tokio::time::timeout(Duration::from_secs(5), registrations.join_next())
19007 .await
19008 .expect("registration storm exceeded its bound")
19009 {
19010 phones.push(result.unwrap());
19011 }
19012 assert_eq!(phones.len(), PHONE_COUNT);
19013
19014 let mut registered = HashSet::new();
19015 while registered.len() < PHONE_COUNT {
19016 let event = tokio::time::timeout(Duration::from_secs(5), events.recv())
19017 .await
19018 .expect("registration events exceeded their bound")
19019 .expect("server stopped during registration storm");
19020 if let Event::Device(DeviceEvent {
19021 session_generation: _,
19022 device_id: _,
19023 event: DeviceEventKind::Registered(registration),
19024 }) = event
19025 {
19026 assert!(registered.insert(registration.id));
19027 }
19028 }
19029 drop(phones);
19030
19031 let mut disconnected = HashSet::new();
19032 while disconnected.len() < PHONE_COUNT {
19033 let event = tokio::time::timeout(Duration::from_secs(5), events.recv())
19034 .await
19035 .expect("disconnect events exceeded their bound")
19036 .expect("server stopped during disconnect storm");
19037 if let Event::Device(DeviceEvent {
19038 session_generation: _,
19039 device_id,
19040 event: DeviceEventKind::Disconnected {},
19041 }) = event
19042 {
19043 assert!(disconnected.insert(device_id));
19044 }
19045 }
19046 assert_eq!(registered, disconnected);
19047
19048 handle.shutdown().await.unwrap();
19049 server_task.await.unwrap().unwrap();
19050 }
19051
19052 #[tokio::test]
19053 async fn repeated_server_load_and_unload_releases_all_shared_runtime_state() {
19054 const CYCLES: usize = 32;
19055 for _ in 0..CYCLES {
19056 let config = ServerConfig {
19057 bind: "127.0.0.1:0".parse().unwrap(),
19058 advertised_address: Ipv4Addr::LOCALHOST,
19059 ..ServerConfig::default()
19060 };
19061 let (server, handle, events) = Server::bind(config, [definition()]).await.unwrap();
19062 let sessions = Arc::downgrade(&server.sessions);
19063 let call_ids = Arc::downgrade(&handle.next_call_id);
19064 let statistics = Arc::downgrade(&handle.latest_media_statistics);
19065 let answer_order = Arc::downgrade(&handle.call_answer_order);
19066 let server_task = tokio::spawn(server.run());
19067
19068 handle.shutdown().await.unwrap();
19069 server_task.await.unwrap().unwrap();
19070 drop(events);
19071 drop(handle);
19072
19073 assert!(sessions.upgrade().is_none());
19074 assert!(call_ids.upgrade().is_none());
19075 assert!(statistics.upgrade().is_none());
19076 assert!(answer_order.upgrade().is_none());
19077 }
19078 }
19079
19080 #[tokio::test]
19081 async fn reconfiguration_disconnects_a_removed_device_without_touching_its_peer() {
19082 let retained = definition_for("SEP001122334455");
19083 let removed = definition_for("SEP112233445566");
19084 let config = ServerConfig {
19085 bind: "127.0.0.1:0".parse().unwrap(),
19086 advertised_address: Ipv4Addr::LOCALHOST,
19087 ..ServerConfig::default()
19088 };
19089 let (server, handle, mut events) =
19090 Server::bind(config, [retained.clone(), removed.clone()])
19091 .await
19092 .unwrap();
19093 let address = server.local_addr().unwrap();
19094 let task = tokio::spawn(server.run());
19095 let protocol = ProtocolVersion::V22;
19096
19097 let mut retained_phone = TcpStream::connect(address).await.unwrap();
19098 let mut retained_decoder = FrameDecoder::new();
19099 retained_phone
19100 .write_all(®ister_bytes_for_device(
19101 protocol,
19102 115,
19103 retained.id.as_str(),
19104 ))
19105 .await
19106 .unwrap();
19107 read_until_message(
19108 &mut retained_phone,
19109 &mut retained_decoder,
19110 id::CAPABILITIES_REQ,
19111 )
19112 .await;
19113 assert!(matches!(
19114 events.recv().await,
19115 Some(Event::Device(DeviceEvent {
19116 session_generation: _,
19117 device_id: _,
19118 event: DeviceEventKind::Registered(_)
19119 }))
19120 ));
19121
19122 let mut removed_phone = TcpStream::connect(address).await.unwrap();
19123 let mut removed_decoder = FrameDecoder::new();
19124 removed_phone
19125 .write_all(®ister_bytes_for_device(
19126 protocol,
19127 115,
19128 removed.id.as_str(),
19129 ))
19130 .await
19131 .unwrap();
19132 read_until_message(
19133 &mut removed_phone,
19134 &mut removed_decoder,
19135 id::CAPABILITIES_REQ,
19136 )
19137 .await;
19138 assert!(matches!(
19139 events.recv().await,
19140 Some(Event::Device(DeviceEvent {
19141 session_generation: _,
19142 device_id: _,
19143 event: DeviceEventKind::Registered(_)
19144 }))
19145 ));
19146
19147 let report = handle.reconfigure([retained.clone()]).await.unwrap();
19148 assert_eq!(report.removed, vec![removed.id.clone()]);
19149 assert!(matches!(
19150 tokio::time::timeout(Duration::from_secs(1), events.recv()).await,
19151 Ok(Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::Disconnected {} }))) if device_id == removed.id
19152 ));
19153
19154 retained_phone
19155 .write_all(
19156 &Frame::new(protocol.wire(), id::KEEP_ALIVE, Vec::new())
19157 .encode()
19158 .unwrap(),
19159 )
19160 .await
19161 .unwrap();
19162 read_until_message(
19163 &mut retained_phone,
19164 &mut retained_decoder,
19165 id::KEEP_ALIVE_ACK,
19166 )
19167 .await;
19168 assert!(
19169 tokio::time::timeout(Duration::from_millis(50), events.recv())
19170 .await
19171 .is_err()
19172 );
19173
19174 handle.shutdown().await.unwrap();
19175 task.await.unwrap().unwrap();
19176 }
19177
19178 #[test]
19179 fn server_response_uses_the_accepted_local_interface_with_configured_fallback() {
19180 assert_eq!(
19181 server_response_address(
19182 "10.20.30.40".parse().unwrap(),
19183 "192.0.2.10".parse().unwrap(),
19184 Some("2001:db8::10".parse().unwrap()),
19185 ),
19186 "10.20.30.40".parse::<IpAddr>().unwrap()
19187 );
19188 assert_eq!(
19189 server_response_address(
19190 "2001:db8::20".parse().unwrap(),
19191 "192.0.2.10".parse().unwrap(),
19192 Some("2001:db8::10".parse().unwrap()),
19193 ),
19194 "2001:db8::20".parse::<IpAddr>().unwrap()
19195 );
19196 assert_eq!(
19197 server_response_address(
19198 "0.0.0.0".parse().unwrap(),
19199 "192.0.2.10".parse().unwrap(),
19200 Some("2001:db8::10".parse().unwrap()),
19201 ),
19202 "192.0.2.10".parse::<IpAddr>().unwrap()
19203 );
19204 assert_eq!(
19205 server_response_address(
19206 "::".parse().unwrap(),
19207 "192.0.2.10".parse().unwrap(),
19208 Some("2001:db8::10".parse().unwrap()),
19209 ),
19210 "2001:db8::10".parse::<IpAddr>().unwrap()
19211 );
19212 }
19213
19214 #[test]
19215 fn synchronous_offer_and_hangup_commands_cannot_overtake_each_other() {
19216 let (command_tx, mut command_rx) = mpsc::channel(4);
19217 let handle = ServerHandle {
19218 command_tx,
19219 next_call_id: Arc::new(AtomicU64::new(1)),
19220 latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19221 call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19222 };
19223 let device_id = DeviceId::new("SEP001122334455").unwrap();
19224 let call_id = CallId(42);
19225 handle
19226 .try_offer_incoming_call_with_id(
19227 device_id.clone(),
19228 LineInstance::new(1),
19229 call_id,
19230 CallInfo {
19231 direction: crate::types::CallDirection::Inbound,
19232 calling_name: "Caller".into(),
19233 calling_number: "1002".into(),
19234 called_name: "Desk".into(),
19235 called_number: "1001".into(),
19236 ..CallInfo::default()
19237 },
19238 )
19239 .unwrap();
19240 handle
19241 .try_send(Command::new(
19242 device_id.clone(),
19243 CommandAction::CloseCall { call_id },
19244 ))
19245 .unwrap();
19246
19247 assert!(matches!(
19248 command_rx.try_recv().unwrap(),
19249 ServerCommand::OfferIncoming {
19250 device_id: offered_device,
19251 call_id: offered_call,
19252 ..
19253 } if offered_device == device_id && offered_call == call_id
19254 ));
19255 assert!(matches!(
19256 command_rx.try_recv().unwrap(),
19257 ServerCommand::Public(command)
19258 if matches!(command.as_ref(), Command {
19259 device_id: closed_device,
19260 action: CommandAction::CloseCall {
19261 call_id: closed_call,
19262 } } if closed_device == &device_id && *closed_call == call_id)
19263 ));
19264 }
19265
19266 #[test]
19267 fn synchronous_command_queue_reports_saturation_and_recovers_after_drain() {
19268 let (command_tx, mut command_rx) = mpsc::channel(1);
19269 let handle = ServerHandle {
19270 command_tx,
19271 next_call_id: Arc::new(AtomicU64::new(1)),
19272 latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19273 call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19274 };
19275 let device_id = DeviceId::new("SEP001122334455").unwrap();
19276
19277 handle
19278 .try_send(Command::new(
19279 device_id.clone(),
19280 CommandAction::DisconnectDevice {},
19281 ))
19282 .unwrap();
19283 assert!(matches!(
19284 handle.try_send(Command::new(
19285 device_id.clone(),
19286 CommandAction::DisconnectDevice {}
19287 )),
19288 Err(ServerError::CommandQueueFull)
19289 ));
19290 assert!(matches!(
19291 handle.try_offer_incoming_call_with_id(
19292 device_id.clone(),
19293 LineInstance::new(1),
19294 CallId(42),
19295 CallInfo {
19296 direction: crate::types::CallDirection::Inbound,
19297 calling_name: "Caller".into(),
19298 calling_number: "1002".into(),
19299 called_name: "Desk".into(),
19300 called_number: "1001".into(),
19301 ..CallInfo::default()
19302 },
19303 ),
19304 Err(ServerError::CommandQueueFull)
19305 ));
19306
19307 assert!(matches!(
19308 command_rx.try_recv().unwrap(),
19309 ServerCommand::Public(command)
19310 if matches!(command.as_ref(), Command {
19311 device_id: queued_device,
19312 action: CommandAction::DisconnectDevice { .. },
19313 } if queued_device == &device_id)
19314 ));
19315 handle
19316 .try_send(Command::new(
19317 device_id.clone(),
19318 CommandAction::DisconnectDevice {},
19319 ))
19320 .unwrap();
19321 assert!(matches!(
19322 command_rx.try_recv().unwrap(),
19323 ServerCommand::Public(command)
19324 if matches!(command.as_ref(), Command {
19325 device_id: queued_device,
19326 action: CommandAction::DisconnectDevice { .. },
19327 } if queued_device == &device_id)
19328 ));
19329 }
19330
19331 #[tokio::test]
19332 async fn confirmed_command_waits_for_device_write_and_propagates_failure() {
19333 let (command_tx, mut command_rx) = mpsc::channel(2);
19334 let handle = ServerHandle {
19335 command_tx,
19336 next_call_id: Arc::new(AtomicU64::new(1)),
19337 latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19338 call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19339 };
19340 let device_id = DeviceId::new("SEP001122334455").unwrap();
19341
19342 let success = tokio::spawn({
19343 let handle = handle.clone();
19344 let device_id = device_id.clone();
19345 async move {
19346 handle
19347 .send_confirmed(Command::new(
19348 device_id,
19349 CommandAction::StopAnnouncement {
19350 conference_id: ConferenceId::new(44),
19351 },
19352 ))
19353 .await
19354 }
19355 });
19356 let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19357 panic!("expected a confirmed command")
19358 };
19359 assert!(!success.is_finished());
19360 written.send(Ok(())).unwrap();
19361 assert!(success.await.unwrap().is_ok());
19362
19363 let failure = tokio::spawn({
19364 let handle = handle.clone();
19365 async move {
19366 handle
19367 .send_confirmed(Command::new(
19368 device_id,
19369 CommandAction::SetMicrophoneMode { enabled: false },
19370 ))
19371 .await
19372 }
19373 });
19374 let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19375 panic!("expected a confirmed command")
19376 };
19377 written.send(Err("socket closed".into())).unwrap();
19378 assert!(matches!(
19379 failure.await.unwrap(),
19380 Err(ServerError::CommandWrite(message)) if message == "socket closed"
19381 ));
19382 }
19383
19384 #[tokio::test(start_paused = true)]
19385 async fn ordering_acknowledgement_timeout_bounds_a_stalled_writer_and_retires_sender() {
19386 let (command_tx, mut command_rx) = mpsc::channel(1);
19387 let handle = ServerHandle {
19388 command_tx,
19389 next_call_id: Arc::new(AtomicU64::new(1)),
19390 latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19391 call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19392 };
19393 let pending = tokio::spawn(async move {
19394 handle
19395 .send_confirmed(Command::new(
19396 DeviceId::new("SEP001122334455").unwrap(),
19397 CommandAction::SetMicrophoneMode { enabled: false },
19398 ))
19399 .await
19400 });
19401 let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19402 panic!("expected a confirmed command")
19403 };
19404
19405 tokio::time::advance(ORDERING_ACKNOWLEDGEMENT_TIMEOUT).await;
19406 assert!(matches!(
19407 pending.await.unwrap(),
19408 Err(ServerError::CommandAcknowledgementTimeout)
19409 ));
19410 assert!(written.send(Ok(())).is_err());
19411 }
19412
19413 #[tokio::test(start_paused = true)]
19414 async fn expired_confirmed_commands_are_retired_at_both_queue_boundaries() {
19415 let device = definition();
19416 let device_id = device.id.clone();
19417 let config = ServerConfig {
19418 bind: "127.0.0.1:0".parse().unwrap(),
19419 advertised_address: Ipv4Addr::LOCALHOST,
19420 ..ServerConfig::default()
19421 };
19422 let (mut server, handle, _events) = Server::bind(config, [device]).await.unwrap();
19423 let (session_tx, mut session_rx) = mpsc::channel(2);
19424 server.sessions.lock().await.insert(
19425 device_id.clone(),
19426 SessionSender {
19427 generation: SessionGeneration::new(1).unwrap(),
19428 anonymous_hotline: false,
19429 tx: session_tx,
19430 },
19431 );
19432
19433 let server_queued = tokio::spawn({
19434 let handle = handle.clone();
19435 let device_id = device_id.clone();
19436 async move {
19437 handle
19438 .send_confirmed(Command::new(
19439 device_id,
19440 CommandAction::SetMicrophoneMode { enabled: false },
19441 ))
19442 .await
19443 }
19444 });
19445 let ServerCommand::Confirmed {
19446 command,
19447 written,
19448 expires_at,
19449 } = server.command_rx.recv().await.unwrap()
19450 else {
19451 panic!("expected a server-queued confirmed command")
19452 };
19453 tokio::time::advance(ORDERING_ACKNOWLEDGEMENT_TIMEOUT).await;
19454 assert!(matches!(
19455 server_queued.await.unwrap(),
19456 Err(ServerError::CommandAcknowledgementTimeout)
19457 ));
19458 server
19459 .dispatch_confirmed(command, written, expires_at)
19460 .await;
19461 assert!(matches!(
19462 session_rx.try_recv(),
19463 Err(mpsc::error::TryRecvError::Empty)
19464 ));
19465
19466 let session_queued = tokio::spawn({
19467 let handle = handle.clone();
19468 async move {
19469 handle
19470 .send_confirmed(Command::new(
19471 device_id,
19472 CommandAction::SetMicrophoneMode { enabled: true },
19473 ))
19474 .await
19475 }
19476 });
19477 let ServerCommand::Confirmed {
19478 command,
19479 written,
19480 expires_at,
19481 } = server.command_rx.recv().await.unwrap()
19482 else {
19483 panic!("expected another server-queued confirmed command")
19484 };
19485 server
19486 .dispatch_confirmed(command, written, expires_at)
19487 .await;
19488 let queued = session_rx.recv().await.unwrap();
19489 tokio::time::advance(ORDERING_ACKNOWLEDGEMENT_TIMEOUT).await;
19490 assert!(matches!(
19491 session_queued.await.unwrap(),
19492 Err(ServerError::CommandAcknowledgementTimeout)
19493 ));
19494 assert!(prepare_session_command(queued).is_none());
19495 }
19496
19497 #[tokio::test]
19498 async fn forwarding_collection_commands_propagate_confirmed_writer_failures() {
19499 let (command_tx, mut command_rx) = mpsc::channel(3);
19500 let handle = ServerHandle {
19501 command_tx,
19502 next_call_id: Arc::new(AtomicU64::new(1)),
19503 latest_media_statistics: Arc::new(RwLock::new(HashMap::new())),
19504 call_answer_order: Arc::new(RwLock::new(CallSelectionOrder::OldestFirst)),
19505 };
19506 let device_id = DeviceId::new("SEP001122334455").unwrap();
19507 let commands = [
19508 Command::new(
19509 device_id.clone(),
19510 CommandAction::BeginCall {
19511 line_instance: LineInstance(1),
19512 call_id: CallId(42),
19513 codec: Codec::Pcmu,
19514 },
19515 ),
19516 Command::new(
19517 device_id.clone(),
19518 CommandAction::DisplayPrompt {
19519 call_id: CallId(42),
19520 timeout_seconds: 0,
19521 text: "Enter forwarding destination".into(),
19522 },
19523 ),
19524 Command::new(
19525 device_id,
19526 CommandAction::CloseCall {
19527 call_id: CallId(42),
19528 },
19529 ),
19530 ];
19531
19532 for (index, command) in commands.into_iter().enumerate() {
19533 let pending = tokio::spawn({
19534 let handle = handle.clone();
19535 async move { handle.send_confirmed(command).await }
19536 });
19537 let ServerCommand::Confirmed { written, .. } = command_rx.recv().await.unwrap() else {
19538 panic!("expected a confirmed forwarding command")
19539 };
19540 assert!(!pending.is_finished());
19541 written
19542 .send(Err(format!("forwarding writer failed at stage {index}")))
19543 .unwrap();
19544 assert!(matches!(
19545 pending.await.unwrap(),
19546 Err(ServerError::CommandWrite(message))
19547 if message == format!("forwarding writer failed at stage {index}")
19548 ));
19549 }
19550 }
19551
19552 #[tokio::test]
19553 async fn two_phone_shared_offer_honors_ring_policy_and_remote_control_events() {
19554 let protocol = ProtocolVersion::V22;
19555 let first_id = DeviceId::new("SEP001122334455").unwrap();
19556 let second_id = DeviceId::new("SEP112233445566").unwrap();
19557 let config = ServerConfig {
19558 bind: "127.0.0.1:0".parse().unwrap(),
19559 advertised_address: Ipv4Addr::LOCALHOST,
19560 ..ServerConfig::default()
19561 };
19562 let mut first_definition = definition_for(first_id.as_str());
19563 let mut second_definition = definition_for(second_id.as_str());
19564 for definition in [&mut first_definition, &mut second_definition] {
19565 definition.soft_keys = profile_with(
19566 KeyMode::OnHookStealable,
19567 vec![
19568 SoftKey::Intercept,
19569 SoftKey::Barge,
19570 SoftKey::Conference,
19571 SoftKey::NewCall,
19572 ],
19573 );
19574 }
19575 let stealable_mask = second_definition
19576 .soft_keys
19577 .valid_mask(KeyMode::OnHookStealable);
19578 let (server, handle, mut events) =
19579 Server::bind(config, [first_definition, second_definition])
19580 .await
19581 .unwrap();
19582 let address = server.local_addr().unwrap();
19583 let task = tokio::spawn(server.run());
19584 let mut first = TcpStream::connect(address).await.unwrap();
19585 let mut second = TcpStream::connect(address).await.unwrap();
19586 let mut first_decoder = FrameDecoder::new();
19587 let mut second_decoder = FrameDecoder::new();
19588 first
19589 .write_all(®ister_bytes_for_device(protocol, 115, first_id.as_str()))
19590 .await
19591 .unwrap();
19592 second
19593 .write_all(®ister_bytes_for_device(
19594 protocol,
19595 115,
19596 second_id.as_str(),
19597 ))
19598 .await
19599 .unwrap();
19600 read_until_message(&mut first, &mut first_decoder, id::REGISTER_ACK).await;
19601 read_until_message(&mut second, &mut second_decoder, id::REGISTER_ACK).await;
19602 let mut registered = HashSet::new();
19603 while registered.len() < 2 {
19604 if let Some(Event::Device(DeviceEvent {
19605 session_generation: _,
19606 device_id: _,
19607 event: DeviceEventKind::Registered(registration),
19608 })) = events.recv().await
19609 {
19610 registered.insert(registration.id);
19611 }
19612 }
19613 assert_eq!(
19614 registered,
19615 HashSet::from([first_id.clone(), second_id.clone()])
19616 );
19617
19618 let info = CallInfo {
19619 direction: crate::types::CallDirection::Inbound,
19620 calling_name: "Caller".into(),
19621 calling_number: "1002".into(),
19622 called_name: "Shared desk".into(),
19623 called_number: "1001".into(),
19624 ..CallInfo::default()
19625 };
19626 let first_call = CallId(101);
19627 let second_call = CallId(102);
19628 handle
19629 .try_offer_incoming_call_with_id_and_ring(
19630 first_id.clone(),
19631 LineInstance::new(1),
19632 first_call,
19633 info.clone(),
19634 true,
19635 )
19636 .unwrap();
19637 handle
19638 .try_offer_incoming_call_with_id_and_ring(
19639 second_id.clone(),
19640 LineInstance::new(1),
19641 second_call,
19642 info,
19643 false,
19644 )
19645 .unwrap();
19646 let first_frames = read_until_message(
19647 &mut first,
19648 &mut first_decoder,
19649 id::DISPLAY_DYNAMIC_PROMPT_STATUS,
19650 )
19651 .await;
19652 let second_frames = read_until_message(
19653 &mut second,
19654 &mut second_decoder,
19655 id::DISPLAY_DYNAMIC_PROMPT_STATUS,
19656 )
19657 .await;
19658 assert!(first_frames.iter().any(|frame| matches!(
19659 ServerMessage::decode(frame.clone(), protocol),
19660 Ok(ServerMessage::SetRinger {
19661 mode: RingerMode::Inside,
19662 ..
19663 })
19664 )));
19665 assert!(!second_frames.iter().any(|frame| matches!(
19666 ServerMessage::decode(frame.clone(), protocol),
19667 Ok(ServerMessage::SetRinger {
19668 mode: RingerMode::Inside,
19669 ..
19670 })
19671 )));
19672
19673 for (device_id, call_id) in [
19674 (first_id.clone(), first_call),
19675 (second_id.clone(), second_call),
19676 ] {
19677 handle
19678 .send(Command::new(
19679 device_id,
19680 CommandAction::SetCallState {
19681 call_id,
19682 state: CallState::RemoteMultiline,
19683 },
19684 ))
19685 .await
19686 .unwrap();
19687 }
19688 let first_frames =
19689 read_until_message(&mut first, &mut first_decoder, id::SELECT_SOFT_KEYS).await;
19690 let second_frames =
19691 read_until_message(&mut second, &mut second_decoder, id::SELECT_SOFT_KEYS).await;
19692 assert!(first_frames.iter().any(|frame| matches!(
19693 ServerMessage::decode(frame.clone(), protocol),
19694 Ok(ServerMessage::SetRinger {
19695 mode: RingerMode::Off,
19696 ..
19697 })
19698 )));
19699 assert!(first_frames.iter().any(|frame| matches!(
19700 ServerMessage::decode(frame.clone(), protocol),
19701 Ok(ServerMessage::SetLamp {
19702 stimulus: ButtonType::Line,
19703 mode: LampMode::On,
19704 ..
19705 })
19706 )));
19707 assert!(second_frames.iter().any(|frame| matches!(
19708 ServerMessage::decode(frame.clone(), protocol),
19709 Ok(ServerMessage::SelectSoftKeys {
19710 set: KeyMode::OnHookStealable,
19711 valid_mask,
19712 ..
19713 }) if valid_mask == stealable_mask
19714 )));
19715
19716 second
19717 .write_all(
19718 &ClientMessage::SoftKeyEvent {
19719 event: SoftKey::Intercept.wire_value(),
19720 line_instance: 1,
19721 call_reference: 0,
19722 }
19723 .encode(protocol)
19724 .unwrap(),
19725 )
19726 .await
19727 .unwrap();
19728 assert!(matches!(
19729 events.recv().await,
19730 Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::SoftKey {
19731 call_id: Some(call_id),
19732 soft_key: SoftKey::Intercept,
19733 ..
19734 } })) if device_id == second_id && call_id == second_call
19735 ));
19736 second
19737 .write_all(
19738 &ClientMessage::SoftKeyEvent {
19739 event: SoftKey::Barge.wire_value(),
19740 line_instance: 1,
19741 call_reference: 0,
19742 }
19743 .encode(protocol)
19744 .unwrap(),
19745 )
19746 .await
19747 .unwrap();
19748 assert!(matches!(
19749 events.recv().await,
19750 Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::SoftKey {
19751 call_id: Some(call_id),
19752 soft_key: SoftKey::Barge,
19753 ..
19754 } })) if device_id == second_id && call_id == second_call
19755 ));
19756 second
19757 .write_all(
19758 &ClientMessage::Stimulus {
19759 stimulus: Stimulus::Conference,
19760 instance: 1,
19761 call_reference: 0,
19762 status: 0,
19763 }
19764 .encode(protocol)
19765 .unwrap(),
19766 )
19767 .await
19768 .unwrap();
19769 assert!(matches!(
19770 events.recv().await,
19771 Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::SoftKey {
19772 call_id: Some(call_id),
19773 soft_key: SoftKey::Conference,
19774 ..
19775 } })) if device_id == second_id && call_id == second_call
19776 ));
19777 second
19778 .write_all(
19779 &ClientMessage::Stimulus {
19780 stimulus: Stimulus::Line,
19781 instance: 1,
19782 call_reference: 0,
19783 status: 0,
19784 }
19785 .encode(protocol)
19786 .unwrap(),
19787 )
19788 .await
19789 .unwrap();
19790 assert!(matches!(
19791 events.recv().await,
19792 Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::LineButton {
19793 call_id: Some(call_id),
19794 ..
19795 } })) if device_id == second_id && call_id == second_call
19796 ));
19797
19798 second
19799 .write_all(
19800 &ClientMessage::OnHook {
19801 line_instance: 1,
19802 call_reference: 0,
19803 }
19804 .encode(protocol)
19805 .unwrap(),
19806 )
19807 .await
19808 .unwrap();
19809 read_until_message(&mut second, &mut second_decoder, id::DEFINE_TIME_DATE).await;
19810 assert!(matches!(
19811 events.recv().await,
19812 Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::OnHook {
19813 call_id,
19814 ..
19815 } })) if device_id == second_id && call_id == second_call
19816 ));
19817 handle
19818 .send(Command::new(
19819 second_id.clone(),
19820 CommandAction::SetCallState {
19821 call_id: second_call,
19822 state: CallState::RemoteMultiline,
19823 },
19824 ))
19825 .await
19826 .unwrap();
19827 let restored =
19828 read_until_message(&mut second, &mut second_decoder, id::SELECT_SOFT_KEYS).await;
19829 assert!(restored.iter().any(|frame| matches!(
19830 ServerMessage::decode(frame.clone(), protocol),
19831 Ok(ServerMessage::CallState {
19832 state: CallState::RemoteMultiline,
19833 ..
19834 })
19835 )));
19836
19837 for (device_id, call_id) in [
19838 (first_id.clone(), first_call),
19839 (second_id.clone(), second_call),
19840 ] {
19841 handle
19842 .send(Command::new(
19843 device_id,
19844 CommandAction::CloseCall { call_id },
19845 ))
19846 .await
19847 .unwrap();
19848 }
19849 let first_close = read_until_message(&mut first, &mut first_decoder, id::CALL_STATE).await;
19850 let second_close =
19851 read_until_message(&mut second, &mut second_decoder, id::CALL_STATE).await;
19852 for frames in [first_close, second_close] {
19853 assert!(frames.iter().any(|frame| matches!(
19854 ServerMessage::decode(frame.clone(), protocol),
19855 Ok(ServerMessage::CallState {
19856 state: CallState::OnHook,
19857 ..
19858 })
19859 )));
19860 }
19861
19862 handle.shutdown().await.unwrap();
19863 task.await.unwrap().unwrap();
19864 }
19865
19866 #[tokio::test]
19867 async fn standalone_server_registers_and_serves_line_status() {
19868 for protocol in [
19869 ProtocolVersion::V3,
19870 ProtocolVersion::V17,
19871 ProtocolVersion::V22,
19872 ] {
19873 let config = ServerConfig {
19874 bind: "127.0.0.1:0".parse().unwrap(),
19875 advertised_address: Ipv4Addr::LOCALHOST,
19876 ..ServerConfig::default()
19877 };
19878 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
19879 let address = server.local_addr().unwrap();
19880 let task = tokio::spawn(server.run());
19881 let mut phone = TcpStream::connect(address).await.unwrap();
19882 let mut alarm_payload = vec![0; 2_000];
19883 let alarm = b"<?xml version=\"1.0\"?><x-cisco-alarm></x-cisco-alarm>";
19884 alarm_payload[..alarm.len()].copy_from_slice(alarm);
19885 phone
19886 .write_all(
19887 &Frame::new(0, id::XML_ALARM, alarm_payload)
19888 .encode()
19889 .unwrap(),
19890 )
19891 .await
19892 .unwrap();
19893 phone.write_all(®ister_bytes(protocol)).await.unwrap();
19894 let mut decoder = FrameDecoder::new();
19895 let mut buffer = [0_u8; 1024];
19896 let frames = read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
19897 let ack = frames
19898 .iter()
19899 .find(|frame| frame.message_id == id::REGISTER_ACK)
19900 .cloned()
19901 .unwrap();
19902 assert_eq!(
19903 ServerMessage::decode(ack, protocol).unwrap(),
19904 ServerMessage::RegisterAck {
19905 keepalive_seconds: 30,
19906 secondary_keepalive_seconds: 30,
19907 protocol,
19908 features: PhoneFeatures::empty(),
19909 date_template: Default::default(),
19910 }
19911 );
19912 assert!(
19913 frames
19914 .iter()
19915 .any(|frame| frame.message_id == id::CAPABILITIES_REQ)
19916 );
19917 assert!(matches!(
19918 events.recv().await,
19919 Some(Event::Device(DeviceEvent {
19920 session_generation: _,
19921 device_id: _,
19922 event: DeviceEventKind::Registered(_)
19923 }))
19924 ));
19925
19926 let malformed = Frame::new(protocol.wire(), id::IP_PORT, vec![0, 1])
19927 .encode()
19928 .unwrap();
19929 let keepalive = Frame::new(protocol.wire(), id::KEEP_ALIVE, vec![0; 4])
19930 .encode()
19931 .unwrap();
19932 phone
19933 .write_all(&[malformed, keepalive].concat())
19934 .await
19935 .unwrap();
19936 let count = phone.read(&mut buffer).await.unwrap();
19937 let frames = decoder.push(&buffer[..count]).unwrap();
19938 assert!(
19939 frames
19940 .iter()
19941 .any(|frame| frame.message_id == id::KEEP_ALIVE_ACK),
19942 "session did not survive a malformed application message"
19943 );
19944 assert!(matches!(
19945 events.recv().await,
19946 Some(Event::ProtocolWarning {
19947 message_id: id::IP_PORT,
19948 ..
19949 })
19950 ));
19951
19952 phone
19953 .write_all(
19954 &Frame::new(
19955 protocol.wire(),
19956 id::LINE_STAT_REQ,
19957 1_u32.to_le_bytes().to_vec(),
19958 )
19959 .encode()
19960 .unwrap(),
19961 )
19962 .await
19963 .unwrap();
19964 let line_message_id = if protocol >= ProtocolVersion::V17 {
19965 id::LINE_STAT_DYNAMIC
19966 } else {
19967 id::LINE_STAT
19968 };
19969 let frames = read_until_message(&mut phone, &mut decoder, line_message_id).await;
19970 let line = frames
19971 .iter()
19972 .find(|frame| frame.message_id == line_message_id)
19973 .unwrap();
19974 assert!(matches!(
19975 ServerMessage::decode(line.clone(), protocol).unwrap(),
19976 ServerMessage::LineStatus { number, .. } if number == "1001"
19977 ));
19978
19979 phone
19980 .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
19981 .await
19982 .unwrap();
19983 let frames = read_until_message(&mut phone, &mut decoder, id::SERVER_RES).await;
19984 let response = frames
19985 .into_iter()
19986 .find(|frame| frame.message_id == id::SERVER_RES)
19987 .unwrap();
19988 assert_eq!(
19989 ServerMessage::decode(response, protocol).unwrap(),
19990 ServerMessage::ServerResponse {
19991 servers: vec![SignalingServerEndpoint {
19992 name: "sccp-protocol".into(),
19993 address: address.ip(),
19994 port: NonZeroU16::new(address.port()).unwrap(),
19995 }],
19996 }
19997 );
19998
19999 let cancelled_call = CallId(9_001);
20000 handle
20001 .try_send(Command::new(
20002 DeviceId::new("SEP001122334455").unwrap(),
20003 CommandAction::CloseCall {
20004 call_id: cancelled_call,
20005 },
20006 ))
20007 .unwrap();
20008 handle
20009 .try_offer_incoming_call_with_id(
20010 DeviceId::new("SEP001122334455").unwrap(),
20011 LineInstance::new(1),
20012 cancelled_call,
20013 CallInfo {
20014 direction: crate::types::CallDirection::Inbound,
20015 calling_name: "Cancelled caller".into(),
20016 calling_number: "1009".into(),
20017 called_name: "Desk".into(),
20018 called_number: "1001".into(),
20019 ..CallInfo::default()
20020 },
20021 )
20022 .unwrap();
20023 handle
20024 .try_send(Command::new(
20025 DeviceId::new("SEP001122334455").unwrap(),
20026 CommandAction::SetCallState {
20027 call_id: cancelled_call,
20028 state: CallState::Connected,
20029 },
20030 ))
20031 .unwrap();
20032 assert!(
20033 tokio::time::timeout(Duration::from_millis(50), phone.read(&mut buffer))
20034 .await
20035 .is_err(),
20036 "a call cancelled before its offer still rang the phone"
20037 );
20038
20039 let incoming = handle
20040 .offer_incoming_call(
20041 DeviceId::new("SEP001122334455").unwrap(),
20042 LineInstance::new(1),
20043 CallInfo {
20044 direction: crate::types::CallDirection::Inbound,
20045 calling_name: "Caller".into(),
20046 calling_number: "1002".into(),
20047 called_name: "Desk".into(),
20048 called_number: "1001".into(),
20049 ..CallInfo::default()
20050 },
20051 )
20052 .await
20053 .unwrap();
20054 let frames = read_until_message(
20055 &mut phone,
20056 &mut decoder,
20057 if protocol >= ProtocolVersion::V8 {
20058 id::DISPLAY_DYNAMIC_PROMPT_STATUS
20059 } else {
20060 id::DISPLAY_PROMPT_STATUS
20061 },
20062 )
20063 .await;
20064 assert!(
20065 frames
20066 .iter()
20067 .all(|frame| frame.message_id != id::ACTIVATE_CALL_PLANE),
20068 "RingIn activated the call plane before answer"
20069 );
20070
20071 phone
20072 .write_all(
20073 &ClientMessage::SoftKeyEvent {
20074 event: SoftKey::Answer.wire_value(),
20075 line_instance: 0,
20076 call_reference: 0,
20077 }
20078 .encode(protocol)
20079 .unwrap(),
20080 )
20081 .await
20082 .unwrap();
20083 let frames = read_until_message(&mut phone, &mut decoder, id::SET_LAMP).await;
20084 let off_hook = frames
20085 .iter()
20086 .position(|frame| {
20087 matches!(
20088 ServerMessage::decode(frame.clone(), protocol),
20089 Ok(ServerMessage::CallState {
20090 state: CallState::OffHook,
20091 ..
20092 })
20093 )
20094 })
20095 .expect("answer did not transition through OffHook");
20096 let activate = frames
20097 .iter()
20098 .position(|frame| frame.message_id == id::ACTIVATE_CALL_PLANE)
20099 .expect("answer did not activate the call plane");
20100 assert!(
20101 off_hook < activate,
20102 "OffHook must precede call-plane activation"
20103 );
20104 let answer_event = events.recv().await;
20105 assert!(
20106 matches!(
20107 answer_event,
20108 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20109 call_id: Some(answered),
20110 soft_key: SoftKey::Answer,
20111 ..
20112 } })) if answered == incoming
20113 ),
20114 "unexpected answer event: {answer_event:?}"
20115 );
20116
20117 handle
20118 .send(Command::new(
20119 DeviceId::new("SEP001122334455").unwrap(),
20120 CommandAction::SetCallState {
20121 call_id: incoming,
20122 state: CallState::Connected,
20123 },
20124 ))
20125 .await
20126 .unwrap();
20127 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
20128 assert!(
20129 frames
20130 .iter()
20131 .any(|frame| frame.message_id == id::SET_SPEAKER_MODE),
20132 "Connected did not enable the active audio accessory"
20133 );
20134 assert!(frames.iter().any(|frame| {
20135 matches!(
20136 ServerMessage::decode(frame.clone(), protocol),
20137 Ok(ServerMessage::DisplayPrompt { text, .. }) if text == "Connected"
20138 )
20139 }));
20140
20141 phone
20142 .write_all(
20143 &ClientMessage::OffHook {
20144 line_instance: 1,
20145 call_reference: incoming.0 as u32,
20146 }
20147 .encode(protocol)
20148 .unwrap(),
20149 )
20150 .await
20151 .unwrap();
20152 assert!(
20153 tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
20154 .await
20155 .is_err(),
20156 "duplicate OffHook rewrote a connected call's handset UI"
20157 );
20158 assert!(
20159 tokio::time::timeout(Duration::from_millis(50), events.recv())
20160 .await
20161 .is_err(),
20162 "duplicate OffHook emitted a second application event"
20163 );
20164
20165 phone
20166 .write_all(
20167 &ClientMessage::SoftKeyEvent {
20168 event: SoftKey::Hold.wire_value(),
20169 line_instance: 1,
20170 call_reference: 0,
20171 }
20172 .encode(protocol)
20173 .unwrap(),
20174 )
20175 .await
20176 .unwrap();
20177 assert!(matches!(
20178 events.recv().await,
20179 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20180 call_id: Some(held),
20181 soft_key: SoftKey::Hold,
20182 ..
20183 } })) if held == incoming
20184 ));
20185 handle
20186 .send(Command::new(
20187 DeviceId::new("SEP001122334455").unwrap(),
20188 CommandAction::SetCallState {
20189 call_id: incoming,
20190 state: CallState::Hold,
20191 },
20192 ))
20193 .await
20194 .unwrap();
20195 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
20196 assert!(frames.iter().any(|frame| matches!(
20197 ServerMessage::decode(frame.clone(), protocol),
20198 Ok(ServerMessage::CallState {
20199 state: CallState::Hold,
20200 ..
20201 })
20202 )));
20203 assert!(frames.iter().any(|frame| matches!(
20204 ServerMessage::decode(frame.clone(), protocol),
20205 Ok(ServerMessage::SetLamp {
20206 mode: LampMode::Wink,
20207 ..
20208 })
20209 )));
20210 assert!(frames.iter().any(|frame| matches!(
20211 ServerMessage::decode(frame.clone(), protocol),
20212 Ok(ServerMessage::SetSpeakerMode(SpeakerMode::Off))
20213 )));
20214 assert!(frames.iter().any(|frame| matches!(
20215 ServerMessage::decode(frame.clone(), protocol),
20216 Ok(ServerMessage::SelectSoftKeys {
20217 set: KeyMode::OnHold,
20218 ..
20219 })
20220 )));
20221
20222 phone
20223 .write_all(
20224 &ClientMessage::SoftKeyEvent {
20225 event: SoftKey::Resume.wire_value(),
20226 line_instance: 1,
20227 call_reference: 0,
20228 }
20229 .encode(protocol)
20230 .unwrap(),
20231 )
20232 .await
20233 .unwrap();
20234 assert!(matches!(
20235 events.recv().await,
20236 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20237 call_id: Some(resumed),
20238 soft_key: SoftKey::Resume,
20239 ..
20240 } })) if resumed == incoming
20241 ));
20242 handle
20243 .send(Command::new(
20244 DeviceId::new("SEP001122334455").unwrap(),
20245 CommandAction::SetCallState {
20246 call_id: incoming,
20247 state: CallState::Connected,
20248 },
20249 ))
20250 .await
20251 .unwrap();
20252 let frames = read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
20253 assert!(frames.iter().any(|frame| matches!(
20254 ServerMessage::decode(frame.clone(), protocol),
20255 Ok(ServerMessage::CallState {
20256 state: CallState::Connected,
20257 ..
20258 })
20259 )));
20260 assert!(frames.iter().any(|frame| matches!(
20261 ServerMessage::decode(frame.clone(), protocol),
20262 Ok(ServerMessage::SetSpeakerMode(SpeakerMode::On))
20263 )));
20264 assert!(frames.iter().any(|frame| matches!(
20265 ServerMessage::decode(frame.clone(), protocol),
20266 Ok(ServerMessage::SelectSoftKeys {
20267 set: KeyMode::Connected,
20268 ..
20269 })
20270 )));
20271
20272 phone
20273 .write_all(
20274 &ClientMessage::KeypadButton {
20275 button: Digit::Number(5),
20276 line_instance: 1,
20277 call_reference: 0,
20278 wire_layout: None,
20279 }
20280 .encode(protocol)
20281 .unwrap(),
20282 )
20283 .await
20284 .unwrap();
20285 assert!(matches!(
20286 events.recv().await,
20287 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Digit {
20288 call_id,
20289 digit: Digit::Number(5),
20290 ..
20291 } })) if call_id == incoming
20292 ));
20293 assert!(
20294 tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
20295 .await
20296 .is_err(),
20297 "connected DTMF emitted dial-collection UI"
20298 );
20299
20300 phone
20301 .write_all(
20302 &ClientMessage::SoftKeyEvent {
20303 event: SoftKey::EndCall.wire_value(),
20304 line_instance: 1,
20305 call_reference: 0,
20306 }
20307 .encode(protocol)
20308 .unwrap(),
20309 )
20310 .await
20311 .unwrap();
20312 assert!(matches!(
20313 events.recv().await,
20314 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::SoftKey {
20315 call_id: Some(ended),
20316 soft_key: SoftKey::EndCall,
20317 ..
20318 } })) if ended == incoming
20319 ));
20320
20321 handle
20322 .send(Command::new(
20323 DeviceId::new("SEP001122334455").unwrap(),
20324 CommandAction::CloseCall { call_id: incoming },
20325 ))
20326 .await
20327 .unwrap();
20328 let frames = read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
20329 let on_hook = frames
20330 .iter()
20331 .position(|frame| {
20332 matches!(
20333 ServerMessage::decode(frame.clone(), protocol),
20334 Ok(ServerMessage::CallState {
20335 state: CallState::OnHook,
20336 ..
20337 })
20338 )
20339 })
20340 .expect("close did not send OnHook");
20341 let ringer_off = frames
20342 .iter()
20343 .position(|frame| {
20344 matches!(
20345 ServerMessage::decode(frame.clone(), protocol),
20346 Ok(ServerMessage::SetRinger {
20347 mode: RingerMode::Off,
20348 ..
20349 })
20350 )
20351 })
20352 .expect("close did not stop the ringer");
20353 assert!(
20354 on_hook < ringer_off,
20355 "79x1 phones require OnHook before the final ringer-off indication"
20356 );
20357
20358 let mut updated = definition();
20359 let crate::types::ButtonDefinition::Line(line) = &mut updated.buttons[0] else {
20360 panic!("expected line button");
20361 };
20362 line.label = Some("Updated desk".into());
20363 handle.reconfigure([updated]).await.unwrap();
20364 assert!(matches!(
20365 tokio::time::timeout(Duration::from_secs(1), events.recv()).await,
20366 Ok(Some(Event::Device(DeviceEvent { session_generation: _, device_id, event: DeviceEventKind::Disconnected {} })))
20367 if device_id.as_str() == "SEP001122334455"
20368 ));
20369
20370 handle.shutdown().await.unwrap();
20371 task.await.unwrap().unwrap();
20372 }
20373 }
20374
20375 #[tokio::test]
20376 async fn injected_streams_enforce_station_transport_requirements() {
20377 for (requirement, transport, accepted) in [
20378 (
20379 StationTransportRequirement::Clear,
20380 StationTransport::Clear,
20381 true,
20382 ),
20383 (
20384 StationTransportRequirement::Clear,
20385 StationTransport::Secure,
20386 false,
20387 ),
20388 (
20389 StationTransportRequirement::Secure,
20390 StationTransport::Secure,
20391 true,
20392 ),
20393 (
20394 StationTransportRequirement::Secure,
20395 StationTransport::Clear,
20396 false,
20397 ),
20398 (
20399 StationTransportRequirement::Either,
20400 StationTransport::Clear,
20401 true,
20402 ),
20403 (
20404 StationTransportRequirement::Either,
20405 StationTransport::Secure,
20406 true,
20407 ),
20408 ] {
20409 let mut station = definition();
20410 station.transport = requirement;
20411 let config = ServerConfig {
20412 bind: "127.0.0.1:0".parse().unwrap(),
20413 advertised_address: Ipv4Addr::LOCALHOST,
20414 ..ServerConfig::default()
20415 };
20416 let (server, handle, mut events, ingress) =
20417 Server::with_ingress(config, [station]).unwrap();
20418 let task = tokio::spawn(server.run());
20419 let (server_stream, mut phone) = tokio::io::duplex(8_192);
20420 let peer = SocketAddr::from(([127, 0, 0, 1], 40_000));
20421 let local = SocketAddr::from(([127, 0, 0, 1], 2_000));
20422 ingress
20423 .accept(server_stream, peer, local, transport)
20424 .await
20425 .unwrap();
20426 phone
20427 .write_all(®ister_bytes(ProtocolVersion::V22))
20428 .await
20429 .unwrap();
20430
20431 let mut decoder = FrameDecoder::new();
20432 let expected = if accepted {
20433 id::REGISTER_ACK
20434 } else {
20435 id::REGISTER_REJECT
20436 };
20437 let frames = read_until_message(&mut phone, &mut decoder, expected).await;
20438 assert!(frames.iter().any(|frame| frame.message_id == expected));
20439 if accepted {
20440 assert!(matches!(
20441 events.recv().await,
20442 Some(Event::Device(DeviceEvent { session_generation: _,
20443 event: DeviceEventKind::Registered(registration),
20444 ..
20445 })) if registration.transport == transport
20446 ));
20447 }
20448
20449 handle.shutdown().await.unwrap();
20450 task.await.unwrap().unwrap();
20451 }
20452 }
20453
20454 #[tokio::test]
20455 async fn registration_tokens_apply_transport_priority_parity_and_configured_backoff() {
20456 let cases = [
20457 (
20458 RegistrationFallback::Reject,
20459 1,
20460 "SEP001122334455",
20461 StationTransport::Clear,
20462 false,
20463 ),
20464 (
20465 RegistrationFallback::ReturnToPrimary,
20466 1,
20467 "SEP001122334455",
20468 StationTransport::Clear,
20469 true,
20470 ),
20471 (
20472 RegistrationFallback::ReturnToPrimary,
20473 2,
20474 "SEP001122334455",
20475 StationTransport::Clear,
20476 false,
20477 ),
20478 (
20479 RegistrationFallback::DeviceIdOdd,
20480 2,
20481 "SEP001122334455",
20482 StationTransport::Clear,
20483 true,
20484 ),
20485 (
20486 RegistrationFallback::DeviceIdEven,
20487 2,
20488 "SEP001122334455",
20489 StationTransport::Clear,
20490 false,
20491 ),
20492 ];
20493 for (fallback, server_priority, device_id, transport, accepted) in cases {
20494 let station = definition();
20495 let config = ServerConfig {
20496 registration_tokens: RegistrationTokenPolicy {
20497 fallback,
20498 backoff: Duration::from_secs(75),
20499 server_priority,
20500 },
20501 ..ServerConfig::default()
20502 };
20503 let (server, handle, _events, ingress) =
20504 Server::with_ingress(config, [station]).unwrap();
20505 let task = tokio::spawn(server.run());
20506 let (server_stream, mut phone) = tokio::io::duplex(2_048);
20507 ingress
20508 .accept(
20509 server_stream,
20510 SocketAddr::from(([127, 0, 0, 1], 40_000)),
20511 SocketAddr::from(([127, 0, 0, 1], 2_000)),
20512 transport,
20513 )
20514 .await
20515 .unwrap();
20516 phone
20517 .write_all(
20518 &ClientMessage::RegisterToken(crate::message::RegisterTokenMessage {
20519 device_id: DeviceId::new(device_id).unwrap(),
20520 device_instance: 1,
20521 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20522 device_type: DeviceType::from(115),
20523 flags: 0,
20524 })
20525 .encode(ProtocolVersion::V17)
20526 .unwrap(),
20527 )
20528 .await
20529 .unwrap();
20530 let expected = if accepted {
20531 id::REGISTER_TOKEN_ACK
20532 } else {
20533 id::REGISTER_TOKEN_REJECT
20534 };
20535 let mut decoder = FrameDecoder::new();
20536 let frames = read_until_message(&mut phone, &mut decoder, expected).await;
20537 let response = frames
20538 .into_iter()
20539 .find(|frame| frame.message_id == expected)
20540 .unwrap();
20541 if accepted {
20542 assert_eq!(
20543 ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20544 ServerMessage::RegisterTokenAck
20545 );
20546 } else {
20547 assert_eq!(
20548 ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20549 ServerMessage::RegisterTokenReject {
20550 backoff_seconds: 75,
20551 }
20552 );
20553 }
20554 handle.shutdown().await.unwrap();
20555 task.await.unwrap().unwrap();
20556 }
20557
20558 let mut secure_station = definition();
20559 secure_station.transport = StationTransportRequirement::Secure;
20560 let config = ServerConfig {
20561 registration_tokens: RegistrationTokenPolicy {
20562 fallback: RegistrationFallback::ReturnToPrimary,
20563 backoff: Duration::from_secs(90),
20564 server_priority: 1,
20565 },
20566 ..ServerConfig::default()
20567 };
20568 let (server, handle, _events, ingress) =
20569 Server::with_ingress(config, [secure_station]).unwrap();
20570 let task = tokio::spawn(server.run());
20571 let (server_stream, mut phone) = tokio::io::duplex(2_048);
20572 ingress
20573 .accept(
20574 server_stream,
20575 SocketAddr::from(([127, 0, 0, 1], 40_001)),
20576 SocketAddr::from(([127, 0, 0, 1], 2_000)),
20577 StationTransport::Clear,
20578 )
20579 .await
20580 .unwrap();
20581 phone
20582 .write_all(
20583 &ClientMessage::RegisterToken(crate::message::RegisterTokenMessage {
20584 device_id: DeviceId::new("SEP001122334455").unwrap(),
20585 device_instance: 1,
20586 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20587 device_type: DeviceType::from(115),
20588 flags: 0,
20589 })
20590 .encode(ProtocolVersion::V17)
20591 .unwrap(),
20592 )
20593 .await
20594 .unwrap();
20595 let mut decoder = FrameDecoder::new();
20596 let response = read_until_message(&mut phone, &mut decoder, id::REGISTER_TOKEN_REJECT)
20597 .await
20598 .into_iter()
20599 .find(|frame| frame.message_id == id::REGISTER_TOKEN_REJECT)
20600 .unwrap();
20601 assert_eq!(
20602 ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20603 ServerMessage::RegisterTokenReject {
20604 backoff_seconds: 90,
20605 }
20606 );
20607 handle.shutdown().await.unwrap();
20608 task.await.unwrap().unwrap();
20609 }
20610
20611 #[test]
20612 fn registration_token_parity_requires_a_canonical_sep_mac_identity() {
20613 let policy = |fallback| RegistrationTokenPolicy {
20614 fallback,
20615 server_priority: 2,
20616 ..RegistrationTokenPolicy::default()
20617 };
20618 assert!(
20619 policy(RegistrationFallback::DeviceIdOdd)
20620 .accepts(&DeviceId::new("SEP001122334455").unwrap())
20621 );
20622 assert!(
20623 policy(RegistrationFallback::DeviceIdEven)
20624 .accepts(&DeviceId::new("SEP001122334454").unwrap())
20625 );
20626 for device_id in ["ALICE1", "SEP1", "SEP00112233445Z"] {
20627 let device_id = DeviceId::new(device_id).unwrap();
20628 assert!(!policy(RegistrationFallback::DeviceIdOdd).accepts(&device_id));
20629 assert!(!policy(RegistrationFallback::DeviceIdEven).accepts(&device_id));
20630 }
20631 let return_to_primary = RegistrationTokenPolicy {
20632 fallback: RegistrationFallback::ReturnToPrimary,
20633 server_priority: 1,
20634 ..RegistrationTokenPolicy::default()
20635 };
20636 assert!(return_to_primary.accepts(&DeviceId::new("ALICE1").unwrap()));
20637 }
20638
20639 #[tokio::test]
20640 async fn duplicate_registration_token_leaves_the_live_session_addressable() {
20641 let config = ServerConfig {
20642 bind: "127.0.0.1:0".parse().unwrap(),
20643 advertised_address: Ipv4Addr::LOCALHOST,
20644 registration_tokens: RegistrationTokenPolicy {
20645 fallback: RegistrationFallback::ReturnToPrimary,
20646 backoff: Duration::from_secs(75),
20647 server_priority: 1,
20648 },
20649 ..ServerConfig::default()
20650 };
20651 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
20652 let address = server.local_addr().unwrap();
20653 let task = tokio::spawn(server.run());
20654 let protocol = ProtocolVersion::V22;
20655 let device_id = DeviceId::new("SEP001122334455").unwrap();
20656 let call_id = CallId(7001);
20657
20658 let mut phone = TcpStream::connect(address).await.unwrap();
20659 let mut decoder = FrameDecoder::new();
20660 phone.write_all(®ister_bytes(protocol)).await.unwrap();
20661 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
20662 assert!(matches!(
20663 events.recv().await,
20664 Some(Event::Device(DeviceEvent {
20665 session_generation: _,
20666 event: DeviceEventKind::Registered(_),
20667 ..
20668 }))
20669 ));
20670
20671 handle
20672 .send_confirmed(Command::new(
20673 device_id.clone(),
20674 CommandAction::BeginCall {
20675 line_instance: LineInstance::new(1),
20676 call_id,
20677 codec: Codec::Pcmu,
20678 },
20679 ))
20680 .await
20681 .unwrap();
20682 let wire_call_reference = read_until_message(&mut phone, &mut decoder, id::CALL_STATE)
20683 .await
20684 .into_iter()
20685 .find_map(|frame| match ServerMessage::decode(frame, protocol) {
20686 Ok(ServerMessage::CallState { call_reference, .. }) => Some(call_reference),
20687 _ => None,
20688 })
20689 .expect("begin call omitted its wire reference");
20690 handle
20691 .send_confirmed(Command::new(
20692 device_id.clone(),
20693 CommandAction::OpenReceiveChannel {
20694 call_id,
20695 source: None,
20696 codec: Codec::Pcmu,
20697 packet_ms: 20,
20698 max_frames_per_packet: 1,
20699 dtmf_mode: DtmfMode::Skinny,
20700 audio_processing: AudioProcessingPolicy::default(),
20701 },
20702 ))
20703 .await
20704 .unwrap();
20705 let frames = read_until_message(&mut phone, &mut decoder, id::OPEN_RECEIVE_CHANNEL).await;
20706 let media_party = open_receive_request_party(&frames, protocol);
20707 phone
20708 .write_all(
20709 &ClientMessage::OpenReceiveChannelAck {
20710 status: MediaStatus::Ok,
20711 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20712 port: 4000,
20713 call_reference: wire_call_reference,
20714 passthrough_party_id: media_party,
20715 }
20716 .encode(protocol)
20717 .unwrap(),
20718 )
20719 .await
20720 .unwrap();
20721 assert!(matches!(
20722 events.recv().await,
20723 Some(Event::Device(DeviceEvent { session_generation: _,
20724 event: DeviceEventKind::ReceiveChannelOpened {
20725 call_id: actual_call_id,
20726 ..
20727 },
20728 ..
20729 })) if actual_call_id == call_id
20730 ));
20731
20732 let mut contender = TcpStream::connect(address).await.unwrap();
20733 let mut contender_decoder = FrameDecoder::new();
20734 contender
20735 .write_all(
20736 &ClientMessage::RegisterToken(crate::message::RegisterTokenMessage {
20737 device_id: device_id.clone(),
20738 device_instance: 1,
20739 address: IpAddr::V4(Ipv4Addr::LOCALHOST),
20740 device_type: DeviceType::from(115),
20741 flags: 0,
20742 })
20743 .encode(ProtocolVersion::V17)
20744 .unwrap(),
20745 )
20746 .await
20747 .unwrap();
20748 let response = read_until_message(
20749 &mut contender,
20750 &mut contender_decoder,
20751 id::REGISTER_TOKEN_REJECT,
20752 )
20753 .await
20754 .into_iter()
20755 .find(|frame| frame.message_id == id::REGISTER_TOKEN_REJECT)
20756 .unwrap();
20757 assert_eq!(
20758 ServerMessage::decode(response, ProtocolVersion::V17).unwrap(),
20759 ServerMessage::RegisterTokenReject {
20760 backoff_seconds: 75,
20761 }
20762 );
20763
20764 handle
20765 .send_confirmed(Command::new(
20766 device_id.clone(),
20767 CommandAction::SetCallState {
20768 call_id,
20769 state: CallState::Connected,
20770 },
20771 ))
20772 .await
20773 .unwrap();
20774 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
20775 assert!(frames.into_iter().any(|frame| matches!(
20776 ServerMessage::decode(frame, protocol),
20777 Ok(ServerMessage::CallState {
20778 state: CallState::Connected,
20779 ..
20780 })
20781 )));
20782 handle
20783 .send_confirmed(Command::new(
20784 device_id,
20785 CommandAction::CloseReceiveChannel { call_id },
20786 ))
20787 .await
20788 .unwrap();
20789 read_until_message(&mut phone, &mut decoder, id::CLOSE_RECEIVE_CHANNEL).await;
20790 phone
20791 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
20792 .await
20793 .unwrap();
20794 read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
20795
20796 handle.shutdown().await.unwrap();
20797 task.await.unwrap().unwrap();
20798 }
20799
20800 #[tokio::test]
20801 async fn server_list_selects_ordered_endpoints_for_the_active_transport() {
20802 let config = ServerConfig {
20803 signaling_servers: vec![
20804 SignalingServerRoute {
20805 priority: 2,
20806 name: "backup".into(),
20807 address: "192.0.2.20".parse().unwrap(),
20808 clear_port: NonZeroU16::new(2001),
20809 secure_port: None,
20810 },
20811 SignalingServerRoute {
20812 priority: 1,
20813 name: "primary".into(),
20814 address: "192.0.2.10".parse().unwrap(),
20815 clear_port: NonZeroU16::new(2000),
20816 secure_port: NonZeroU16::new(2443),
20817 },
20818 ],
20819 ..ServerConfig::default()
20820 };
20821 let (server, handle, mut events, ingress) =
20822 Server::with_ingress(config, [definition()]).unwrap();
20823 let task = tokio::spawn(server.run());
20824
20825 for (index, transport, expected) in [
20826 (
20827 0,
20828 StationTransport::Clear,
20829 vec![
20830 SignalingServerEndpoint {
20831 name: "primary".into(),
20832 address: "192.0.2.10".parse().unwrap(),
20833 port: NonZeroU16::new(2000).unwrap(),
20834 },
20835 SignalingServerEndpoint {
20836 name: "backup".into(),
20837 address: "192.0.2.20".parse().unwrap(),
20838 port: NonZeroU16::new(2001).unwrap(),
20839 },
20840 ],
20841 ),
20842 (
20843 1,
20844 StationTransport::Secure,
20845 vec![SignalingServerEndpoint {
20846 name: "primary".into(),
20847 address: "192.0.2.10".parse().unwrap(),
20848 port: NonZeroU16::new(2443).unwrap(),
20849 }],
20850 ),
20851 ] {
20852 let (server_stream, mut phone) = tokio::io::duplex(8_192);
20853 ingress
20854 .accept(
20855 server_stream,
20856 SocketAddr::from(([127, 0, 0, 1], 40_000 + index)),
20857 SocketAddr::from(([127, 0, 0, 1], if index == 0 { 2_000 } else { 2_443 })),
20858 transport,
20859 )
20860 .await
20861 .unwrap();
20862 let protocol = ProtocolVersion::V22;
20863 phone.write_all(®ister_bytes(protocol)).await.unwrap();
20864 let mut decoder = FrameDecoder::new();
20865 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
20866 loop {
20867 if matches!(
20868 events.recv().await,
20869 Some(Event::Device(DeviceEvent {
20870 session_generation: _,
20871 event: DeviceEventKind::Registered(_),
20872 ..
20873 }))
20874 ) {
20875 break;
20876 }
20877 }
20878 phone
20879 .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
20880 .await
20881 .unwrap();
20882 let response = read_until_message(&mut phone, &mut decoder, id::SERVER_RES)
20883 .await
20884 .into_iter()
20885 .find(|frame| frame.message_id == id::SERVER_RES)
20886 .unwrap();
20887 assert_eq!(
20888 ServerMessage::decode(response, protocol).unwrap(),
20889 ServerMessage::ServerResponse { servers: expected }
20890 );
20891 }
20892
20893 handle.shutdown().await.unwrap();
20894 task.await.unwrap().unwrap();
20895 }
20896
20897 #[tokio::test]
20898 async fn server_list_never_empties_when_routes_do_not_fit_the_session() {
20899 let config = ServerConfig {
20900 advertised_address: "192.0.2.99".parse().unwrap(),
20901 signaling_servers: vec![SignalingServerRoute {
20902 priority: 1,
20903 name: "secure-v6".into(),
20904 address: "2001:db8::20".parse().unwrap(),
20905 clear_port: None,
20906 secure_port: NonZeroU16::new(2443),
20907 }],
20908 ..ServerConfig::default()
20909 };
20910 let (server, handle, mut events, ingress) =
20911 Server::with_ingress(config, [definition()]).unwrap();
20912 let task = tokio::spawn(server.run());
20913
20914 for (offset, transport, local_address, expected_address) in [
20915 (
20916 0,
20917 StationTransport::Clear,
20918 "192.0.2.30".parse().unwrap(),
20919 "192.0.2.30".parse().unwrap(),
20920 ),
20921 (
20922 1,
20923 StationTransport::Secure,
20924 "192.0.2.31".parse().unwrap(),
20925 "192.0.2.31".parse().unwrap(),
20926 ),
20927 (
20928 2,
20929 StationTransport::Secure,
20930 "2001:db8::30".parse().unwrap(),
20931 "192.0.2.99".parse().unwrap(),
20932 ),
20933 ] {
20934 let local = SocketAddr::new(
20935 local_address,
20936 if transport == StationTransport::Clear {
20937 2000
20938 } else {
20939 2443
20940 },
20941 );
20942 let (server_stream, mut phone) = tokio::io::duplex(8_192);
20943 ingress
20944 .accept(
20945 server_stream,
20946 SocketAddr::from(([127, 0, 0, 1], 41_000 + offset)),
20947 local,
20948 transport,
20949 )
20950 .await
20951 .unwrap();
20952 let protocol = ProtocolVersion::V3;
20953 phone.write_all(®ister_bytes(protocol)).await.unwrap();
20954 let mut decoder = FrameDecoder::new();
20955 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
20956 while !matches!(
20957 events.recv().await,
20958 Some(Event::Device(DeviceEvent {
20959 session_generation: _,
20960 event: DeviceEventKind::Registered(_),
20961 ..
20962 }))
20963 ) {}
20964 phone
20965 .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
20966 .await
20967 .unwrap();
20968 let response = read_until_message(&mut phone, &mut decoder, id::SERVER_RES)
20969 .await
20970 .into_iter()
20971 .find(|frame| frame.message_id == id::SERVER_RES)
20972 .unwrap();
20973 assert_eq!(
20974 ServerMessage::decode(response, protocol).unwrap(),
20975 ServerMessage::ServerResponse {
20976 servers: vec![SignalingServerEndpoint {
20977 name: "sccp-protocol".into(),
20978 address: expected_address,
20979 port: NonZeroU16::new(local.port()).unwrap(),
20980 }]
20981 }
20982 );
20983 }
20984
20985 handle.shutdown().await.unwrap();
20986 task.await.unwrap().unwrap();
20987 }
20988
20989 #[tokio::test(start_paused = true)]
20990 async fn secondary_sessions_use_the_secondary_keepalive_deadline() {
20991 let config = ServerConfig {
20992 keepalive_seconds: 5,
20993 secondary_keepalive_seconds: 20,
20994 registration_tokens: RegistrationTokenPolicy {
20995 server_priority: 2,
20996 ..RegistrationTokenPolicy::default()
20997 },
20998 ..ServerConfig::default()
20999 };
21000 let (server, handle, mut events, ingress) =
21001 Server::with_ingress(config, [definition()]).unwrap();
21002 let task = tokio::spawn(server.run());
21003 let (server_stream, mut phone) = tokio::io::duplex(8_192);
21004 ingress
21005 .accept(
21006 server_stream,
21007 SocketAddr::from(([127, 0, 0, 1], 40_000)),
21008 SocketAddr::from(([127, 0, 0, 1], 2_000)),
21009 StationTransport::Clear,
21010 )
21011 .await
21012 .unwrap();
21013 let protocol = ProtocolVersion::V22;
21014 phone.write_all(®ister_bytes(protocol)).await.unwrap();
21015 let mut decoder = FrameDecoder::new();
21016 let frames = read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21017 let acknowledgement = frames
21018 .into_iter()
21019 .find(|frame| frame.message_id == id::REGISTER_ACK)
21020 .unwrap();
21021 assert_eq!(
21022 ServerMessage::decode(acknowledgement, protocol).unwrap(),
21023 ServerMessage::RegisterAck {
21024 keepalive_seconds: 5,
21025 secondary_keepalive_seconds: 20,
21026 protocol,
21027 features: PhoneFeatures::empty(),
21028 date_template: Default::default(),
21029 }
21030 );
21031 assert!(matches!(
21032 events.recv().await,
21033 Some(Event::Device(DeviceEvent {
21034 session_generation: _,
21035 event: DeviceEventKind::Registered(_),
21036 ..
21037 }))
21038 ));
21039
21040 tokio::time::advance(Duration::from_secs(59)).await;
21041 tokio::task::yield_now().await;
21042 assert!(events.try_recv().is_err());
21043 tokio::time::advance(Duration::from_secs(2)).await;
21044 assert!(matches!(
21045 events.recv().await,
21046 Some(Event::Device(DeviceEvent {
21047 session_generation: _,
21048 event: DeviceEventKind::Disconnected {},
21049 ..
21050 }))
21051 ));
21052
21053 handle.shutdown().await.unwrap();
21054 task.await.unwrap().unwrap();
21055 }
21056
21057 #[derive(Debug)]
21058 struct RecordingSocketQos {
21059 applied: Arc<std::sync::Mutex<Vec<SignalingQos>>>,
21060 fail: bool,
21061 }
21062
21063 impl StationSocketQos for RecordingSocketQos {
21064 fn apply(&self, qos: SignalingQos) -> SocketQosReport {
21065 self.applied.lock().unwrap().push(qos);
21066 if self.fail {
21067 SocketQosReport::failed(
21068 SocketQosMark::SocketPriority,
21069 std::io::Error::new(std::io::ErrorKind::Unsupported, "test platform"),
21070 )
21071 } else {
21072 SocketQosReport::default()
21073 }
21074 }
21075 }
21076
21077 #[tokio::test]
21078 async fn registration_applies_device_socket_qos_without_making_failure_fatal() {
21079 let baseline = SignalingQos::new(8, 1);
21080 let device_policy = SignalingQos::new(26, 5);
21081 let mut station = definition();
21082 station.signaling_qos = Some(device_policy);
21083 let config = ServerConfig {
21084 signaling_qos: baseline,
21085 ..ServerConfig::default()
21086 };
21087 let (server, handle, mut events, ingress) =
21088 Server::with_ingress(config, [station]).unwrap();
21089 let task = tokio::spawn(server.run());
21090 let (server_stream, mut phone) = tokio::io::duplex(8_192);
21091 let applied = Arc::new(std::sync::Mutex::new(Vec::new()));
21092 ingress
21093 .accept_with_socket_qos(
21094 server_stream,
21095 SocketAddr::from(([127, 0, 0, 1], 40_000)),
21096 SocketAddr::from(([127, 0, 0, 1], 2_000)),
21097 StationTransport::Clear,
21098 RecordingSocketQos {
21099 applied: Arc::clone(&applied),
21100 fail: true,
21101 },
21102 )
21103 .await
21104 .unwrap();
21105 phone
21106 .write_all(®ister_bytes(ProtocolVersion::V22))
21107 .await
21108 .unwrap();
21109
21110 let mut decoder = FrameDecoder::new();
21111 let frames = read_until_message(&mut phone, &mut decoder, id::REGISTER_ACK).await;
21112 assert!(
21113 frames
21114 .iter()
21115 .any(|frame| frame.message_id == id::REGISTER_ACK)
21116 );
21117 assert!(matches!(
21118 events.recv().await,
21119 Some(Event::Device(DeviceEvent {
21120 session_generation: _,
21121 event: DeviceEventKind::Registered(_),
21122 ..
21123 }))
21124 ));
21125 assert_eq!(*applied.lock().unwrap(), vec![baseline, device_policy]);
21126
21127 handle.shutdown().await.unwrap();
21128 task.await.unwrap().unwrap();
21129 }
21130
21131 #[tokio::test]
21132 async fn headset_and_accessory_changes_are_typed_and_duplicate_stable() {
21133 let config = ServerConfig {
21134 bind: "127.0.0.1:0".parse().unwrap(),
21135 advertised_address: Ipv4Addr::LOCALHOST,
21136 ..ServerConfig::default()
21137 };
21138 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21139 let address = server.local_addr().unwrap();
21140 let task = tokio::spawn(server.run());
21141 let mut phone = TcpStream::connect(address).await.unwrap();
21142 let mut decoder = FrameDecoder::new();
21143 let protocol = ProtocolVersion::V22;
21144
21145 phone.write_all(®ister_bytes(protocol)).await.unwrap();
21146 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21147 assert!(matches!(
21148 events.recv().await,
21149 Some(Event::Device(DeviceEvent {
21150 session_generation: _,
21151 device_id: _,
21152 event: DeviceEventKind::Registered(_)
21153 }))
21154 ));
21155 phone
21156 .write_all(
21157 &ClientMessage::HeadsetStatus { enabled: true }
21158 .encode(protocol)
21159 .unwrap(),
21160 )
21161 .await
21162 .unwrap();
21163 assert!(matches!(
21164 events.recv().await,
21165 Some(Event::Device(DeviceEvent {
21166 session_generation: _,
21167 device_id: _,
21168 event: DeviceEventKind::HeadsetStatusChanged { enabled: true, .. }
21169 }))
21170 ));
21171 phone
21172 .write_all(
21173 &ClientMessage::MediaPathEvent {
21174 path: crate::MediaPathId::Speaker,
21175 event: crate::MediaPathEvent::On,
21176 }
21177 .encode(protocol)
21178 .unwrap(),
21179 )
21180 .await
21181 .unwrap();
21182 assert!(matches!(
21183 events.recv().await,
21184 Some(Event::Device(DeviceEvent {
21185 session_generation: _,
21186 device_id: _,
21187 event: DeviceEventKind::MediaPathChanged {
21188 path: crate::MediaPathId::Speaker,
21189 event: crate::MediaPathEvent::On,
21190 ..
21191 }
21192 }))
21193 ));
21194 phone
21195 .write_all(
21196 &ClientMessage::MediaPathEvent {
21197 path: crate::MediaPathId::Speaker,
21198 event: crate::MediaPathEvent::On,
21199 }
21200 .encode(protocol)
21201 .unwrap(),
21202 )
21203 .await
21204 .unwrap();
21205 assert!(
21206 tokio::time::timeout(Duration::from_millis(50), events.recv())
21207 .await
21208 .is_err()
21209 );
21210
21211 handle.shutdown().await.unwrap();
21212 task.await.unwrap().unwrap();
21213 }
21214
21215 #[tokio::test]
21216 async fn unpaired_active_media_path_release_completes_on_hook_after_grace() {
21217 let config = ServerConfig {
21218 bind: "127.0.0.1:0".parse().unwrap(),
21219 advertised_address: Ipv4Addr::LOCALHOST,
21220 ..ServerConfig::default()
21221 };
21222 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21223 let address = server.local_addr().unwrap();
21224 let task = tokio::spawn(server.run());
21225 let mut phone = TcpStream::connect(address).await.unwrap();
21226 let mut decoder = FrameDecoder::new();
21227 let protocol = ProtocolVersion::V22;
21228 let device_id = DeviceId::new("SEP001122334455").unwrap();
21229 let call_id = CallId(7101);
21230
21231 phone.write_all(®ister_bytes(protocol)).await.unwrap();
21232 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21233 assert!(matches!(
21234 events.recv().await,
21235 Some(Event::Device(DeviceEvent {
21236 event: DeviceEventKind::Registered(_),
21237 ..
21238 }))
21239 ));
21240 handle
21241 .send(Command::new(
21242 device_id.clone(),
21243 CommandAction::BeginCall {
21244 line_instance: LineInstance::new(1),
21245 call_id,
21246 codec: Codec::Pcmu,
21247 },
21248 ))
21249 .await
21250 .unwrap();
21251 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21252 handle
21253 .send(Command::new(
21254 device_id,
21255 CommandAction::SetCallState {
21256 call_id,
21257 state: CallState::Connected,
21258 },
21259 ))
21260 .await
21261 .unwrap();
21262 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21263
21264 for event in [crate::MediaPathEvent::On, crate::MediaPathEvent::Off] {
21265 phone
21266 .write_all(
21267 &ClientMessage::MediaPathEvent {
21268 path: crate::MediaPathId::Speaker,
21269 event,
21270 }
21271 .encode(protocol)
21272 .unwrap(),
21273 )
21274 .await
21275 .unwrap();
21276 assert!(matches!(
21277 events.recv().await,
21278 Some(Event::Device(DeviceEvent {
21279 event: DeviceEventKind::MediaPathChanged {
21280 path: crate::MediaPathId::Speaker,
21281 event: actual,
21282 },
21283 ..
21284 })) if actual == event
21285 ));
21286 }
21287 assert!(
21288 tokio::time::timeout(Duration::from_millis(100), events.recv())
21289 .await
21290 .is_err(),
21291 "media-path release bypassed its route-change grace period"
21292 );
21293 assert!(matches!(
21294 tokio::time::timeout(Duration::from_millis(300), events.recv()).await,
21295 Ok(Some(Event::Device(DeviceEvent {
21296 event: DeviceEventKind::OnHook {
21297 call_id: ended,
21298 line_instance: LineInstance(1),
21299 },
21300 ..
21301 }))) if ended == call_id
21302 ));
21303 let frames = read_until_message(&mut phone, &mut decoder, id::SET_RINGER).await;
21304 assert!(frames.into_iter().any(|frame| matches!(
21305 ServerMessage::decode(frame, protocol),
21306 Ok(ServerMessage::CallState {
21307 state: CallState::OnHook,
21308 ..
21309 })
21310 )));
21311
21312 handle.shutdown().await.unwrap();
21313 task.await.unwrap().unwrap();
21314 }
21315
21316 #[tokio::test]
21317 async fn replacement_media_path_cancels_pending_on_hook_completion() {
21318 let config = ServerConfig {
21319 bind: "127.0.0.1:0".parse().unwrap(),
21320 advertised_address: Ipv4Addr::LOCALHOST,
21321 ..ServerConfig::default()
21322 };
21323 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21324 let address = server.local_addr().unwrap();
21325 let task = tokio::spawn(server.run());
21326 let mut phone = TcpStream::connect(address).await.unwrap();
21327 let mut decoder = FrameDecoder::new();
21328 let protocol = ProtocolVersion::V22;
21329 let device_id = DeviceId::new("SEP001122334455").unwrap();
21330 let call_id = CallId(7102);
21331
21332 phone.write_all(®ister_bytes(protocol)).await.unwrap();
21333 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21334 assert!(matches!(
21335 events.recv().await,
21336 Some(Event::Device(DeviceEvent {
21337 event: DeviceEventKind::Registered(_),
21338 ..
21339 }))
21340 ));
21341 handle
21342 .send(Command::new(
21343 device_id.clone(),
21344 CommandAction::BeginCall {
21345 line_instance: LineInstance::new(1),
21346 call_id,
21347 codec: Codec::Pcmu,
21348 },
21349 ))
21350 .await
21351 .unwrap();
21352 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21353 handle
21354 .send(Command::new(
21355 device_id,
21356 CommandAction::SetCallState {
21357 call_id,
21358 state: CallState::Connected,
21359 },
21360 ))
21361 .await
21362 .unwrap();
21363 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
21364
21365 for (path, event) in [
21366 (crate::MediaPathId::Speaker, crate::MediaPathEvent::On),
21367 (crate::MediaPathId::Speaker, crate::MediaPathEvent::Off),
21368 (crate::MediaPathId::Headset, crate::MediaPathEvent::On),
21369 ] {
21370 phone
21371 .write_all(
21372 &ClientMessage::MediaPathEvent { path, event }
21373 .encode(protocol)
21374 .unwrap(),
21375 )
21376 .await
21377 .unwrap();
21378 assert!(matches!(
21379 events.recv().await,
21380 Some(Event::Device(DeviceEvent {
21381 event: DeviceEventKind::MediaPathChanged {
21382 path: actual_path,
21383 event: actual_event,
21384 },
21385 ..
21386 })) if actual_path == path && actual_event == event
21387 ));
21388 }
21389 assert!(
21390 tokio::time::timeout(Duration::from_millis(350), events.recv())
21391 .await
21392 .is_err(),
21393 "a replacement audio path was mistaken for terminal OnHook"
21394 );
21395 assert!(
21396 tokio::time::timeout(Duration::from_millis(50), phone.read_u8())
21397 .await
21398 .is_err(),
21399 "route switching emitted terminal handset UI"
21400 );
21401
21402 handle.shutdown().await.unwrap();
21403 task.await.unwrap().unwrap();
21404 }
21405
21406 #[tokio::test]
21407 async fn ipv6_signaling_requires_extended_layouts_and_preserves_station_addresses() {
21408 let config = ServerConfig {
21409 bind: "[::1]:0".parse().unwrap(),
21410 ..ServerConfig::default()
21411 };
21412 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21413 let address = server.local_addr().unwrap();
21414 assert!(address.is_ipv6());
21415 let task = tokio::spawn(server.run());
21416
21417 let mut legacy = TcpStream::connect(address).await.unwrap();
21418 legacy
21419 .write_all(®ister_bytes(ProtocolVersion::V3))
21420 .await
21421 .unwrap();
21422 let mut legacy_decoder = FrameDecoder::new();
21423 let rejection = read_until_message(&mut legacy, &mut legacy_decoder, id::REGISTER_REJECT)
21424 .await
21425 .into_iter()
21426 .find(|frame| frame.message_id == id::REGISTER_REJECT)
21427 .unwrap();
21428 assert!(matches!(
21429 ServerMessage::decode(rejection, ProtocolVersion::V3).unwrap(),
21430 ServerMessage::RegisterReject { reason } if reason == "IPv6 requires protocol v17"
21431 ));
21432 assert!(
21433 tokio::time::timeout(Duration::from_millis(25), events.recv())
21434 .await
21435 .is_err()
21436 );
21437
21438 let protocol = ProtocolVersion::V22;
21439 let reported_ipv6: Ipv6Addr = "2001:db8::42".parse().unwrap();
21440 let registration = ClientMessage::Register(RegistrationMessage {
21441 device_id: DeviceId::new("SEP001122334455").unwrap(),
21442 reported_address: None,
21443 reported_ipv6_address: Some(reported_ipv6),
21444 device_type: DeviceType::Cisco7962,
21445 advertised_protocol: protocol.wire(),
21446 features: PhoneFeatures::empty(),
21447 firmware: "test-load".into(),
21448 configuration_version_stamp: crate::message::BoundedBytes::default(),
21449 wire: None,
21450 });
21451 let mut phone = TcpStream::connect(address).await.unwrap();
21452 phone
21453 .write_all(®istration.encode(protocol).unwrap())
21454 .await
21455 .unwrap();
21456 let mut decoder = FrameDecoder::new();
21457 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21458 assert!(matches!(
21459 events.recv().await,
21460 Some(Event::Device(DeviceEvent { session_generation: _, device_id: _, event: DeviceEventKind::Registered(DeviceRegistration {
21461 peer,
21462 transport: StationTransport::Clear,
21463 reported_address: None,
21464 reported_ipv6_address: Some(reported),
21465 ..
21466 }),
21467 ..
21468 })) if peer.is_ipv6() && reported == reported_ipv6
21469 ));
21470
21471 phone
21472 .write_all(&ClientMessage::ServerRequest.encode(protocol).unwrap())
21473 .await
21474 .unwrap();
21475 let response = read_until_message(&mut phone, &mut decoder, id::SERVER_RES)
21476 .await
21477 .into_iter()
21478 .find(|frame| frame.message_id == id::SERVER_RES)
21479 .unwrap();
21480 assert_eq!(
21481 ServerMessage::decode(response, protocol).unwrap(),
21482 ServerMessage::ServerResponse {
21483 servers: vec![SignalingServerEndpoint {
21484 name: "sccp-protocol".into(),
21485 address: address.ip(),
21486 port: NonZeroU16::new(address.port()).unwrap(),
21487 }],
21488 }
21489 );
21490
21491 handle.shutdown().await.unwrap();
21492 task.await.unwrap().unwrap();
21493 }
21494
21495 #[test]
21496 fn status_messages_preserve_persistence_timeout_priority_and_phone_family() {
21497 let mut persistent = false;
21498 assert!(matches!(
21499 status_message_frames(
21500 HandsetStatusMessage::Display {
21501 text: "Persistent".into(),
21502 timeout_seconds: 0,
21503 priority: None,
21504 },
21505 DeviceType::Cisco7960,
21506 &mut persistent,
21507 )
21508 .as_slice(),
21509 [ServerMessage::DisplayPrompt {
21510 timeout_seconds: 0,
21511 line_instance: 0,
21512 call_reference: 0,
21513 text,
21514 }] if text == "Persistent"
21515 ));
21516 assert!(persistent);
21517 assert_eq!(
21518 status_message_frames(
21519 HandsetStatusMessage::Clear { priority: None },
21520 DeviceType::Cisco7960,
21521 &mut persistent,
21522 ),
21523 [
21524 ServerMessage::ClearPrompt {
21525 line_instance: 0,
21526 call_reference: 0,
21527 },
21528 ServerMessage::ClearPriorityNotify {
21529 priority: NotificationPriority::Timed,
21530 },
21531 ]
21532 );
21533 assert!(!persistent);
21534
21535 assert!(matches!(
21536 status_message_frames(
21537 HandsetStatusMessage::Display {
21538 text: "Timed".into(),
21539 timeout_seconds: 9,
21540 priority: None,
21541 },
21542 DeviceType::Cisco7960,
21543 &mut persistent,
21544 )
21545 .as_slice(),
21546 [ServerMessage::DisplayPriorityNotify {
21547 timeout_seconds: 9,
21548 priority: NotificationPriority::Timed,
21549 text,
21550 }] if text == "Timed"
21551 ));
21552 assert!(matches!(
21553 status_message_frames(
21554 HandsetStatusMessage::Display {
21555 text: "Timed".into(),
21556 timeout_seconds: 9,
21557 priority: None,
21558 },
21559 DeviceType::Cisco6945,
21560 &mut persistent,
21561 )
21562 .as_slice(),
21563 [ServerMessage::DisplayPrompt {
21564 timeout_seconds: 9,
21565 line_instance: 0,
21566 call_reference: 0,
21567 ..
21568 }]
21569 ));
21570 }
21571
21572 #[test]
21573 fn every_status_priority_round_trips_through_typed_frames() {
21574 for priority in NotificationPriority::ALL_KNOWN {
21575 let mut persistent = false;
21576 assert_eq!(
21577 status_message_frames(
21578 HandsetStatusMessage::Display {
21579 text: "Priority".into(),
21580 timeout_seconds: 5,
21581 priority: Some(*priority),
21582 },
21583 DeviceType::Cisco7960,
21584 &mut persistent,
21585 ),
21586 [ServerMessage::DisplayPriorityNotify {
21587 timeout_seconds: 5,
21588 priority: *priority,
21589 text: "Priority".into(),
21590 }]
21591 );
21592 assert_eq!(
21593 status_message_frames(
21594 HandsetStatusMessage::Clear {
21595 priority: Some(*priority),
21596 },
21597 DeviceType::Cisco7960,
21598 &mut persistent,
21599 ),
21600 [ServerMessage::ClearPriorityNotify {
21601 priority: *priority,
21602 }]
21603 );
21604 }
21605 }
21606
21607 #[test]
21608 fn text_service_delivery_types_priority_and_segments_only_modern_documents() {
21609 let short = CiscoIpPhoneText::new("Sender", "Read", "Hello & goodbye").unwrap();
21610 let legacy = text_service_messages(
21611 LineInstance::new(3),
21612 CallReference::new(71),
21613 TransactionId::new(99),
21614 PhoneServicePriority::NORMAL,
21615 &short,
21616 ProtocolVersion::V17,
21617 )
21618 .unwrap();
21619 assert!(matches!(
21620 legacy.as_slice(),
21621 [ServerMessage::UserToDeviceDataV1(message)]
21622 if message.application_id == PHONE_TEXT_APPLICATION_ID
21623 && message.line_instance == 3
21624 && message.call_reference == 71
21625 && message.transaction_id == 99
21626 && message.sequence_flag == 2
21627 && message.display_priority == 1
21628 && message.conference_id == 71
21629 && message.application_instance_id == PHONE_TEXT_APPLICATION_ID
21630 && message.routing == 1
21631 && CiscoIpPhoneText::from_xml(&message.data).unwrap() == short
21632 ));
21633
21634 let legacy_oversized = CiscoIpPhoneText::new(
21635 "Sender",
21636 "Read",
21637 "x".repeat(PHONE_TEXT_LEGACY_MAX_CHARS + 1),
21638 )
21639 .unwrap();
21640 assert!(matches!(
21641 text_service_messages(
21642 LineInstance::new(0),
21643 CallReference::new(1),
21644 TransactionId::new(1),
21645 PhoneServicePriority::LOW,
21646 &legacy_oversized,
21647 ProtocolVersion::V17,
21648 ),
21649 Err(ServerError::PhoneXml(PhoneXmlError::InvalidField {
21650 field: "legacy phone text body",
21651 ..
21652 }))
21653 ));
21654
21655 let modern = CiscoIpPhoneText::new("Sender", "Read", "&".repeat(3_000)).unwrap();
21656 let messages = text_service_messages(
21657 LineInstance::new(0),
21658 CallReference::new(1),
21659 TransactionId::new(100),
21660 PhoneServicePriority::HIGH,
21661 &modern,
21662 ProtocolVersion::V18,
21663 )
21664 .unwrap();
21665 assert!(messages.len() > 2);
21666 let mut reassembled = Vec::new();
21667 for (index, message) in messages.iter().enumerate() {
21668 let ServerMessage::UserToDeviceDataV1(message) = message else {
21669 panic!("expected text application-data segment");
21670 };
21671 assert!(message.data.len() <= 2_000);
21672 assert_eq!(message.display_priority, 2);
21673 assert_eq!(
21674 message.sequence_flag,
21675 if index == 0 {
21676 0
21677 } else if index + 1 == messages.len() {
21678 2
21679 } else {
21680 1
21681 }
21682 );
21683 reassembled.extend_from_slice(&message.data);
21684 }
21685 assert_eq!(CiscoIpPhoneText::from_xml(&reassembled).unwrap(), modern);
21686 }
21687
21688 #[tokio::test]
21689 async fn registered_phone_receives_typed_text_service_controls_and_priority() {
21690 let config = ServerConfig {
21691 bind: "127.0.0.1:0".parse().unwrap(),
21692 advertised_address: Ipv4Addr::LOCALHOST,
21693 ..ServerConfig::default()
21694 };
21695 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21696 let address = server.local_addr().unwrap();
21697 let task = tokio::spawn(server.run());
21698 let mut phone = TcpStream::connect(address).await.unwrap();
21699 let mut decoder = FrameDecoder::new();
21700 let protocol = ProtocolVersion::V22;
21701 let device_id = DeviceId::new("SEP001122334455").unwrap();
21702
21703 phone.write_all(®ister_bytes(protocol)).await.unwrap();
21704 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21705 assert!(matches!(
21706 events.recv().await,
21707 Some(Event::Device(DeviceEvent {
21708 session_generation: _,
21709 device_id: _,
21710 event: DeviceEventKind::Registered(_)
21711 }))
21712 ));
21713
21714 let mut expected =
21715 CiscoIpPhoneText::new("Dispatch", "Read", "Café <ready> & waiting").unwrap();
21716 expected.soft_keys.push(CiscoIpPhoneSoftKeyItem {
21717 name: Some("Refresh".into()),
21718 position: PhoneSoftKeyPosition::new(1).unwrap(),
21719 url: Some("https://pbx.example/text?id=7&view=full".into()),
21720 url_down: None,
21721 });
21722 handle
21723 .send(Command::new(
21724 device_id.clone(),
21725 CommandAction::ShowTextService {
21726 line_instance: LineInstance::new(2),
21727 call_reference: CallReference::new(42),
21728 transaction_id: TransactionId::new(73),
21729 priority: PhoneServicePriority::HIGH,
21730 document: expected.clone(),
21731 },
21732 ))
21733 .await
21734 .unwrap();
21735 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
21736 let message = frames
21737 .into_iter()
21738 .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
21739 .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
21740 .unwrap();
21741 let ServerMessage::UserToDeviceDataV1(message) = message else {
21742 panic!("expected text application data");
21743 };
21744 assert_eq!(message.application_id, PHONE_TEXT_APPLICATION_ID);
21745 assert_eq!(message.line_instance, 2);
21746 assert_eq!(message.call_reference, 42);
21747 assert_eq!(message.transaction_id, 73);
21748 assert_eq!(message.sequence_flag, 2);
21749 assert_eq!(message.display_priority, 2);
21750 assert_eq!(CiscoIpPhoneText::from_xml(&message.data).unwrap(), expected);
21751
21752 handle.shutdown().await.unwrap();
21753 task.await.unwrap().unwrap();
21754 }
21755
21756 #[test]
21757 fn input_service_delivery_preserves_typed_fields_and_modern_segmentation() {
21758 let short = CiscoIpPhoneInput::new(
21759 "Invite",
21760 "Enter number",
21761 "conference/44/invite",
21762 vec![CiscoIpPhoneInputItem {
21763 display_name: Some("Number".into()),
21764 parameter: PhoneInputParameterName::new("NUMBER").unwrap(),
21765 flags: PhoneInputFlags::Telephone,
21766 default_value: Some("5550100".into()),
21767 }],
21768 )
21769 .unwrap();
21770 let legacy = input_service_messages(
21771 LineInstance::new(3),
21772 CallReference::new(71),
21773 ApplicationId::new(9_092),
21774 TransactionId::new(99),
21775 PhoneServicePriority::NORMAL,
21776 &short,
21777 ProtocolVersion::V17,
21778 )
21779 .unwrap();
21780 assert!(matches!(
21781 legacy.as_slice(),
21782 [ServerMessage::UserToDeviceDataV1(message)]
21783 if message.application_id == 9_092
21784 && message.line_instance == 3
21785 && message.call_reference == 71
21786 && message.transaction_id == 99
21787 && message.sequence_flag == 2
21788 && message.display_priority == 1
21789 && message.conference_id == 71
21790 && message.application_instance_id == 9_092
21791 && message.routing == 1
21792 && CiscoIpPhoneInput::from_xml(&message.data).unwrap() == short
21793 ));
21794
21795 let mut large = short;
21796 large.key_items = (0..32)
21797 .map(|index| CiscoIpPhoneKeyItem {
21798 key: PhoneXmlKey::NavBack,
21799 url: Some(format!("{}-{index:02}", "x".repeat(252))),
21800 url_down: Some(format!("{}-{index:02}", "y".repeat(252))),
21801 })
21802 .collect();
21803 assert!(matches!(
21804 input_service_messages(
21805 LineInstance::new(3),
21806 CallReference::new(71),
21807 ApplicationId::new(9_092),
21808 TransactionId::new(100),
21809 PhoneServicePriority::HIGH,
21810 &large,
21811 ProtocolVersion::V17,
21812 ),
21813 Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
21814 maximum: 2_000,
21815 ..
21816 }))
21817 ));
21818 let messages = input_service_messages(
21819 LineInstance::new(3),
21820 CallReference::new(71),
21821 ApplicationId::new(9_092),
21822 TransactionId::new(100),
21823 PhoneServicePriority::HIGH,
21824 &large,
21825 ProtocolVersion::V18,
21826 )
21827 .unwrap();
21828 assert!(messages.len() > 2);
21829 let mut reassembled = Vec::new();
21830 for (index, message) in messages.iter().enumerate() {
21831 let ServerMessage::UserToDeviceDataV1(message) = message else {
21832 panic!("expected input application-data segment");
21833 };
21834 assert!(message.data.len() <= 2_000);
21835 assert_eq!(message.application_id, 9_092);
21836 assert_eq!(message.display_priority, 2);
21837 assert_eq!(
21838 message.sequence_flag,
21839 if index == 0 {
21840 0
21841 } else if index + 1 == messages.len() {
21842 2
21843 } else {
21844 1
21845 }
21846 );
21847 reassembled.extend_from_slice(&message.data);
21848 }
21849 assert_eq!(CiscoIpPhoneInput::from_xml(&reassembled).unwrap(), large);
21850 }
21851
21852 #[tokio::test]
21853 async fn registered_phone_receives_typed_input_and_returns_ordered_submission() {
21854 let config = ServerConfig {
21855 bind: "127.0.0.1:0".parse().unwrap(),
21856 advertised_address: Ipv4Addr::LOCALHOST,
21857 ..ServerConfig::default()
21858 };
21859 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
21860 let address = server.local_addr().unwrap();
21861 let task = tokio::spawn(server.run());
21862 let mut phone = TcpStream::connect(address).await.unwrap();
21863 let mut decoder = FrameDecoder::new();
21864 let protocol = ProtocolVersion::V22;
21865 let device_id = DeviceId::new("SEP001122334455").unwrap();
21866
21867 phone.write_all(®ister_bytes(protocol)).await.unwrap();
21868 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
21869 assert!(matches!(
21870 events.recv().await,
21871 Some(Event::Device(DeviceEvent {
21872 session_generation: _,
21873 device_id: _,
21874 event: DeviceEventKind::Registered(_)
21875 }))
21876 ));
21877
21878 let mut expected = CiscoIpPhoneInput::new(
21879 "Invite <guest>",
21880 "Enter details",
21881 "conference/44/invite",
21882 vec![
21883 CiscoIpPhoneInputItem {
21884 display_name: Some("Number".into()),
21885 parameter: PhoneInputParameterName::new("NUMBER").unwrap(),
21886 flags: PhoneInputFlags::Telephone,
21887 default_value: None,
21888 },
21889 CiscoIpPhoneInputItem {
21890 display_name: Some("Name".into()),
21891 parameter: PhoneInputParameterName::new("NAME").unwrap(),
21892 flags: PhoneInputFlags::Alphabetic,
21893 default_value: Some("François".into()),
21894 },
21895 ],
21896 )
21897 .unwrap();
21898 expected.soft_keys.push(CiscoIpPhoneSoftKeyItem {
21899 name: Some("Submit".into()),
21900 position: PhoneSoftKeyPosition::new(1).unwrap(),
21901 url: Some("SoftKey:Submit".into()),
21902 url_down: None,
21903 });
21904 handle
21905 .send(Command::new(
21906 device_id.clone(),
21907 CommandAction::ShowInputService {
21908 line_instance: LineInstance::new(2),
21909 call_reference: CallReference::new(42),
21910 application_id: ApplicationId::new(9_092),
21911 transaction_id: TransactionId::new(73),
21912 priority: PhoneServicePriority::HIGH,
21913 document: expected.clone(),
21914 },
21915 ))
21916 .await
21917 .unwrap();
21918 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
21919 let message = frames
21920 .into_iter()
21921 .find(|frame| frame.message_id == id::USER_TO_DEVICE_DATA_V1)
21922 .map(|frame| ServerMessage::decode(frame, protocol).unwrap())
21923 .unwrap();
21924 let ServerMessage::UserToDeviceDataV1(message) = message else {
21925 panic!("expected input application data");
21926 };
21927 assert_eq!(message.application_id, 9_092);
21928 assert_eq!(message.line_instance, 2);
21929 assert_eq!(message.call_reference, 42);
21930 assert_eq!(message.transaction_id, 73);
21931 assert_eq!(message.sequence_flag, 2);
21932 assert_eq!(message.display_priority, 2);
21933 assert_eq!(
21934 CiscoIpPhoneInput::from_xml(&message.data).unwrap(),
21935 expected
21936 );
21937
21938 phone
21939 .write_all(
21940 &ClientMessage::DeviceToUserDataV1(UserDataV1Message {
21941 application_id: 9_092,
21942 line_instance: 2,
21943 call_reference: 42,
21944 transaction_id: 73,
21945 sequence_flag: 2,
21946 display_priority: 2,
21947 conference_id: 42,
21948 application_instance_id: 9_092,
21949 routing: 1,
21950 data: b"conference/44/invite?NUMBER=555%2A12&NAME=Fran%C3%A7ois".to_vec(),
21951 })
21952 .encode(protocol)
21953 .unwrap(),
21954 )
21955 .await
21956 .unwrap();
21957 let Some(Event::Device(DeviceEvent {
21958 session_generation: _,
21959 device_id: _,
21960 event: DeviceEventKind::PhoneServiceResponse { response, .. },
21961 })) = events.recv().await
21962 else {
21963 panic!("expected typed input submission");
21964 };
21965 assert_eq!(response.routing.application_id, ApplicationId::new(9_092));
21966 assert_eq!(response.routing.line_instance, LineInstance::new(2));
21967 assert_eq!(response.routing.call_reference, CallReference::new(42));
21968 assert_eq!(response.routing.transaction_id, TransactionId::new(73));
21969 let PhoneServicePayload::Submission(submission) = response.payload else {
21970 panic!("expected typed input submission payload");
21971 };
21972 assert_eq!(submission.route, ["conference", "44", "invite"]);
21973 assert_eq!(
21974 submission.values_named("NUMBER").collect::<Vec<_>>(),
21975 ["555*12"]
21976 );
21977 assert_eq!(
21978 submission.values_named("NAME").collect::<Vec<_>>(),
21979 ["François"]
21980 );
21981
21982 handle.shutdown().await.unwrap();
21983 task.await.unwrap().unwrap();
21984 }
21985
21986 #[test]
21987 fn execute_action_delivery_preserves_envelope_order_and_protocol_bounds() {
21988 let short = CiscoIpPhoneExecute::new(vec![
21989 CiscoIpPhoneExecuteItem::with_priority(
21990 "Key:Directories?view=all&side=west",
21991 PhoneExecutePriority::LOW,
21992 )
21993 .unwrap(),
21994 CiscoIpPhoneExecuteItem::new("Application:PlacedCalls").unwrap(),
21995 ])
21996 .unwrap();
21997 let legacy = execute_phone_action_messages(
21998 LineInstance::new(3),
21999 CallReference::new(71),
22000 ApplicationId::new(9_093),
22001 TransactionId::new(99),
22002 PhoneServicePriority::NORMAL,
22003 &short,
22004 ProtocolVersion::V17,
22005 )
22006 .unwrap();
22007 assert!(matches!(
22008 legacy.as_slice(),
22009 [ServerMessage::UserToDeviceDataV1(message)]
22010 if message.application_id == 9_093
22011 && message.line_instance == 3
22012 && message.call_reference == 71
22013 && message.transaction_id == 99
22014 && message.sequence_flag == 2
22015 && message.display_priority == 1
22016 && message.routing == 1
22017 && CiscoIpPhoneExecute::from_xml(&message.data).unwrap() == short
22018 ));
22019
22020 let large = CiscoIpPhoneExecute::new(
22021 (0..PHONE_EXECUTE_MAX_ITEMS)
22022 .map(|_| {
22023 CiscoIpPhoneExecuteItem::with_priority(
22024 "\"".repeat(256),
22025 PhoneExecutePriority::HIGH,
22026 )
22027 .unwrap()
22028 })
22029 .collect(),
22030 )
22031 .unwrap();
22032 assert!(matches!(
22033 execute_phone_action_messages(
22034 LineInstance::new(3),
22035 CallReference::new(71),
22036 ApplicationId::new(9_093),
22037 TransactionId::new(100),
22038 PhoneServicePriority::HIGH,
22039 &large,
22040 ProtocolVersion::V17,
22041 ),
22042 Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
22043 maximum: 2_000,
22044 ..
22045 }))
22046 ));
22047 let messages = execute_phone_action_messages(
22048 LineInstance::new(3),
22049 CallReference::new(71),
22050 ApplicationId::new(9_093),
22051 TransactionId::new(100),
22052 PhoneServicePriority::HIGH,
22053 &large,
22054 ProtocolVersion::V18,
22055 )
22056 .unwrap();
22057 assert!(messages.len() > 2);
22058 let mut reassembled = Vec::new();
22059 for (index, message) in messages.iter().enumerate() {
22060 let ServerMessage::UserToDeviceDataV1(message) = message else {
22061 panic!("expected execute application-data segment");
22062 };
22063 assert!(message.data.len() <= 2_000);
22064 assert_eq!(message.display_priority, 2);
22065 assert_eq!(
22066 message.sequence_flag,
22067 if index == 0 {
22068 0
22069 } else if index + 1 == messages.len() {
22070 2
22071 } else {
22072 1
22073 }
22074 );
22075 reassembled.extend_from_slice(&message.data);
22076 }
22077 assert_eq!(CiscoIpPhoneExecute::from_xml(&reassembled).unwrap(), large);
22078 }
22079
22080 #[test]
22081 fn image_service_delivery_preserves_family_envelope_and_protocol_bounds() {
22082 let short = PhoneImageDocument::ImageFile(CiscoIpPhoneImageFile {
22083 keypad_target: None,
22084 application_id: Some("maps".into()),
22085 on_focus_lost: None,
22086 on_focus_gained: None,
22087 on_minimized: None,
22088 on_closed: Some("Notify:maps/closed".into()),
22089 title: Some("Floor map".into()),
22090 prompt: Some("Inspect".into()),
22091 soft_keys: Vec::new(),
22092 key_items: Vec::new(),
22093 location_x: Some(-1),
22094 location_y: Some(167),
22095 url: PhoneImageUrl::new("https://pbx.example/map.png?floor=2&site=east").unwrap(),
22096 });
22097 let legacy = image_service_messages(
22098 LineInstance::new(3),
22099 CallReference::new(71),
22100 ApplicationId::new(9_095),
22101 TransactionId::new(101),
22102 PhoneServicePriority::NORMAL,
22103 &short,
22104 ProtocolVersion::V17,
22105 )
22106 .unwrap();
22107 assert!(matches!(
22108 legacy.as_slice(),
22109 [ServerMessage::UserToDeviceDataV1(message)]
22110 if message.application_id == 9_095
22111 && message.line_instance == 3
22112 && message.call_reference == 71
22113 && message.transaction_id == 101
22114 && message.sequence_flag == 2
22115 && message.display_priority == 1
22116 && message.routing == 1
22117 && PhoneImageDocument::from_xml(&message.data).unwrap() == short
22118 ));
22119
22120 let large = PhoneImageDocument::GraphicFileMenu(CiscoIpPhoneGraphicFileMenu {
22121 keypad_target: None,
22122 application_id: Some("map-regions".into()),
22123 on_focus_lost: None,
22124 on_focus_gained: None,
22125 on_minimized: None,
22126 on_closed: None,
22127 title: Some("Map regions".into()),
22128 prompt: Some("Choose".into()),
22129 soft_keys: Vec::new(),
22130 key_items: Vec::new(),
22131 location_x: Some(0),
22132 location_y: Some(0),
22133 url: PhoneImageUrl::new("https://pbx.example/map.png").unwrap(),
22134 items: (0..crate::phone::xml::PHONE_GRAPHIC_FILE_MENU_MAX_ITEMS)
22135 .map(|index| CiscoIpPhoneTouchAreaMenuItem {
22136 name: Some(format!("Region {index}")),
22137 url: Some("x".repeat(256)),
22138 touch_area: Some(PhoneTouchArea {
22139 x1: index as u16,
22140 y1: index as u16,
22141 x2: index as u16 + 1,
22142 y2: index as u16 + 1,
22143 }),
22144 })
22145 .collect(),
22146 });
22147 assert!(matches!(
22148 image_service_messages(
22149 LineInstance::new(3),
22150 CallReference::new(71),
22151 ApplicationId::new(9_095),
22152 TransactionId::new(102),
22153 PhoneServicePriority::HIGH,
22154 &large,
22155 ProtocolVersion::V17,
22156 ),
22157 Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
22158 maximum: 2_000,
22159 ..
22160 }))
22161 ));
22162 let messages = image_service_messages(
22163 LineInstance::new(3),
22164 CallReference::new(71),
22165 ApplicationId::new(9_095),
22166 TransactionId::new(102),
22167 PhoneServicePriority::HIGH,
22168 &large,
22169 ProtocolVersion::V18,
22170 )
22171 .unwrap();
22172 assert!(messages.len() > 2);
22173 let mut reassembled = Vec::new();
22174 for (index, message) in messages.iter().enumerate() {
22175 let ServerMessage::UserToDeviceDataV1(message) = message else {
22176 panic!("expected image application-data segment");
22177 };
22178 assert!(message.data.len() <= 2_000);
22179 assert_eq!(message.display_priority, 2);
22180 assert_eq!(
22181 message.sequence_flag,
22182 if index == 0 {
22183 0
22184 } else if index + 1 == messages.len() {
22185 2
22186 } else {
22187 1
22188 }
22189 );
22190 reassembled.extend_from_slice(&message.data);
22191 }
22192 assert_eq!(PhoneImageDocument::from_xml(&reassembled).unwrap(), large);
22193 }
22194
22195 #[test]
22196 fn background_control_delivery_uses_reserved_application_envelope_and_typed_xml() {
22197 let set = CiscoIpPhoneSetBackground::new(
22198 PhoneBackgroundHttpUrl::new("http://pbx.example/background.png?site=east").unwrap(),
22199 PhoneBackgroundHttpUrl::new("http://pbx.example/background-thumb.png").unwrap(),
22200 );
22201 let message = background_control_message(
22202 TransactionId::new(107),
22203 &PhoneBackgroundControlDocument::Set(set.clone()),
22204 )
22205 .unwrap();
22206 assert!(matches!(
22207 message,
22208 ServerMessage::UserToDeviceDataV1(message)
22209 if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22210 && message.line_instance == 0
22211 && message.call_reference == 0
22212 && message.transaction_id == 107
22213 && message.sequence_flag == 2
22214 && message.display_priority == 0
22215 && message.conference_id == 0
22216 && message.application_instance_id == PHONE_BACKGROUND_APPLICATION_ID
22217 && message.routing == 1
22218 && CiscoIpPhoneSetBackground::from_xml(&message.data).unwrap() == set
22219 ));
22220
22221 let preview = CiscoIpPhoneSetBackgroundPreview::new(
22222 PhoneBackgroundHttpUrl::new("http://pbx.example/background.png").unwrap(),
22223 );
22224 let message = background_control_message(
22225 TransactionId::new(108),
22226 &PhoneBackgroundControlDocument::Preview(preview.clone()),
22227 )
22228 .unwrap();
22229 assert!(matches!(
22230 message,
22231 ServerMessage::UserToDeviceDataV1(message)
22232 if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22233 && message.transaction_id == 108
22234 && CiscoIpPhoneSetBackgroundPreview::from_xml(&message.data).unwrap() == preview
22235 ));
22236 }
22237
22238 #[test]
22239 fn ringtone_control_delivery_uses_reserved_application_envelope_and_typed_xml() {
22240 let document = CiscoIpPhoneSetRingTone::new(
22241 PhoneRingtoneUrl::new("http://pbx.example/ringtones/Classic.raw?locale=sv").unwrap(),
22242 );
22243 let message = ringtone_control_message(TransactionId::new(111), &document).unwrap();
22244 assert!(matches!(
22245 message,
22246 ServerMessage::UserToDeviceDataV1(message)
22247 if message.application_id == PHONE_RINGTONE_APPLICATION_ID
22248 && message.line_instance == 0
22249 && message.call_reference == 0
22250 && message.transaction_id == 111
22251 && message.sequence_flag == 2
22252 && message.display_priority == 0
22253 && message.conference_id == 0
22254 && message.application_instance_id == PHONE_RINGTONE_APPLICATION_ID
22255 && message.routing == 1
22256 && CiscoIpPhoneSetRingTone::from_xml(&message.data).unwrap() == document
22257 ));
22258 }
22259
22260 #[tokio::test]
22261 async fn registered_phone_receives_typed_background_selection_and_preview_commands() {
22262 let config = ServerConfig {
22263 bind: "127.0.0.1:0".parse().unwrap(),
22264 advertised_address: Ipv4Addr::LOCALHOST,
22265 ..ServerConfig::default()
22266 };
22267 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22268 let address = server.local_addr().unwrap();
22269 let task = tokio::spawn(server.run());
22270 let mut phone = TcpStream::connect(address).await.unwrap();
22271 let mut decoder = FrameDecoder::new();
22272 let protocol = ProtocolVersion::V22;
22273 let device_id = DeviceId::new("SEP001122334455").unwrap();
22274
22275 phone.write_all(®ister_bytes(protocol)).await.unwrap();
22276 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22277 assert!(matches!(
22278 events.recv().await,
22279 Some(Event::Device(DeviceEvent {
22280 session_generation: _,
22281 device_id: _,
22282 event: DeviceEventKind::Registered(_)
22283 }))
22284 ));
22285
22286 let set = CiscoIpPhoneSetBackground::new(
22287 PhoneBackgroundHttpUrl::new("http://pbx.example/background.png").unwrap(),
22288 PhoneBackgroundHttpUrl::new("http://pbx.example/background-thumb.png").unwrap(),
22289 );
22290 handle
22291 .send(Command::new(
22292 device_id.clone(),
22293 CommandAction::SetBackgroundImage {
22294 transaction_id: TransactionId::new(109),
22295 document: set.clone(),
22296 },
22297 ))
22298 .await
22299 .unwrap();
22300 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22301 assert!(frames.into_iter().any(|frame| matches!(
22302 ServerMessage::decode(frame, protocol),
22303 Ok(ServerMessage::UserToDeviceDataV1(message))
22304 if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22305 && message.transaction_id == 109
22306 && CiscoIpPhoneSetBackground::from_xml(&message.data).unwrap() == set
22307 )));
22308
22309 let preview = CiscoIpPhoneSetBackgroundPreview::new(
22310 PhoneBackgroundHttpUrl::new("http://pbx.example/background.png?preview=1").unwrap(),
22311 );
22312 handle
22313 .send(Command::new(
22314 device_id,
22315 CommandAction::PreviewBackgroundImage {
22316 transaction_id: TransactionId::new(110),
22317 document: preview.clone(),
22318 },
22319 ))
22320 .await
22321 .unwrap();
22322 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22323 assert!(frames.into_iter().any(|frame| matches!(
22324 ServerMessage::decode(frame, protocol),
22325 Ok(ServerMessage::UserToDeviceDataV1(message))
22326 if message.application_id == PHONE_BACKGROUND_APPLICATION_ID
22327 && message.transaction_id == 110
22328 && CiscoIpPhoneSetBackgroundPreview::from_xml(&message.data).unwrap() == preview
22329 )));
22330
22331 handle.shutdown().await.unwrap();
22332 task.await.unwrap().unwrap();
22333 }
22334
22335 #[tokio::test]
22336 async fn registered_phone_receives_typed_ringtone_command() {
22337 let config = ServerConfig {
22338 bind: "127.0.0.1:0".parse().unwrap(),
22339 advertised_address: Ipv4Addr::LOCALHOST,
22340 ..ServerConfig::default()
22341 };
22342 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22343 let address = server.local_addr().unwrap();
22344 let task = tokio::spawn(server.run());
22345 let mut phone = TcpStream::connect(address).await.unwrap();
22346 let mut decoder = FrameDecoder::new();
22347 let protocol = ProtocolVersion::V22;
22348 let device_id = DeviceId::new("SEP001122334455").unwrap();
22349
22350 phone.write_all(®ister_bytes(protocol)).await.unwrap();
22351 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22352 assert!(matches!(
22353 events.recv().await,
22354 Some(Event::Device(DeviceEvent {
22355 session_generation: _,
22356 device_id: _,
22357 event: DeviceEventKind::Registered(_)
22358 }))
22359 ));
22360
22361 let document = CiscoIpPhoneSetRingTone::new(
22362 PhoneRingtoneUrl::new("http://pbx.example/ringtones/Classic.raw?locale=sv").unwrap(),
22363 );
22364 handle
22365 .send(Command::new(
22366 device_id,
22367 CommandAction::SetRingtone {
22368 transaction_id: TransactionId::new(112),
22369 document: document.clone(),
22370 },
22371 ))
22372 .await
22373 .unwrap();
22374 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22375 assert!(frames.into_iter().any(|frame| matches!(
22376 ServerMessage::decode(frame, protocol),
22377 Ok(ServerMessage::UserToDeviceDataV1(message))
22378 if message.application_id == PHONE_RINGTONE_APPLICATION_ID
22379 && message.line_instance == 0
22380 && message.call_reference == 0
22381 && message.transaction_id == 112
22382 && message.sequence_flag == 2
22383 && message.display_priority == 0
22384 && message.conference_id == 0
22385 && message.application_instance_id == PHONE_RINGTONE_APPLICATION_ID
22386 && message.routing == 1
22387 && CiscoIpPhoneSetRingTone::from_xml(&message.data).unwrap() == document
22388 )));
22389
22390 handle.shutdown().await.unwrap();
22391 task.await.unwrap().unwrap();
22392 }
22393
22394 #[test]
22395 fn status_service_delivery_preserves_items_icons_timers_and_envelope() {
22396 let bitmap = PhoneStatusDocument::Bitmap(CiscoIpPhoneStatus {
22397 text: Some("Calls waiting".into()),
22398 timer_seconds: Some(30),
22399 location_x: Some(-1),
22400 location_y: Some(20),
22401 width: 106,
22402 height: 21,
22403 depth: 2,
22404 data: Some(PhoneBitmapData::new(vec![0x5a; PHONE_STATUS_BITMAP_MAX_BYTES]).unwrap()),
22405 });
22406 let legacy = status_service_messages(
22407 LineInstance::new(3),
22408 CallReference::new(71),
22409 ApplicationId::new(9_096),
22410 TransactionId::new(103),
22411 PhoneServicePriority::HIGH,
22412 &bitmap,
22413 ProtocolVersion::V17,
22414 )
22415 .unwrap();
22416 assert!(matches!(
22417 legacy.as_slice(),
22418 [ServerMessage::UserToDeviceDataV1(message)]
22419 if message.application_id == 9_096
22420 && message.line_instance == 3
22421 && message.call_reference == 71
22422 && message.transaction_id == 103
22423 && message.sequence_flag == 2
22424 && message.display_priority == 2
22425 && message.routing == 1
22426 && PhoneStatusDocument::from_xml(&message.data).unwrap() == bitmap
22427 ));
22428
22429 let file = PhoneStatusDocument::File(CiscoIpPhoneStatusFile {
22430 text: Some("Map status".into()),
22431 timer_seconds: Some(0),
22432 location_x: Some(261),
22433 location_y: Some(49),
22434 url: PhoneImageUrl::new("https://pbx.example/status.png?site=east").unwrap(),
22435 });
22436 let modern = status_service_messages(
22437 LineInstance::new(3),
22438 CallReference::new(71),
22439 ApplicationId::new(9_096),
22440 TransactionId::new(104),
22441 PhoneServicePriority::LOW,
22442 &file,
22443 ProtocolVersion::V22,
22444 )
22445 .unwrap();
22446 assert!(matches!(
22447 modern.as_slice(),
22448 [ServerMessage::UserToDeviceDataV1(message)]
22449 if message.sequence_flag == 2
22450 && message.display_priority == 0
22451 && PhoneStatusDocument::from_xml(&message.data).unwrap() == file
22452 ));
22453
22454 let invalid = PhoneStatusDocument::Bitmap(CiscoIpPhoneStatus {
22455 text: None,
22456 timer_seconds: None,
22457 location_x: None,
22458 location_y: None,
22459 width: 1,
22460 height: 1,
22461 depth: 1,
22462 data: Some(PhoneBitmapData::new(vec![0; PHONE_STATUS_BITMAP_MAX_BYTES + 1]).unwrap()),
22463 });
22464 assert!(matches!(
22465 status_service_messages(
22466 LineInstance::new(3),
22467 CallReference::new(71),
22468 ApplicationId::new(9_096),
22469 TransactionId::new(105),
22470 PhoneServicePriority::NORMAL,
22471 &invalid,
22472 ProtocolVersion::V22,
22473 ),
22474 Err(ServerError::PhoneXml(PhoneXmlError::LimitExceeded {
22475 kind: "phone status bitmap bytes",
22476 maximum: PHONE_STATUS_BITMAP_MAX_BYTES,
22477 ..
22478 }))
22479 ));
22480 }
22481
22482 #[tokio::test]
22483 async fn registered_phone_receives_typed_execute_image_status_tone_and_announcement_commands() {
22484 let config = ServerConfig {
22485 bind: "127.0.0.1:0".parse().unwrap(),
22486 advertised_address: Ipv4Addr::LOCALHOST,
22487 ..ServerConfig::default()
22488 };
22489 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22490 let address = server.local_addr().unwrap();
22491 let task = tokio::spawn(server.run());
22492 let mut phone = TcpStream::connect(address).await.unwrap();
22493 let mut decoder = FrameDecoder::new();
22494 let protocol = ProtocolVersion::V22;
22495 let device_id = DeviceId::new("SEP001122334455").unwrap();
22496
22497 phone.write_all(®ister_bytes(protocol)).await.unwrap();
22498 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22499 assert!(matches!(
22500 events.recv().await,
22501 Some(Event::Device(DeviceEvent {
22502 session_generation: _,
22503 device_id: _,
22504 event: DeviceEventKind::Registered(_)
22505 }))
22506 ));
22507
22508 let execute = CiscoIpPhoneExecute::new(vec![
22509 CiscoIpPhoneExecuteItem::with_priority("App:Close:9093", PhoneExecutePriority::NORMAL)
22510 .unwrap(),
22511 ])
22512 .unwrap();
22513 handle
22514 .send(Command::new(
22515 device_id.clone(),
22516 CommandAction::ExecutePhoneActions {
22517 line_instance: LineInstance::new(2),
22518 call_reference: CallReference::new(42),
22519 application_id: ApplicationId::new(9_093),
22520 transaction_id: TransactionId::new(73),
22521 priority: PhoneServicePriority::HIGH,
22522 document: execute.clone(),
22523 },
22524 ))
22525 .await
22526 .unwrap();
22527 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22528 assert!(frames.into_iter().any(|frame| matches!(
22529 ServerMessage::decode(frame, protocol),
22530 Ok(ServerMessage::UserToDeviceDataV1(message))
22531 if message.application_id == 9_093
22532 && message.line_instance == 2
22533 && message.call_reference == 42
22534 && message.transaction_id == 73
22535 && message.display_priority == 2
22536 && CiscoIpPhoneExecute::from_xml(&message.data).unwrap() == execute
22537 )));
22538
22539 let image = PhoneImageDocument::ImageFile(CiscoIpPhoneImageFile {
22540 keypad_target: None,
22541 application_id: Some("map".into()),
22542 on_focus_lost: None,
22543 on_focus_gained: None,
22544 on_minimized: None,
22545 on_closed: None,
22546 title: Some("Site map".into()),
22547 prompt: Some("Inspect".into()),
22548 soft_keys: Vec::new(),
22549 key_items: Vec::new(),
22550 location_x: Some(12),
22551 location_y: Some(8),
22552 url: PhoneImageUrl::new("https://pbx.example/site.png?view=all").unwrap(),
22553 });
22554 handle
22555 .send(Command::new(
22556 device_id.clone(),
22557 CommandAction::ShowImageService {
22558 line_instance: LineInstance::new(2),
22559 call_reference: CallReference::new(42),
22560 application_id: ApplicationId::new(9_095),
22561 transaction_id: TransactionId::new(74),
22562 priority: PhoneServicePriority::LOW,
22563 document: image.clone(),
22564 },
22565 ))
22566 .await
22567 .unwrap();
22568 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22569 assert!(frames.into_iter().any(|frame| matches!(
22570 ServerMessage::decode(frame, protocol),
22571 Ok(ServerMessage::UserToDeviceDataV1(message))
22572 if message.application_id == 9_095
22573 && message.line_instance == 2
22574 && message.call_reference == 42
22575 && message.transaction_id == 74
22576 && message.display_priority == 0
22577 && PhoneImageDocument::from_xml(&message.data).unwrap() == image
22578 )));
22579
22580 let status = PhoneStatusDocument::File(CiscoIpPhoneStatusFile {
22581 text: Some("Queue ready".into()),
22582 timer_seconds: Some(10),
22583 location_x: Some(4),
22584 location_y: Some(8),
22585 url: PhoneImageUrl::new("https://pbx.example/status.png?queue=support").unwrap(),
22586 });
22587 handle
22588 .send(Command::new(
22589 device_id.clone(),
22590 CommandAction::ShowStatusService {
22591 line_instance: LineInstance::new(2),
22592 call_reference: CallReference::new(42),
22593 application_id: ApplicationId::new(9_096),
22594 transaction_id: TransactionId::new(75),
22595 priority: PhoneServicePriority::NORMAL,
22596 document: status.clone(),
22597 },
22598 ))
22599 .await
22600 .unwrap();
22601 let frames = read_until_message(&mut phone, &mut decoder, id::USER_TO_DEVICE_DATA_V1).await;
22602 assert!(frames.into_iter().any(|frame| matches!(
22603 ServerMessage::decode(frame, protocol),
22604 Ok(ServerMessage::UserToDeviceDataV1(message))
22605 if message.application_id == 9_096
22606 && message.line_instance == 2
22607 && message.call_reference == 42
22608 && message.transaction_id == 75
22609 && message.display_priority == 1
22610 && PhoneStatusDocument::from_xml(&message.data).unwrap() == status
22611 )));
22612
22613 let call_id = CallId(7001);
22614 handle
22615 .send(Command::new(
22616 device_id.clone(),
22617 CommandAction::BeginCall {
22618 line_instance: LineInstance(1),
22619 call_id,
22620 codec: Codec::Pcma,
22621 },
22622 ))
22623 .await
22624 .unwrap();
22625 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
22626 handle
22627 .send(Command::new(
22628 device_id.clone(),
22629 CommandAction::StartTone {
22630 call_id,
22631 tone: Tone::RecorderWarning,
22632 },
22633 ))
22634 .await
22635 .unwrap();
22636 let frames = read_until_message(&mut phone, &mut decoder, id::START_TONE).await;
22637 assert!(frames.into_iter().any(|frame| matches!(
22638 ServerMessage::decode(frame, protocol),
22639 Ok(ServerMessage::StartTone {
22640 tone: Tone::RecorderWarning,
22641 direction: ToneDirection::User,
22642 line_instance: 1,
22643 call_reference: 7001,
22644 })
22645 )));
22646
22647 handle
22648 .send_confirmed(Command::new(
22649 device_id.clone(),
22650 CommandAction::SetMicrophoneMode { enabled: false },
22651 ))
22652 .await
22653 .unwrap();
22654 let frames = read_until_message(&mut phone, &mut decoder, id::SET_MICROPHONE_MODE).await;
22655 assert!(frames.into_iter().any(|frame| matches!(
22656 ServerMessage::decode(frame, protocol),
22657 Ok(ServerMessage::SetMicrophoneMode(MicrophoneMode::Off))
22658 )));
22659
22660 handle
22661 .send_confirmed(Command::new(
22662 device_id.clone(),
22663 CommandAction::SetRecordingStatus {
22664 call_id,
22665 active: true,
22666 },
22667 ))
22668 .await
22669 .unwrap();
22670 let frames = read_until_message(&mut phone, &mut decoder, id::RECORDING_STATUS).await;
22671 assert!(frames.into_iter().any(|frame| matches!(
22672 ServerMessage::decode(frame, protocol),
22673 Ok(ServerMessage::RecordingStatus {
22674 call_reference: 7001,
22675 active: true,
22676 })
22677 )));
22678
22679 let conference_id = ConferenceId::new(44);
22680 let rejected = handle
22681 .send_confirmed(Command::new(
22682 device_id.clone(),
22683 CommandAction::StartAnnouncement {
22684 conference_id,
22685 announcements: vec![AnnouncementEntry {
22686 locale: 1,
22687 country: 46,
22688 tone: Tone::Zip,
22689 }],
22690 end_of_ack: true,
22691 participant_ids: vec![ParticipantId::new(7), ParticipantId::new(9)],
22692 hearing_participant_mask: 0b11,
22693 play_mode: 2,
22694 },
22695 ))
22696 .await
22697 .unwrap_err();
22698 assert!(
22699 matches!(rejected, ServerError::CommandWrite(message) if message.contains("not a station command"))
22700 );
22701
22702 handle
22705 .send_confirmed(Command::new(
22706 device_id.clone(),
22707 CommandAction::SetMicrophoneMode { enabled: true },
22708 ))
22709 .await
22710 .unwrap();
22711 let frames = read_until_message(&mut phone, &mut decoder, id::SET_MICROPHONE_MODE).await;
22712 assert!(frames.into_iter().any(|frame| matches!(
22713 ServerMessage::decode(frame, protocol),
22714 Ok(ServerMessage::SetMicrophoneMode(MicrophoneMode::On))
22715 )));
22716
22717 handle.shutdown().await.unwrap();
22718 task.await.unwrap().unwrap();
22719 }
22720
22721 #[test]
22722 fn announcement_command_mapping_preserves_typed_ids_and_wire_bounds() {
22723 let message = start_announcement_message(
22724 ConferenceId::new(44),
22725 vec![AnnouncementEntry {
22726 locale: 1,
22727 country: 46,
22728 tone: Tone::Zip,
22729 }],
22730 true,
22731 vec![ParticipantId::new(7), ParticipantId::new(9)],
22732 0b11,
22733 2,
22734 );
22735 assert!(matches!(
22736 message,
22737 ServerMessage::StartAnnouncement {
22738 conference_id: 44,
22739 end_of_ack: 1,
22740 ref matrix_conference_party_ids,
22741 ..
22742 } if matrix_conference_party_ids == &[7, 9]
22743 ));
22744 assert!(matches!(
22745 message.encode(ProtocolVersion::V22),
22746 Err(CodecError::UnexpectedRoute {
22747 actual: crate::MessageRoute::IntraControl,
22748 ..
22749 })
22750 ));
22751 let control = ControlMessage::StartAnnouncement {
22752 announcements: vec![AnnouncementEntry {
22753 locale: 1,
22754 country: 46,
22755 tone: Tone::Zip,
22756 }],
22757 end_of_ack: EndOfAnnouncementAck::Required,
22758 conference_id: 44,
22759 matrix_conference_party_ids: vec![7, 9],
22760 hearing_conference_party_mask: 0b11,
22761 play_mode: AnnouncementPlayMode::Continuous,
22762 };
22763 assert!(control.encode(ProtocolVersion::V22).is_ok());
22764
22765 let too_many_announcements = start_announcement_message(
22766 ConferenceId::new(44),
22767 vec![
22768 AnnouncementEntry {
22769 locale: 1,
22770 country: 46,
22771 tone: Tone::Zip,
22772 };
22773 33
22774 ],
22775 false,
22776 Vec::new(),
22777 0,
22778 0,
22779 );
22780 assert!(matches!(
22781 too_many_announcements.encode(ProtocolVersion::V22),
22782 Err(CodecError::CountTooLarge {
22783 field: "announcements",
22784 maximum: 32,
22785 ..
22786 })
22787 ));
22788
22789 let too_many_participants = start_announcement_message(
22790 ConferenceId::new(44),
22791 Vec::new(),
22792 false,
22793 (1..=17).map(ParticipantId::new).collect(),
22794 0,
22795 0,
22796 );
22797 assert!(matches!(
22798 too_many_participants.encode(ProtocolVersion::V22),
22799 Err(CodecError::CountTooLarge {
22800 field: "matrix conference party identifiers",
22801 maximum: 16,
22802 ..
22803 })
22804 ));
22805 }
22806
22807 #[tokio::test]
22808 async fn registered_xml_alarms_route_typed_or_opaque_without_leaking_payloads() {
22809 let config = ServerConfig {
22810 bind: "127.0.0.1:0".parse().unwrap(),
22811 advertised_address: Ipv4Addr::LOCALHOST,
22812 ..ServerConfig::default()
22813 };
22814 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22815 let address = server.local_addr().unwrap();
22816 let task = tokio::spawn(server.run());
22817 let mut phone = TcpStream::connect(address).await.unwrap();
22818 let mut decoder = FrameDecoder::new();
22819 let protocol = ProtocolVersion::V22;
22820
22821 phone.write_all(®ister_bytes(protocol)).await.unwrap();
22822 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22823 assert!(matches!(
22824 events.recv().await,
22825 Some(Event::Device(DeviceEvent {
22826 session_generation: _,
22827 device_id: _,
22828 event: DeviceEventKind::Registered(_)
22829 }))
22830 ));
22831
22832 let known = "<x-cisco-alarm><Alarm Name=\"LastOutOfServiceInformation\"><ParameterList><String name=\"DeviceName\">private-device-name</String><Enum name=\"ReasonForOutOfService\">25</Enum></ParameterList></Alarm></x-cisco-alarm>";
22833 phone
22834 .write_all(
22835 &ClientMessage::XmlAlarm(XmlAlarmMessage::from_xml(known).unwrap())
22836 .encode(protocol)
22837 .unwrap(),
22838 )
22839 .await
22840 .unwrap();
22841 let Some(Event::Device(DeviceEvent {
22842 session_generation: _,
22843 device_id,
22844 event: DeviceEventKind::XmlAlarm { telemetry },
22845 })) = events.recv().await
22846 else {
22847 panic!("typed XML alarm event was not emitted");
22848 };
22849 assert_eq!(device_id, DeviceId::new("SEP001122334455").unwrap());
22850 assert_eq!(
22851 telemetry.summary(),
22852 Some(crate::phone::xml::PhoneAlarmSummary {
22853 kind: crate::phone::xml::PhoneAlarmKind::LastOutOfService,
22854 reason_for_out_of_service: Some(25),
22855 })
22856 );
22857 assert!(!format!("{telemetry:?}").contains("private-device-name"));
22858
22859 let unknown = "<vendor-alarm><Credential>private-token</Credential></vendor-alarm>";
22860 phone
22861 .write_all(
22862 &ClientMessage::XmlAlarm(XmlAlarmMessage::from_xml(unknown).unwrap())
22863 .encode(protocol)
22864 .unwrap(),
22865 )
22866 .await
22867 .unwrap();
22868 let Some(Event::Device(DeviceEvent {
22869 session_generation: _,
22870 device_id: _,
22871 event: DeviceEventKind::XmlAlarm { telemetry, .. },
22872 })) = events.recv().await
22873 else {
22874 panic!("opaque XML alarm event was not emitted");
22875 };
22876 assert!(telemetry.is_opaque());
22877 assert_eq!(telemetry.summary(), None);
22878 assert!(!format!("{telemetry:?}").contains("private-token"));
22879
22880 phone
22881 .write_all(
22882 &ClientMessage::XmlAlarm(
22883 XmlAlarmMessage::from_xml(
22884 "<x-cisco-alarm><Alarm Name=\"Unknown\">&undeclared;</Alarm></x-cisco-alarm>",
22885 )
22886 .unwrap(),
22887 )
22888 .encode(protocol)
22889 .unwrap(),
22890 )
22891 .await
22892 .unwrap();
22893 assert!(
22894 tokio::time::timeout(Duration::from_millis(50), events.recv())
22895 .await
22896 .is_err()
22897 );
22898
22899 phone
22900 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
22901 .await
22902 .unwrap();
22903 let frames = read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
22904 assert!(
22905 frames
22906 .iter()
22907 .any(|frame| frame.message_id == id::KEEP_ALIVE_ACK)
22908 );
22909
22910 handle.shutdown().await.unwrap();
22911 task.await.unwrap().unwrap();
22912 }
22913
22914 #[tokio::test]
22915 async fn registered_location_information_routes_typed_or_opaque_without_leaking_fields() {
22916 let config = ServerConfig {
22917 bind: "127.0.0.1:0".parse().unwrap(),
22918 advertised_address: Ipv4Addr::LOCALHOST,
22919 ..ServerConfig::default()
22920 };
22921 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
22922 let address = server.local_addr().unwrap();
22923 let task = tokio::spawn(server.run());
22924 let mut phone = TcpStream::connect(address).await.unwrap();
22925 let mut decoder = FrameDecoder::new();
22926 let protocol = ProtocolVersion::V22;
22927
22928 phone.write_all(®ister_bytes(protocol)).await.unwrap();
22929 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
22930 assert!(matches!(
22931 events.recv().await,
22932 Some(Event::Device(DeviceEvent {
22933 session_generation: _,
22934 device_id: _,
22935 event: DeviceEventKind::Registered(_)
22936 }))
22937 ));
22938
22939 let known = "<Interface1><wifi><BSSID>E8:ED:F3:10:29:FD</BSSID><SSID>private-network</SSID><APName>private-access-point</APName></wifi><OffPrem></OffPrem></Interface1>";
22940 phone
22941 .write_all(
22942 &ClientMessage::LocationInfo { xml: known.into() }
22943 .encode(protocol)
22944 .unwrap(),
22945 )
22946 .await
22947 .unwrap();
22948 let Some(Event::Device(DeviceEvent {
22949 session_generation: _,
22950 device_id,
22951 event: DeviceEventKind::LocationInformation { telemetry },
22952 })) = events.recv().await
22953 else {
22954 panic!("typed location-information event was not emitted");
22955 };
22956 assert_eq!(device_id, DeviceId::new("SEP001122334455").unwrap());
22957 assert_eq!(
22958 telemetry.summary(),
22959 Some(crate::phone::xml::PhoneLocationSummary {
22960 kind: crate::phone::xml::PhoneLocationKind::WirelessInterface,
22961 off_premises: true,
22962 })
22963 );
22964 let crate::phone::xml::PhoneLocationTelemetry::WirelessInterface(location) = &telemetry
22965 else {
22966 panic!("known wireless location was not typed");
22967 };
22968 assert_eq!(
22969 location.wifi.bssid.octets(),
22970 [0xe8, 0xed, 0xf3, 0x10, 0x29, 0xfd]
22971 );
22972 assert_eq!(location.wifi.ssid, "private-network");
22973 assert_eq!(location.wifi.access_point_name, "private-access-point");
22974 let debug = format!("{telemetry:?}");
22975 assert!(!debug.contains("private-network"));
22976 assert!(!debug.contains("private-access-point"));
22977 assert!(!debug.contains("E8:ED:F3:10:29:FD"));
22978
22979 let unknown =
22980 "<DeviceLocation><CivicAddress>private-building</CivicAddress></DeviceLocation>";
22981 phone
22982 .write_all(
22983 &ClientMessage::LocationInfo {
22984 xml: unknown.into(),
22985 }
22986 .encode(protocol)
22987 .unwrap(),
22988 )
22989 .await
22990 .unwrap();
22991 let Some(Event::Device(DeviceEvent {
22992 session_generation: _,
22993 device_id: _,
22994 event: DeviceEventKind::LocationInformation { telemetry, .. },
22995 })) = events.recv().await
22996 else {
22997 panic!("opaque location-information event was not emitted");
22998 };
22999 assert!(telemetry.is_opaque());
23000 assert_eq!(telemetry.summary(), None);
23001 assert!(!format!("{telemetry:?}").contains("private-building"));
23002
23003 phone
23004 .write_all(
23005 &ClientMessage::LocationInfo {
23006 xml: "<Interface1>&undeclared;</Interface1>".into(),
23007 }
23008 .encode(protocol)
23009 .unwrap(),
23010 )
23011 .await
23012 .unwrap();
23013 assert!(
23014 tokio::time::timeout(Duration::from_millis(50), events.recv())
23015 .await
23016 .is_err()
23017 );
23018
23019 phone
23020 .write_all(&ClientMessage::KeepAlive.encode(protocol).unwrap())
23021 .await
23022 .unwrap();
23023 let frames = read_until_message(&mut phone, &mut decoder, id::KEEP_ALIVE_ACK).await;
23024 assert!(
23025 frames
23026 .iter()
23027 .any(|frame| frame.message_id == id::KEEP_ALIVE_ACK)
23028 );
23029
23030 handle.shutdown().await.unwrap();
23031 task.await.unwrap().unwrap();
23032 }
23033
23034 #[tokio::test]
23035 async fn transfer_presentation_marks_source_and_keeps_consultation_active() {
23036 let config = ServerConfig {
23037 bind: "127.0.0.1:0".parse().unwrap(),
23038 advertised_address: Ipv4Addr::LOCALHOST,
23039 ..ServerConfig::default()
23040 };
23041 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
23042 let address = server.local_addr().unwrap();
23043 let task = tokio::spawn(server.run());
23044 let mut phone = TcpStream::connect(address).await.unwrap();
23045 let mut decoder = FrameDecoder::new();
23046 let protocol = ProtocolVersion::V22;
23047 let device_id = DeviceId::new("SEP001122334455").unwrap();
23048
23049 phone.write_all(®ister_bytes(protocol)).await.unwrap();
23050 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
23051 assert!(matches!(
23052 events.recv().await,
23053 Some(Event::Device(DeviceEvent {
23054 session_generation: _,
23055 device_id: _,
23056 event: DeviceEventKind::Registered(_)
23057 }))
23058 ));
23059 handle
23060 .send_confirmed(Command::new(
23061 device_id.clone(),
23062 CommandAction::BeginCall {
23063 line_instance: LineInstance(1),
23064 call_id: CallId(10),
23065 codec: Codec::Pcmu,
23066 },
23067 ))
23068 .await
23069 .unwrap();
23070 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23071 for state in [CallState::Connected, CallState::Hold] {
23072 handle
23073 .send_confirmed(Command::new(
23074 device_id.clone(),
23075 CommandAction::SetCallState {
23076 call_id: CallId(10),
23077 state,
23078 },
23079 ))
23080 .await
23081 .unwrap();
23082 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23083 }
23084
23085 handle
23086 .send_confirmed(Command::new(
23087 device_id.clone(),
23088 CommandAction::BeginTransfer {
23089 source_call_id: CallId(10),
23090 consultation_line_instance: LineInstance(1),
23091 consultation_call_id: CallId(20),
23092 codec: Codec::Pcmu,
23093 },
23094 ))
23095 .await
23096 .unwrap();
23097 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
23098 matches!(
23099 message,
23100 ServerMessage::SetLamp {
23101 stimulus: ButtonType::Transfer,
23102 mode: LampMode::Flash,
23103 ..
23104 }
23105 )
23106 })
23107 .await;
23108 let states = messages
23109 .iter()
23110 .filter_map(|message| match message {
23111 ServerMessage::CallState {
23112 state,
23113 call_reference,
23114 ..
23115 } => Some((*state, *call_reference)),
23116 _ => None,
23117 })
23118 .collect::<Vec<_>>();
23119 assert_eq!(
23120 states,
23121 vec![(CallState::Transfer, 10), (CallState::OffHook, 20)]
23122 );
23123 assert!(messages.iter().any(|message| matches!(
23124 message,
23125 ServerMessage::SelectSoftKeys {
23126 call_reference: 20,
23127 set: KeyMode::OffHookFeature,
23128 ..
23129 }
23130 )));
23131 assert!(!messages.iter().any(|message| matches!(
23132 message,
23133 ServerMessage::CallState {
23134 state: CallState::Transfer,
23135 call_reference: 20,
23136 ..
23137 }
23138 )));
23139
23140 handle
23141 .send_confirmed(Command::new(
23142 device_id.clone(),
23143 CommandAction::SetCallState {
23144 call_id: CallId(20),
23145 state: CallState::Connected,
23146 },
23147 ))
23148 .await
23149 .unwrap();
23150 let messages = read_until_server_message(&mut phone, &mut decoder, protocol, |message| {
23151 matches!(
23152 message,
23153 ServerMessage::SelectSoftKeys {
23154 call_reference: 20,
23155 set: KeyMode::ConnectedTransfer,
23156 ..
23157 }
23158 )
23159 })
23160 .await;
23161 assert!(messages.iter().any(|message| matches!(
23162 message,
23163 ServerMessage::SelectSoftKeys {
23164 call_reference: 20,
23165 set: KeyMode::ConnectedTransfer,
23166 ..
23167 }
23168 )));
23169
23170 handle.shutdown().await.unwrap();
23171 task.await.unwrap().unwrap();
23172 }
23173
23174 #[tokio::test]
23175 async fn active_call_selection_and_hook_flash_use_exact_session_identity() {
23176 let config = ServerConfig {
23177 bind: "127.0.0.1:0".parse().unwrap(),
23178 advertised_address: Ipv4Addr::LOCALHOST,
23179 ..ServerConfig::default()
23180 };
23181 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
23182 let address = server.local_addr().unwrap();
23183 let task = tokio::spawn(server.run());
23184 let mut phone = TcpStream::connect(address).await.unwrap();
23185 let mut decoder = FrameDecoder::new();
23186 let protocol = ProtocolVersion::V22;
23187 let device_id = DeviceId::new("SEP001122334455").unwrap();
23188
23189 phone.write_all(®ister_bytes(protocol)).await.unwrap();
23190 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
23191 assert!(matches!(
23192 events.recv().await,
23193 Some(Event::Device(DeviceEvent {
23194 session_generation: _,
23195 device_id: _,
23196 event: DeviceEventKind::Registered(_)
23197 }))
23198 ));
23199 for call_id in [CallId(10), CallId(20)] {
23200 handle
23201 .send(Command::new(
23202 device_id.clone(),
23203 CommandAction::BeginCall {
23204 line_instance: LineInstance(1),
23205 call_id,
23206 codec: Codec::Pcmu,
23207 },
23208 ))
23209 .await
23210 .unwrap();
23211 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23212 }
23213
23214 handle
23215 .send(Command::new(
23216 device_id.clone(),
23217 CommandAction::SetCallSelected {
23218 call_id: CallId(10),
23219 selected: true,
23220 },
23221 ))
23222 .await
23223 .unwrap();
23224 let frames = read_until_message(&mut phone, &mut decoder, id::CALL_SELECT_STAT).await;
23225 assert!(frames.into_iter().any(|frame| matches!(
23226 ServerMessage::decode(frame, protocol),
23227 Ok(ServerMessage::CallSelectStatus {
23228 status: 1,
23229 call_reference: 10,
23230 line_instance: 1,
23231 })
23232 )));
23233
23234 phone
23235 .write_all(
23236 &ClientMessage::HookFlash {
23237 line_instance: 1,
23238 call_reference: 0,
23239 }
23240 .encode(protocol)
23241 .unwrap(),
23242 )
23243 .await
23244 .unwrap();
23245 assert!(matches!(
23246 events.recv().await,
23247 Some(Event::Device(DeviceEvent {
23248 session_generation: _,
23249 device_id: _,
23250 event: DeviceEventKind::HookFlash {
23251 call_id: Some(CallId(20)),
23252 line_instance: LineInstance(1),
23253 ..
23254 }
23255 }))
23256 ));
23257
23258 handle.shutdown().await.unwrap();
23259 task.await.unwrap().unwrap();
23260 }
23261
23262 #[tokio::test]
23263 async fn omitted_answer_uses_live_policy_and_skips_an_offer_closed_before_input() {
23264 let config = ServerConfig {
23265 bind: "127.0.0.1:0".parse().unwrap(),
23266 advertised_address: Ipv4Addr::LOCALHOST,
23267 call_answer_order: CallSelectionOrder::LastFirst,
23268 ..ServerConfig::default()
23269 };
23270 let (server, handle, mut events) = Server::bind(config, [definition()]).await.unwrap();
23271 let address = server.local_addr().unwrap();
23272 let task = tokio::spawn(server.run());
23273 let mut phone = TcpStream::connect(address).await.unwrap();
23274 let mut decoder = FrameDecoder::new();
23275 let protocol = ProtocolVersion::V22;
23276 let device_id = DeviceId::new("SEP001122334455").unwrap();
23277
23278 phone.write_all(®ister_bytes(protocol)).await.unwrap();
23279 read_until_message(&mut phone, &mut decoder, id::CAPABILITIES_REQ).await;
23280 assert!(matches!(
23281 events.recv().await,
23282 Some(Event::Device(DeviceEvent {
23283 session_generation: _,
23284 device_id: _,
23285 event: DeviceEventKind::Registered(_)
23286 }))
23287 ));
23288 handle
23289 .send(Command::new(
23290 device_id.clone(),
23291 CommandAction::BeginCall {
23292 line_instance: LineInstance(1),
23293 call_id: CallId(1),
23294 codec: Codec::Pcmu,
23295 },
23296 ))
23297 .await
23298 .unwrap();
23299 read_until_message(&mut phone, &mut decoder, id::SELECT_SOFT_KEYS).await;
23300 handle
23301 .send(Command::new(
23302 device_id.clone(),
23303 CommandAction::SetCallState {
23304 call_id: CallId(1),
23305 state: CallState::Connected,
23306 },
23307 ))
23308 .await
23309 .unwrap();
23310 read_until_message(&mut phone, &mut decoder, id::CALL_STATE).await;
23311
23312 let info = CallInfo {
23313 direction: crate::types::CallDirection::Inbound,
23314 calling_name: "Caller".into(),
23315 calling_number: "1002".into(),
23316 called_name: "Desk".into(),
23317 called_number: "1001".into(),
23318 ..CallInfo::default()
23319 };
23320 for call_id in [CallId(10), CallId(20)] {
23321 handle
23322 .offer_incoming_call_with_id(
23323 device_id.clone(),
23324 LineInstance::new(1),
23325 call_id,
23326 info.clone(),
23327 )
23328 .await
23329 .unwrap();
23330 read_until_message(&mut phone, &mut decoder, id::DISPLAY_DYNAMIC_PROMPT_STATUS).await;
23331 }
23332 phone
23333 .write_all(
23334 &ClientMessage::SoftKeyEvent {
23335 event: SoftKey::Answer.wire_value(),
23336 line_instance: 1,
23337 call_reference: 0,
23338 }
23339 .encode(protocol)
23340 .unwrap(),
23341 )
23342 .await
23343 .unwrap();
23344 assert!(matches!(
23345 events.recv().await,
23346 Some(Event::Device(DeviceEvent {
23347 session_generation: _,
23348 device_id: _,
23349 event: DeviceEventKind::SoftKey {
23350 call_id: Some(CallId(20)),
23351 soft_key: SoftKey::Answer,
23352 ..
23353 }
23354 }))
23355 ));
23356
23357 handle
23358 .try_offer_incoming_call_with_id(
23359 device_id.clone(),
23360 LineInstance::new(1),
23361 CallId(30),
23362 info,
23363 )
23364 .unwrap();
23365 handle
23366 .send_confirmed(Command::new(
23367 device_id.clone(),
23368 CommandAction::CloseCall {
23369 call_id: CallId(30),
23370 },
23371 ))
23372 .await
23373 .unwrap();
23374 handle
23375 .send_confirmed(Command::new(
23376 device_id.clone(),
23377 CommandAction::CloseCall {
23378 call_id: CallId(20),
23379 },
23380 ))
23381 .await
23382 .unwrap();
23383 phone
23384 .write_all(
23385 &ClientMessage::OffHook {
23386 line_instance: 1,
23387 call_reference: 0,
23388 }
23389 .encode(protocol)
23390 .unwrap(),
23391 )
23392 .await
23393 .unwrap();
23394 assert!(matches!(
23395 events.recv().await,
23396 Some(Event::Device(DeviceEvent {
23397 session_generation: _,
23398 device_id: _,
23399 event: DeviceEventKind::OffHook {
23400 call_id: CallId(10),
23401 line_instance: LineInstance(1),
23402 ..
23403 }
23404 }))
23405 ));
23406
23407 handle.shutdown().await.unwrap();
23408 task.await.unwrap().unwrap();
23409 }
23410
23411 #[test]
23412 fn server_messages_are_decodeable_frames() {
23413 let bytes = ServerMessage::CapabilitiesRequest
23414 .encode(ProtocolVersion::V22)
23415 .unwrap();
23416 assert_eq!(
23417 FrameDecoder::new().push(&bytes).unwrap()[0].message_id,
23418 id::CAPABILITIES_REQ
23419 );
23420 assert!(matches!(
23421 ClientMessage::decode(Frame::new(0, id::KEEP_ALIVE, Vec::new())).unwrap(),
23422 ClientMessage::KeepAlive
23423 ));
23424 }
23425}