1mod bounded;
14pub mod capabilities;
15pub mod catalog;
16mod codec;
17pub mod values;
18pub mod wire;
19
20use std::fmt;
21use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
22use std::num::{NonZeroU16, NonZeroU32};
23
24use crate::types::DateTemplate;
25use crate::types::{
26 ApplicationId, CallInfo, CallReference, ConferenceId, DeviceId, MediaEndpoint, SoftKeyProfile,
27 TransactionId,
28};
29use capabilities::CapabilityUpdate;
30use catalog::MessageId;
31use values::{
32 AddParticipantResult, AlarmSeverity, AnnouncementPlayMode, AnnouncementPlayStatus,
33 AuditParticipantResult, BusyLampFieldState, ButtonType, CallHistoryDisposition, CallState,
34 Codec, ConferenceResourceType, CreateConferenceResult, DeleteConferenceResult, DeviceType,
35 Digit, EchoCancellation, EncryptionMethod, EndOfAnnouncementAck, G723BitRate, IpAddressType,
36 KeyMode, LampMode, MediaPathCapability, MediaPathEvent, MediaPathId, MediaStatus,
37 MediaTransport, MediaType, MessageWaitingResult, MicrophoneMode, ModifyConferenceResult,
38 NotificationPriority, PartyInformationRestrictions, PhoneFeatures, ProtocolVersion,
39 QosDirection, QosErrorCode, QosReservationStyle, ResetType, RingDuration, RingerMode,
40 RsvpErrorCode, SilenceSuppression, SpeakerMode, StatisticsProcessing, Stimulus,
41 SubscriptionCause, Tone, ToneDirection, VideoFormat,
42};
43use wire::CodecError;
44
45pub use bounded::{BoundedBytes, BoundedBytesError};
46
47pub const MAX_OPAQUE_MESSAGE_BYTES: usize = wire::MAX_FRAME_SIZE - wire::HEADER_SIZE;
49
50pub const MULTIMEDIA_CAPABILITY_BYTES: usize = 76;
52pub const MAX_MULTIMEDIA_PICTURE_FORMATS: usize = 5;
54
55#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
62pub struct MediaRequestToken(NonZeroU32);
63
64impl MediaRequestToken {
65 pub const fn new(value: u32) -> Option<Self> {
67 match NonZeroU32::new(value) {
68 Some(value) => Some(Self(value)),
69 None => None,
70 }
71 }
72
73 pub const fn get(self) -> u32 {
74 self.0.get()
75 }
76
77 pub const fn checked_next(self) -> Option<Self> {
82 match self.get().checked_add(1) {
83 Some(value) => Self::new(value),
84 None => None,
85 }
86 }
87}
88
89#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
92pub struct MediaRequestIdentity {
93 generation: u64,
94 token: MediaRequestToken,
95}
96
97impl MediaRequestIdentity {
98 pub const fn new(generation: u64, token: MediaRequestToken) -> Option<Self> {
102 if generation == 0 {
103 None
104 } else {
105 Some(Self { generation, token })
106 }
107 }
108
109 pub const fn generation(self) -> u64 {
110 self.generation
111 }
112
113 pub const fn token(self) -> MediaRequestToken {
114 self.token
115 }
116
117 pub const fn checked_next(self) -> Option<Self> {
120 let generation = match self.generation.checked_add(1) {
121 Some(generation) => generation,
122 None => return None,
123 };
124 let token = match self.token.checked_next() {
125 Some(token) => token,
126 None => return None,
127 };
128 Some(Self { generation, token })
129 }
130
131 pub const fn accepts_ack(
139 self,
140 acknowledgement_party_id: u32,
141 acknowledgement_call_reference: u32,
142 stable_call_reference: u32,
143 ) -> bool {
144 let call_matches = acknowledgement_call_reference == 0
145 || acknowledgement_call_reference == stable_call_reference;
146 if acknowledgement_party_id == self.token.get() {
147 return call_matches;
148 }
149 self.generation == 1
150 && acknowledgement_party_id == 0
151 && acknowledgement_call_reference == stable_call_reference
152 }
153}
154
155pub mod id {
160 pub const KEEP_ALIVE: u32 = 0x0000;
161 pub const REGISTER: u32 = 0x0001;
162 pub const IP_PORT: u32 = 0x0002;
163 pub const KEYPAD_BUTTON: u32 = 0x0003;
164 pub const ENBLOC_CALL: u32 = 0x0004;
165 pub const STIMULUS: u32 = 0x0005;
166 pub const OFF_HOOK: u32 = 0x0006;
167 pub const ON_HOOK: u32 = 0x0007;
168 pub const HOOK_FLASH: u32 = 0x0008;
169 pub const FORWARD_STAT_REQ: u32 = 0x0009;
170 pub const SPEED_DIAL_STAT_REQ: u32 = 0x000a;
171 pub const LINE_STAT_REQ: u32 = 0x000b;
172 pub const CONFIG_STAT_REQ: u32 = 0x000c;
173 pub const TIME_DATE_REQ: u32 = 0x000d;
174 pub const BUTTON_TEMPLATE_REQ: u32 = 0x000e;
175 pub const VERSION_REQ: u32 = 0x000f;
176 pub const CAPABILITIES_RES: u32 = 0x0010;
177 pub const SERVER_REQ: u32 = 0x0012;
178 pub const ALARM: u32 = 0x0020;
179 pub const MULTICAST_MEDIA_RECEPTION_ACK: u32 = 0x0021;
180 pub const OPEN_RECEIVE_CHANNEL_ACK: u32 = 0x0022;
181 pub const CONNECTION_STATISTICS_RES: u32 = 0x0023;
182 pub const OFF_HOOK_WITH_CALLING_PARTY: u32 = 0x0024;
183 pub const SOFT_KEY_SET_REQ: u32 = 0x0025;
184 pub const SOFT_KEY_EVENT: u32 = 0x0026;
185 pub const UNREGISTER: u32 = 0x0027;
186 pub const SOFT_KEY_TEMPLATE_REQ: u32 = 0x0028;
187 pub const REGISTER_TOKEN_REQ: u32 = 0x0029;
188 pub const MEDIA_TRANSMISSION_FAILURE: u32 = 0x002a;
189 pub const HEADSET_STATUS: u32 = 0x002b;
190 pub const MEDIA_RESOURCE_NOTIFICATION: u32 = 0x002c;
191 pub const REGISTER_AVAILABLE_LINES: u32 = 0x002d;
192 pub const DEVICE_TO_USER_DATA: u32 = 0x002e;
193 pub const DEVICE_TO_USER_DATA_RESPONSE: u32 = 0x002f;
194 pub const UPDATE_CAPABILITIES: u32 = 0x0030;
195 pub const OPEN_MULTIMEDIA_RECEIVE_CHANNEL_ACK: u32 = 0x0031;
196 pub const CLEAR_CONFERENCE: u32 = 0x0032;
197 pub const SERVICE_URL_STAT_REQ: u32 = 0x0033;
198 pub const FEATURE_STAT_REQ: u32 = 0x0034;
199 pub const CREATE_CONFERENCE_RES: u32 = 0x0035;
200 pub const DELETE_CONFERENCE_RES: u32 = 0x0036;
201 pub const MODIFY_CONFERENCE_RES: u32 = 0x0037;
202 pub const ADD_PARTICIPANT_RES: u32 = 0x0038;
203 pub const AUDIT_CONFERENCE_RES: u32 = 0x0039;
204 pub const AUDIT_PARTICIPANT_RES: u32 = 0x0040;
205 pub const QOS_RESERVATION_NOTIFY: u32 = 0x0046;
206 pub const QOS_ERROR_NOTIFY: u32 = 0x0047;
207 pub const DEVICE_TO_USER_DATA_V1: u32 = 0x0041;
208 pub const DEVICE_TO_USER_DATA_RESPONSE_V1: u32 = 0x0042;
209 pub const UPDATE_CAPABILITIES_V2: u32 = 0x0043;
210 pub const UPDATE_CAPABILITIES_V3: u32 = 0x0044;
211 pub const PORT_RESPONSE: u32 = 0x0045;
212 pub const SUBSCRIPTION_STAT_REQ: u32 = 0x0048;
213 pub const ACCESSORY_STATUS: u32 = 0x0049;
214 pub const MEDIA_PATH_CAPABILITY: u32 = 0x004a;
215 pub const MWI_NOTIFICATION: u32 = 0x004c;
216
217 pub const REGISTER_ACK: u32 = 0x0081;
218 pub const START_TONE: u32 = 0x0082;
219 pub const STOP_TONE: u32 = 0x0083;
220 pub const SET_RINGER: u32 = 0x0085;
221 pub const SET_LAMP: u32 = 0x0086;
222 pub const SET_SPEAKER_MODE: u32 = 0x0088;
223 pub const SET_MICROPHONE_MODE: u32 = 0x0089;
224 pub const START_MEDIA_TRANSMISSION: u32 = 0x008a;
225 pub const STOP_MEDIA_TRANSMISSION: u32 = 0x008b;
226 pub const START_SESSION_TRANSMISSION: u32 = 0x0095;
227 pub const STOP_SESSION_TRANSMISSION: u32 = 0x0096;
228 pub const CALL_INFO: u32 = 0x008f;
229 pub const FORWARD_STAT: u32 = 0x0090;
230 pub const SPEED_DIAL_STAT: u32 = 0x0091;
231 pub const LINE_STAT: u32 = 0x0092;
232 pub const CONFIG_STAT: u32 = 0x0093;
233 pub const DEFINE_TIME_DATE: u32 = 0x0094;
234 pub const BUTTON_TEMPLATE: u32 = 0x0097;
235 pub const VERSION: u32 = 0x0098;
236 pub const DISPLAY_TEXT: u32 = 0x0099;
237 pub const CLEAR_DISPLAY: u32 = 0x009a;
238 pub const CAPABILITIES_REQ: u32 = 0x009b;
239 pub const REGISTER_REJECT: u32 = 0x009d;
240 pub const SERVER_RES: u32 = 0x009e;
241 pub const RESET: u32 = 0x009f;
242 pub const KEEP_ALIVE_ACK: u32 = 0x0100;
243 pub const START_MULTICAST_MEDIA_RECEPTION: u32 = 0x0101;
244 pub const START_MULTICAST_MEDIA_TRANSMISSION: u32 = 0x0102;
245 pub const STOP_MULTICAST_MEDIA_RECEPTION: u32 = 0x0103;
246 pub const STOP_MULTICAST_MEDIA_TRANSMISSION: u32 = 0x0104;
247 pub const OPEN_RECEIVE_CHANNEL: u32 = 0x0105;
248 pub const CLOSE_RECEIVE_CHANNEL: u32 = 0x0106;
249 pub const CONNECTION_STATISTICS_REQ: u32 = 0x0107;
250 pub const SOFT_KEY_TEMPLATE_RES: u32 = 0x0108;
251 pub const SOFT_KEY_SET_RES: u32 = 0x0109;
252 pub const SELECT_SOFT_KEYS: u32 = 0x0110;
253 pub const CALL_STATE: u32 = 0x0111;
254 pub const DISPLAY_PROMPT_STATUS: u32 = 0x0112;
255 pub const CLEAR_PROMPT_STATUS: u32 = 0x0113;
256 pub const DISPLAY_NOTIFY: u32 = 0x0114;
257 pub const CLEAR_NOTIFY: u32 = 0x0115;
258 pub const ACTIVATE_CALL_PLANE: u32 = 0x0116;
259 pub const DEACTIVATE_CALL_PLANE: u32 = 0x0117;
260 pub const UNREGISTER_ACK: u32 = 0x0118;
261 pub const BACKSPACE_RESPONSE: u32 = 0x0119;
262 pub const REGISTER_TOKEN_ACK: u32 = 0x011a;
263 pub const REGISTER_TOKEN_REJECT: u32 = 0x011b;
264 pub const START_MEDIA_FAILURE_DETECTION: u32 = 0x011c;
265 pub const DIALED_NUMBER: u32 = 0x011d;
266 pub const USER_TO_DEVICE_DATA: u32 = 0x011e;
267 pub const FEATURE_STAT: u32 = 0x011f;
268 pub const DISPLAY_PRIORITY_NOTIFY: u32 = 0x0120;
269 pub const CLEAR_PRIORITY_NOTIFY: u32 = 0x0121;
270 pub const START_ANNOUNCEMENT: u32 = 0x0122;
271 pub const STOP_ANNOUNCEMENT: u32 = 0x0123;
272 pub const ANNOUNCEMENT_FINISH: u32 = 0x0124;
273 pub const NOTIFY_DTMF_TONE: u32 = 0x0127;
274 pub const SEND_DTMF_TONE: u32 = 0x0128;
275 pub const SUBSCRIBE_DTMF_PAYLOAD_REQ: u32 = 0x0129;
276 pub const SUBSCRIBE_DTMF_PAYLOAD_RES: u32 = 0x012a;
277 pub const SUBSCRIBE_DTMF_PAYLOAD_ERR: u32 = 0x012b;
278 pub const UNSUBSCRIBE_DTMF_PAYLOAD_REQ: u32 = 0x012c;
279 pub const UNSUBSCRIBE_DTMF_PAYLOAD_RES: u32 = 0x012d;
280 pub const UNSUBSCRIBE_DTMF_PAYLOAD_ERR: u32 = 0x012e;
281 pub const SERVICE_URL_STAT: u32 = 0x012f;
282 pub const CALL_SELECT_STAT: u32 = 0x0130;
283 pub const STOP_MULTIMEDIA_TRANSMISSION: u32 = 0x0133;
284 pub const FLOW_CONTROL_COMMAND: u32 = 0x0135;
285 pub const CLOSE_MULTIMEDIA_RECEIVE_CHANNEL: u32 = 0x0136;
286 pub const CREATE_CONFERENCE_REQ: u32 = 0x0137;
287 pub const DELETE_CONFERENCE_REQ: u32 = 0x0138;
288 pub const MODIFY_CONFERENCE_REQ: u32 = 0x0139;
289 pub const ADD_PARTICIPANT_REQ: u32 = 0x013a;
290 pub const DROP_PARTICIPANT_REQ: u32 = 0x013b;
291 pub const AUDIT_CONFERENCE_REQ: u32 = 0x013c;
292 pub const AUDIT_PARTICIPANT_REQ: u32 = 0x013d;
293 pub const CHANGE_PARTICIPANT_REQ: u32 = 0x013e;
294 pub const USER_TO_DEVICE_DATA_V1: u32 = 0x013f;
295 pub const VIDEO_DISPLAY_COMMAND: u32 = 0x0140;
296 pub const FLOW_CONTROL_NOTIFY: u32 = 0x0141;
297 pub const CONFIG_STAT_DYNAMIC: u32 = 0x0142;
298 pub const DISPLAY_DYNAMIC_NOTIFY: u32 = 0x0143;
299 pub const DISPLAY_DYNAMIC_PRIORITY_NOTIFY: u32 = 0x0144;
300 pub const DISPLAY_DYNAMIC_PROMPT_STATUS: u32 = 0x0145;
301 pub const FEATURE_STAT_DYNAMIC: u32 = 0x0146;
302 pub const LINE_STAT_DYNAMIC: u32 = 0x0147;
303 pub const SERVICE_URL_STAT_DYNAMIC: u32 = 0x0148;
304 pub const SPEED_DIAL_STAT_DYNAMIC: u32 = 0x0149;
305 pub const CALL_INFO_DYNAMIC: u32 = 0x014a;
306 pub const PORT_REQUEST: u32 = 0x014b;
307 pub const PORT_CLOSE: u32 = 0x014c;
308 pub const QOS_LISTEN: u32 = 0x014d;
309 pub const QOS_PATH: u32 = 0x014e;
310 pub const QOS_TEARDOWN: u32 = 0x014f;
311 pub const UPDATE_DSCP: u32 = 0x0150;
312 pub const QOS_MODIFY: u32 = 0x0151;
313 pub const SUBSCRIPTION_STAT: u32 = 0x0152;
314 pub const NOTIFICATION: u32 = 0x0153;
315 pub const START_MEDIA_TRANSMISSION_ACK: u32 = 0x0154;
316 pub const START_MULTIMEDIA_TRANSMISSION_ACK: u32 = 0x0155;
317 pub const OPEN_MULTIMEDIA_CHANNEL: u32 = 0x0131;
318 pub const START_MULTIMEDIA_TRANSMISSION: u32 = 0x0132;
319 pub const MISCELLANEOUS_COMMAND: u32 = 0x0134;
320 pub const CALL_HISTORY_DISPOSITION: u32 = 0x0156;
321 pub const LOCATION_INFO: u32 = 0x0157;
322 pub const MWI_RESPONSE: u32 = 0x0158;
323 pub const EXTENSION_DEVICE_CAPABILITIES: u32 = 0x0159;
324 pub const XML_ALARM: u32 = 0x015a;
325 pub const CALL_COUNT_REQ: u32 = 0x015e;
326 pub const CALL_COUNT_RES: u32 = 0x015f;
327 pub const RECORDING_STATUS: u32 = 0x0160;
328}
329
330#[derive(Clone, Debug, Eq, PartialEq)]
331pub struct RawMessage {
333 pub message_id: u32,
334 pub protocol_version: u32,
335 pub payload: Vec<u8>,
336}
337
338#[derive(Clone, Debug, Eq, PartialEq)]
339pub struct RegistrationMessage {
344 pub device_id: DeviceId,
345 pub reported_address: Option<Ipv4Addr>,
347 pub reported_ipv6_address: Option<Ipv6Addr>,
349 pub device_type: DeviceType,
350 pub advertised_protocol: u32,
354 pub features: PhoneFeatures,
356 pub firmware: String,
357 pub configuration_version_stamp: BoundedBytes<48>,
359 pub wire: Option<RegistrationWireDetails>,
363}
364
365#[derive(Clone, Copy, Debug, Eq, PartialEq)]
370pub struct RegistrationWireDetails {
371 pub station_user_id: u32,
372 pub station_instance: u32,
373 pub max_streams: u32,
374 pub active_streams: u32,
375 pub mac_address_and_padding: [u8; 12],
377 pub max_conferences: u32,
378 pub active_conferences: u32,
379 pub ipv4_address_scope: u32,
381 pub max_lines: u32,
382 pub ipv6_address_scope: u32,
384}
385
386#[derive(Clone, Debug, Eq, PartialEq)]
387pub struct MediaCapability {
389 pub codec: Codec,
390 pub max_frames_per_packet: u32,
391 pub codec_parameters: [u8; 8],
393}
394
395#[derive(Clone, Eq, PartialEq)]
397pub struct MediaEncryption {
398 pub algorithm: EncryptionMethod,
399 key: [u8; 16],
400 key_length: u8,
401 salt: [u8; 16],
402 salt_length: u8,
403 pub mki_present: u32,
405 pub key_derivation_rate: u32,
407}
408
409impl MediaEncryption {
410 pub fn new(
414 algorithm: EncryptionMethod,
415 key: &[u8],
416 salt: &[u8],
417 mki_present: u32,
418 key_derivation_rate: u32,
419 ) -> Result<Self, CodecError> {
420 if key.len() > 16 {
421 return Err(CodecError::SecretTooLong {
422 field: "media encryption key",
423 actual: key.len(),
424 maximum: 16,
425 });
426 }
427 if salt.len() > 16 {
428 return Err(CodecError::SecretTooLong {
429 field: "media encryption salt",
430 actual: salt.len(),
431 maximum: 16,
432 });
433 }
434 let mut wire_key = [0; 16];
435 wire_key[..key.len()].copy_from_slice(key);
436 let mut wire_salt = [0; 16];
437 wire_salt[..salt.len()].copy_from_slice(salt);
438 Ok(Self {
439 algorithm,
440 key: wire_key,
441 key_length: key.len() as u8,
442 salt: wire_salt,
443 salt_length: salt.len() as u8,
444 mki_present,
445 key_derivation_rate,
446 })
447 }
448
449 pub(crate) const fn from_wire(
450 algorithm: EncryptionMethod,
451 key: [u8; 16],
452 key_length: u8,
453 salt: [u8; 16],
454 salt_length: u8,
455 mki_present: u32,
456 key_derivation_rate: u32,
457 ) -> Self {
458 Self {
459 algorithm,
460 key,
461 key_length,
462 salt,
463 salt_length,
464 mki_present,
465 key_derivation_rate,
466 }
467 }
468
469 pub fn key(&self) -> &[u8] {
470 &self.key[..usize::from(self.key_length)]
471 }
472
473 pub fn salt(&self) -> &[u8] {
474 &self.salt[..usize::from(self.salt_length)]
475 }
476}
477
478impl fmt::Debug for MediaEncryption {
479 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
480 formatter
481 .debug_struct("MediaEncryption")
482 .field("algorithm", &self.algorithm)
483 .field("key", &"<redacted>")
484 .field("key_len", &self.key_length)
485 .field("salt", &"<redacted>")
486 .field("salt_len", &self.salt_length)
487 .field("mki_present", &self.mki_present)
488 .field("key_derivation_rate", &self.key_derivation_rate)
489 .finish()
490 }
491}
492
493impl Drop for MediaEncryption {
494 fn drop(&mut self) {
495 self.key.fill(0);
496 self.salt.fill(0);
497 }
498}
499
500#[derive(Clone, Copy, Debug, Eq, PartialEq)]
502pub struct AnnouncementEntry {
503 pub locale: u32,
504 pub country: u32,
505 pub tone: Tone,
506}
507
508#[derive(Clone, Debug, Eq, PartialEq)]
510pub struct CreateConferenceRequest {
511 pub conference_id: ConferenceId,
512 pub reserved_participants: u32,
513 pub resource_type: ConferenceResourceType,
514 pub application_id: ApplicationId,
515 pub application_conference_id: String,
516 pub application_data: String,
517 pub passthrough_data: Vec<u8>,
518}
519
520#[derive(Clone, Debug, Eq, PartialEq)]
521pub struct CreateConferenceResponse {
523 pub conference_id: ConferenceId,
524 pub result: CreateConferenceResult,
525 pub passthrough_data: Vec<u8>,
526}
527
528#[derive(Clone, Debug, Eq, PartialEq)]
530pub struct ModifyConferenceRequest {
531 pub conference_id: ConferenceId,
532 pub reserved_participants: u32,
533 pub application_id: ApplicationId,
534 pub application_conference_id: String,
535 pub application_data: String,
536 pub passthrough_data: Vec<u8>,
537}
538
539#[derive(Clone, Debug, Eq, PartialEq)]
540pub struct ModifyConferenceResponse {
542 pub conference_id: ConferenceId,
543 pub result: ModifyConferenceResult,
544 pub passthrough_data: Vec<u8>,
545}
546
547#[derive(Clone, Debug, Eq, PartialEq)]
548pub struct AuditConferenceEntry {
550 pub conference_id: ConferenceId,
551 pub resource_type: ConferenceResourceType,
552 pub reserved_participants: u32,
553 pub active_participants: u32,
554 pub application_id: ApplicationId,
555 pub application_conference_id: String,
556 pub application_data: String,
557}
558
559#[derive(Clone, Debug, Eq, PartialEq)]
560pub struct AuditConferenceResponse {
562 pub last: u32,
564 pub entries: Vec<AuditConferenceEntry>,
565}
566
567#[derive(Clone, Debug, Eq, PartialEq)]
568pub struct ConferenceParticipant {
570 pub call_reference: CallReference,
571 pub presentation_restrictions: PartyInformationRestrictions,
572 pub name: String,
573 pub number: String,
574 pub conference_name: String,
575}
576
577#[derive(Clone, Debug, Eq, PartialEq)]
578pub struct AddParticipantRequest {
580 pub conference_id: ConferenceId,
581 pub participant: ConferenceParticipant,
582}
583
584#[derive(Clone, Debug, Eq, PartialEq)]
589pub struct ChangeParticipantRequest {
590 pub conference_id: ConferenceId,
591 pub participant: ConferenceParticipant,
592}
593
594#[derive(Clone, Debug, Eq, PartialEq)]
595pub struct AddParticipantResponse {
597 pub conference_id: ConferenceId,
598 pub call_reference: CallReference,
599 pub result: AddParticipantResult,
600 pub bridge_participant_id: BoundedBytes<257>,
602}
603
604#[derive(Clone, Debug, Eq, PartialEq)]
607pub struct AuditParticipantResponse {
608 pub result: AuditParticipantResult,
609 pub last: u32,
610 pub conference_id: ConferenceId,
611 pub number_of_entries: u32,
613 pub participant_entries: Vec<u8>,
615}
616
617#[derive(Clone, Copy, Debug, Eq, PartialEq)]
620pub struct ParticipantChangeRouting {
621 pub application_id: ApplicationId,
622 pub line_instance: u32,
623 pub transaction_id: TransactionId,
624 pub sequence_flag: u32,
625 pub display_priority: u32,
626 pub application_instance_id: ApplicationId,
627 pub routing: u32,
628}
629
630#[derive(Clone, Debug, Eq, PartialEq)]
631pub struct ConferenceParticipantChange {
633 pub conference_id: ConferenceId,
634 pub participant: ConferenceParticipant,
635}
636
637#[derive(Clone, Debug, Eq, PartialEq)]
638pub struct MulticastMediaReception {
640 pub conference_id: ConferenceId,
641 pub passthrough_party_id: crate::types::PassthroughPartyId,
642 pub call_reference: CallReference,
643 pub address: IpAddr,
644 pub port: u16,
645 pub packet_millis: u32,
646 pub codec: Codec,
647 pub echo_cancellation: EchoCancellation,
648 pub g723_bitrate: G723BitRate,
649}
650
651#[derive(Clone, Debug, Eq, PartialEq)]
652pub struct MulticastMediaTransmission {
654 pub conference_id: ConferenceId,
655 pub passthrough_party_id: crate::types::PassthroughPartyId,
656 pub call_reference: CallReference,
657 pub address: IpAddr,
658 pub port: u16,
659 pub packet_millis: u32,
660 pub codec: Codec,
661 pub precedence: u32,
662 pub silence_suppression: u32,
663 pub max_frames_per_packet: u32,
664 pub g723_bitrate: G723BitRate,
665}
666
667#[derive(Clone, Debug, Eq, PartialEq)]
668pub struct KnownOpaqueMessage {
670 pub id: MessageId,
671 pub protocol_version: u32,
672 pub payload: BoundedBytes<MAX_OPAQUE_MESSAGE_BYTES>,
673}
674
675#[derive(Clone, Debug, Eq, PartialEq)]
676pub struct UserDataMessage {
678 pub application_id: u32,
679 pub line_instance: u32,
680 pub call_reference: u32,
681 pub transaction_id: u32,
682 pub data: Vec<u8>,
683}
684
685#[derive(Clone, Debug, Eq, PartialEq)]
687pub struct UserDataV1Message {
688 pub application_id: u32,
689 pub line_instance: u32,
690 pub call_reference: u32,
691 pub transaction_id: u32,
692 pub sequence_flag: u32,
693 pub display_priority: u32,
694 pub conference_id: u32,
695 pub application_instance_id: u32,
696 pub routing: u32,
697 pub data: Vec<u8>,
698}
699
700#[derive(Clone, Debug, Eq, PartialEq)]
701pub struct RegisterTokenMessage {
703 pub device_id: DeviceId,
704 pub device_instance: u32,
705 pub address: IpAddr,
706 pub device_type: DeviceType,
707 pub flags: u32,
709}
710
711pub const MAX_SIGNALING_SERVERS: usize = 5;
713
714#[derive(Clone, Debug, Eq, PartialEq)]
716pub struct SignalingServerEndpoint {
717 pub name: String,
718 pub address: IpAddr,
719 pub port: NonZeroU16,
720}
721
722#[derive(Clone, Debug, Eq, PartialEq)]
723pub struct MediaResourceNotification {
725 pub device_type: DeviceType,
726 pub in_service_streams: u32,
727 pub max_streams_per_conference: u32,
728 pub out_of_service_streams: u32,
729}
730
731#[derive(Clone, Debug, Eq, PartialEq)]
732pub struct SubscriptionRequest {
734 pub transaction_id: u32,
735 pub feature_id: u32,
736 pub timer_seconds: u32,
737 pub subscription_id: String,
738}
739
740#[derive(Clone, Debug, Eq, PartialEq)]
741pub struct PortEndpoint {
743 pub conference_id: u32,
744 pub call_reference: u32,
745 pub passthrough_party_id: u32,
746 pub address: IpAddr,
747 pub rtp_port: u16,
748 pub rtcp_port: u16,
749 pub media_type: Option<MediaType>,
750}
751
752#[derive(Clone, Copy, Debug, Eq, PartialEq)]
753pub struct PortRequest {
755 pub conference_id: ConferenceId,
756 pub call_reference: CallReference,
757 pub passthrough_party_id: crate::types::PassthroughPartyId,
758 pub transport: MediaTransport,
759 pub address_type: Option<IpAddressType>,
760 pub media_type: Option<MediaType>,
761}
762
763#[derive(Clone, Copy, Debug, Eq, PartialEq)]
764pub struct PortClose {
766 pub conference_id: ConferenceId,
767 pub call_reference: CallReference,
768 pub passthrough_party_id: crate::types::PassthroughPartyId,
769 pub media_type: Option<MediaType>,
770}
771
772#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
774pub struct QosFlow {
775 pub conference_id: ConferenceId,
776 pub call_reference: CallReference,
777 pub passthrough_party_id: crate::types::PassthroughPartyId,
778 pub address: Ipv4Addr,
779 pub port: u16,
780}
781
782#[derive(Clone, Copy, Debug, Eq, PartialEq)]
787pub struct QosTrafficSpecification {
788 pub codec: Codec,
789 pub average_bit_rate: u32,
790 pub burst_size: u32,
791 pub peak_rate: u32,
792}
793
794#[derive(Clone, Debug, Eq, PartialEq)]
796pub struct QosApplicationIdentifier {
797 pub vendor_id: String,
798 pub version: String,
799 pub application_name: String,
800 pub sub_application_id: String,
801}
802
803#[derive(Clone, Copy, Debug, Eq, PartialEq)]
804pub struct MessageWaitingCounts {
806 pub new: u32,
807 pub old: u32,
808}
809
810#[derive(Clone, Debug, Eq, PartialEq)]
811pub struct MessageWaitingNotification {
813 pub target_number: String,
814 pub control_number: String,
815 pub messages_waiting: bool,
816 pub total_voicemail: MessageWaitingCounts,
817 pub priority_voicemail: MessageWaitingCounts,
818 pub total_fax: MessageWaitingCounts,
819 pub priority_fax: MessageWaitingCounts,
820}
821
822#[derive(Clone, Copy, Debug, Eq, PartialEq)]
823pub struct OpenMultimediaReceiveChannelAck {
825 pub status: MediaStatus,
826 pub endpoint: MediaEndpointAddress,
827 pub passthrough_party_id: crate::types::PassthroughPartyId,
828 pub call_reference: CallReference,
829}
830
831#[derive(Clone, Copy, Debug, Eq, PartialEq)]
832pub struct StartMultimediaTransmissionAck {
834 pub conference_id: ConferenceId,
835 pub passthrough_party_id: crate::types::PassthroughPartyId,
836 pub call_reference: CallReference,
837 pub endpoint: MediaEndpointAddress,
838 pub status: MediaStatus,
839}
840
841#[derive(Clone, Copy, Debug, Eq, PartialEq)]
842pub struct MediaEndpointAddress {
844 pub address: IpAddr,
845 pub port: u16,
846}
847
848#[derive(Clone, Copy, Debug, Eq, PartialEq)]
849pub struct MultimediaStreamControl {
851 pub conference_id: ConferenceId,
852 pub passthrough_party_id: crate::types::PassthroughPartyId,
853 pub call_reference: CallReference,
854 pub port_handling_flag: u32,
855}
856
857#[derive(Clone, Copy, Debug, Eq, PartialEq)]
858pub struct AudioStreamControl {
860 pub conference_id: ConferenceId,
861 pub passthrough_party_id: crate::types::PassthroughPartyId,
862 pub call_reference: CallReference,
863 pub port_handling_flag: u32,
864}
865
866#[derive(Clone, Copy, Debug, Eq, PartialEq)]
867pub struct SessionTransmission {
869 pub remote_address: IpAddr,
870 pub session_type: u32,
871}
872
873#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
875pub struct RtpPayloadNumber(u8);
876
877impl RtpPayloadNumber {
878 pub const MAX: u32 = 127;
879
880 pub const fn new(value: u32) -> Result<Self, RtpPayloadNumberError> {
881 if value <= Self::MAX {
882 Ok(Self(value as u8))
883 } else {
884 Err(RtpPayloadNumberError { actual: value })
885 }
886 }
887
888 pub const fn get(self) -> u8 {
889 self.0
890 }
891}
892
893impl TryFrom<u32> for RtpPayloadNumber {
894 type Error = RtpPayloadNumberError;
895
896 fn try_from(value: u32) -> Result<Self, Self::Error> {
897 Self::new(value)
898 }
899}
900
901impl From<RtpPayloadNumber> for u32 {
902 fn from(value: RtpPayloadNumber) -> Self {
903 u32::from(value.get())
904 }
905}
906
907#[derive(Clone, Copy, Debug, Eq, PartialEq)]
909pub struct RtpPayloadNumberError {
910 pub actual: u32,
911}
912
913impl fmt::Display for RtpPayloadNumberError {
914 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
915 write!(
916 formatter,
917 "RTP payload number {} exceeds {}",
918 self.actual,
919 RtpPayloadNumber::MAX
920 )
921 }
922}
923
924impl std::error::Error for RtpPayloadNumberError {}
925
926#[derive(Clone, Copy, Debug, Eq, PartialEq)]
928pub struct MultimediaPayloadDescriptor {
929 rfc_number: u32,
930 payload_number: RtpPayloadNumber,
931}
932
933impl MultimediaPayloadDescriptor {
934 pub const fn new(rfc_number: u32, payload_number: RtpPayloadNumber) -> Self {
936 Self {
937 rfc_number,
938 payload_number,
939 }
940 }
941
942 pub const fn rfc_number(self) -> u32 {
944 self.rfc_number
945 }
946
947 pub const fn payload_number(self) -> RtpPayloadNumber {
948 self.payload_number
949 }
950}
951
952#[derive(Clone, Copy, Debug, Eq, PartialEq)]
954pub struct MultimediaPictureFormat {
955 pub format: VideoFormat,
956 pub minimum_picture_interval: u32,
957}
958
959#[derive(Clone, Copy, Debug, Eq, PartialEq)]
961pub enum MultimediaVideoCapabilityArm {
962 H261 {
963 temporal_spatial_trade_off_capability: u32,
964 still_image_transmission: u32,
965 },
966 H263 {
967 capability_bitfield: u32,
968 annex_n_and_w_future_use: u32,
969 },
970 H263Plus {
971 model_number: u32,
972 bandwidth: u32,
973 },
974 H264 {
975 profile: u32,
976 level: u32,
977 custom_max_mbps: u32,
978 custom_max_fs: u32,
979 custom_max_dpb: u32,
980 custom_max_br_and_cpb: u32,
981 },
982}
983
984impl MultimediaVideoCapabilityArm {
985 pub const fn codec(self) -> Codec {
986 match self {
987 Self::H261 { .. } => Codec::H261,
988 Self::H263 { .. } => Codec::H263,
989 Self::H263Plus { .. } => Codec::H263Plus,
990 Self::H264 { .. } => Codec::H264,
991 }
992 }
993}
994
995#[derive(Clone)]
997pub struct MultimediaVideoCapability {
998 bit_rate: u32,
999 picture_formats: Box<[MultimediaPictureFormat]>,
1000 conference_service_number: u32,
1001 arm: MultimediaVideoCapabilityArm,
1002 preserved_wire: Option<[u8; MULTIMEDIA_CAPABILITY_BYTES]>,
1003}
1004
1005impl MultimediaVideoCapability {
1006 pub fn new(
1008 bit_rate: u32,
1009 picture_formats: impl IntoIterator<Item = MultimediaPictureFormat>,
1010 conference_service_number: u32,
1011 arm: MultimediaVideoCapabilityArm,
1012 ) -> Result<Self, MultimediaCapabilityError> {
1013 let picture_formats = picture_formats.into_iter().collect::<Box<[_]>>();
1014 if picture_formats.len() > MAX_MULTIMEDIA_PICTURE_FORMATS {
1015 return Err(MultimediaCapabilityError {
1016 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
1017 actual: picture_formats.len(),
1018 });
1019 }
1020 Ok(Self {
1021 bit_rate,
1022 picture_formats,
1023 conference_service_number,
1024 arm,
1025 preserved_wire: None,
1026 })
1027 }
1028
1029 pub const fn bit_rate(&self) -> u32 {
1030 self.bit_rate
1031 }
1032
1033 pub fn picture_formats(&self) -> &[MultimediaPictureFormat] {
1034 &self.picture_formats
1035 }
1036
1037 pub const fn conference_service_number(&self) -> u32 {
1038 self.conference_service_number
1039 }
1040
1041 pub const fn arm(&self) -> MultimediaVideoCapabilityArm {
1042 self.arm
1043 }
1044
1045 pub const fn codec(&self) -> Codec {
1046 self.arm.codec()
1047 }
1048}
1049
1050impl fmt::Debug for MultimediaVideoCapability {
1051 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1052 formatter
1053 .debug_struct("MultimediaVideoCapability")
1054 .field("bit_rate", &self.bit_rate)
1055 .field("picture_formats", &self.picture_formats)
1056 .field("conference_service_number", &self.conference_service_number)
1057 .field("arm", &self.arm)
1058 .finish()
1059 }
1060}
1061
1062impl PartialEq for MultimediaVideoCapability {
1063 fn eq(&self, other: &Self) -> bool {
1064 self.bit_rate == other.bit_rate
1065 && self.picture_formats == other.picture_formats
1066 && self.conference_service_number == other.conference_service_number
1067 && self.arm == other.arm
1068 && self.preserved_wire == other.preserved_wire
1069 }
1070}
1071
1072impl Eq for MultimediaVideoCapability {}
1073
1074#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1075pub struct MultimediaCapabilityError {
1077 pub maximum: usize,
1078 pub actual: usize,
1079}
1080
1081impl fmt::Display for MultimediaCapabilityError {
1082 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1083 write!(
1084 formatter,
1085 "video capability contains {} picture formats, exceeding the maximum of {}",
1086 self.actual, self.maximum
1087 )
1088 }
1089}
1090
1091impl std::error::Error for MultimediaCapabilityError {}
1092
1093#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1094pub(crate) enum MultimediaPayloadDirection {
1095 Receive,
1096 Transmit,
1097}
1098
1099#[derive(Clone, Eq, PartialEq)]
1100enum MultimediaCapabilityState {
1101 Video(MultimediaVideoCapability),
1102 Preserved([u8; MULTIMEDIA_CAPABILITY_BYTES]),
1103}
1104
1105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1106enum MultimediaPayloadOrigin {
1107 Constructed,
1108 Decoded {
1109 direction: MultimediaPayloadDirection,
1110 protocol: ProtocolVersion,
1111 compression_codec: Codec,
1112 },
1113}
1114
1115#[derive(Clone)]
1117pub struct MultimediaPayload {
1118 descriptor: MultimediaPayloadDescriptor,
1119 capability: MultimediaCapabilityState,
1120 origin: MultimediaPayloadOrigin,
1121}
1122
1123impl MultimediaPayload {
1124 pub fn new(payload_number: RtpPayloadNumber, capability: MultimediaVideoCapability) -> Self {
1126 Self::with_descriptor(
1127 MultimediaPayloadDescriptor::new(0, payload_number),
1128 capability,
1129 )
1130 }
1131
1132 pub fn with_descriptor(
1134 descriptor: MultimediaPayloadDescriptor,
1135 capability: MultimediaVideoCapability,
1136 ) -> Self {
1137 Self {
1138 descriptor,
1139 capability: MultimediaCapabilityState::Video(capability),
1140 origin: MultimediaPayloadOrigin::Constructed,
1141 }
1142 }
1143
1144 const fn from_decoded(
1145 descriptor: MultimediaPayloadDescriptor,
1146 capability: MultimediaCapabilityState,
1147 direction: MultimediaPayloadDirection,
1148 protocol: ProtocolVersion,
1149 compression_codec: Codec,
1150 ) -> Self {
1151 Self {
1152 descriptor,
1153 capability,
1154 origin: MultimediaPayloadOrigin::Decoded {
1155 direction,
1156 protocol,
1157 compression_codec,
1158 },
1159 }
1160 }
1161
1162 #[cfg(test)]
1163 pub(crate) fn from_wire(
1164 rfc_number: u32,
1165 payload_number: RtpPayloadNumber,
1166 capability: [u8; MULTIMEDIA_CAPABILITY_BYTES],
1167 codec: Codec,
1168 direction: MultimediaPayloadDirection,
1169 protocol: ProtocolVersion,
1170 ) -> Self {
1171 Self::from_decoded(
1172 MultimediaPayloadDescriptor::new(rfc_number, payload_number),
1173 MultimediaCapabilityState::Preserved(capability),
1174 direction,
1175 protocol,
1176 codec,
1177 )
1178 }
1179
1180 pub const fn descriptor(&self) -> MultimediaPayloadDescriptor {
1181 self.descriptor
1182 }
1183
1184 pub const fn codec(&self) -> Codec {
1185 self.compression_codec()
1186 }
1187
1188 pub const fn payload_number(&self) -> RtpPayloadNumber {
1189 self.descriptor.payload_number()
1190 }
1191
1192 pub const fn video_capability(&self) -> Option<&MultimediaVideoCapability> {
1194 match &self.capability {
1195 MultimediaCapabilityState::Video(capability) => Some(capability),
1196 MultimediaCapabilityState::Preserved(_) => None,
1197 }
1198 }
1199
1200 pub(crate) fn is_valid_for(
1201 &self,
1202 direction: MultimediaPayloadDirection,
1203 protocol: ProtocolVersion,
1204 ) -> bool {
1205 match self.origin {
1206 MultimediaPayloadOrigin::Constructed => true,
1207 MultimediaPayloadOrigin::Decoded {
1208 direction: decoded_direction,
1209 protocol: decoded_protocol,
1210 ..
1211 } => decoded_direction == direction && decoded_protocol.wire() == protocol.wire(),
1212 }
1213 }
1214
1215 pub(crate) fn is_direction(&self, direction: MultimediaPayloadDirection) -> bool {
1216 match self.origin {
1217 MultimediaPayloadOrigin::Constructed => true,
1218 MultimediaPayloadOrigin::Decoded {
1219 direction: decoded_direction,
1220 ..
1221 } => decoded_direction == direction,
1222 }
1223 }
1224
1225 pub(crate) const fn compression_codec(&self) -> Codec {
1226 match self.origin {
1227 MultimediaPayloadOrigin::Constructed => match &self.capability {
1228 MultimediaCapabilityState::Video(capability) => capability.codec(),
1229 MultimediaCapabilityState::Preserved(_) => unreachable!(),
1230 },
1231 MultimediaPayloadOrigin::Decoded {
1232 compression_codec, ..
1233 } => compression_codec,
1234 }
1235 }
1236}
1237
1238impl PartialEq for MultimediaPayload {
1239 fn eq(&self, other: &Self) -> bool {
1240 self.descriptor == other.descriptor
1241 && self.capability == other.capability
1242 && self.origin == other.origin
1243 }
1244}
1245
1246impl Eq for MultimediaPayload {}
1247
1248impl fmt::Debug for MultimediaPayload {
1249 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1250 formatter
1251 .debug_struct("MultimediaPayload")
1252 .field("descriptor", &self.descriptor)
1253 .field("codec", &self.codec())
1254 .field("video_capability", &self.video_capability())
1255 .finish()
1256 }
1257}
1258
1259#[derive(Clone, Debug, Eq, PartialEq)]
1260pub struct OpenMultimediaChannel {
1262 pub conference_id: ConferenceId,
1263 pub passthrough_party_id: crate::types::PassthroughPartyId,
1264 pub line_instance: u32,
1265 pub call_reference: CallReference,
1266 pub payload: MultimediaPayload,
1267 pub conference_creator: bool,
1268 pub encryption: Option<MediaEncryption>,
1270 pub stream_passthrough_id: u32,
1272 pub associated_stream_id: u32,
1274 pub source: MediaEndpointAddress,
1275 pub requested_address_type: IpAddressType,
1276}
1277
1278#[derive(Clone, Debug, Eq, PartialEq)]
1279pub struct StartMultimediaTransmission {
1281 pub conference_id: ConferenceId,
1282 pub passthrough_party_id: crate::types::PassthroughPartyId,
1283 pub endpoint: MediaEndpointAddress,
1284 pub call_reference: CallReference,
1285 pub payload: MultimediaPayload,
1286 pub traffic_class: crate::types::MediaTrafficClass,
1287 pub encryption: Option<MediaEncryption>,
1289 pub stream_passthrough_id: u32,
1291 pub associated_stream_id: u32,
1293}
1294
1295#[derive(Clone, Debug, Eq, PartialEq)]
1296pub struct MiscellaneousCommand {
1298 pub conference_id: ConferenceId,
1299 pub passthrough_party_id: crate::types::PassthroughPartyId,
1300 pub call_reference: CallReference,
1301 pub command: values::MiscCommandType,
1302 pub data: BoundedBytes<36>,
1304}
1305
1306#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1307pub struct VideoFlowControl {
1309 pub conference_id: ConferenceId,
1310 pub passthrough_party_id: crate::types::PassthroughPartyId,
1311 pub call_reference: CallReference,
1312 pub maximum_bit_rate: u32,
1313}
1314
1315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1316pub struct DtmfToneControl {
1318 pub tone: Tone,
1319 pub conference_id: ConferenceId,
1320 pub passthrough_party_id: u32,
1321}
1322
1323#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1324pub struct DtmfPayloadIdentity {
1326 pub payload_type: u32,
1328 pub conference_id: u32,
1329 pub passthrough_party_id: u32,
1330}
1331
1332#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1333pub struct DtmfPayloadRequest {
1335 pub payload_type: u32,
1337 pub conference_id: u32,
1338 pub passthrough_party_id: u32,
1339 pub dtmf_type: u32,
1341}
1342
1343pub const XML_ALARM_MAX_WIRE_BYTES: usize = 2_048;
1345pub const XML_ALARM_CANONICAL_WIRE_BYTES: usize = 2_004;
1347pub const XML_ALARM_CANONICAL_DOCUMENT_BYTES: usize = 2_000;
1349
1350#[derive(Clone, Debug, Eq, PartialEq)]
1351pub struct XmlAlarmMessage {
1356 wire_payload: BoundedBytes<XML_ALARM_MAX_WIRE_BYTES>,
1357}
1358
1359impl XmlAlarmMessage {
1360 pub fn from_xml(xml: impl AsRef<[u8]>) -> Result<Self, CodecError> {
1362 let xml = xml.as_ref();
1363 if xml.contains(&0) {
1364 return Err(CodecError::InvalidText);
1365 }
1366 if xml.len() > XML_ALARM_CANONICAL_DOCUMENT_BYTES {
1367 return Err(CodecError::TextTooLong {
1368 message_id: id::XML_ALARM,
1369 field: "alarm XML",
1370 actual: xml.len(),
1371 maximum: XML_ALARM_CANONICAL_DOCUMENT_BYTES,
1372 });
1373 }
1374 let mut wire_payload = vec![0; XML_ALARM_CANONICAL_WIRE_BYTES];
1375 wire_payload[..xml.len()].copy_from_slice(xml);
1376 Self::from_wire_payload(wire_payload)
1377 }
1378
1379 pub fn from_wire_payload(payload: impl Into<Box<[u8]>>) -> Result<Self, CodecError> {
1381 let payload = payload.into();
1382 let wire_payload =
1383 BoundedBytes::new(payload).map_err(|error| CodecError::CountTooLarge {
1384 message_id: id::XML_ALARM,
1385 field: "alarm payload",
1386 count: error.actual,
1387 maximum: error.maximum,
1388 })?;
1389 Ok(Self { wire_payload })
1390 }
1391
1392 pub fn xml_bytes(&self) -> &[u8] {
1394 let bytes = self.wire_payload.as_bytes();
1395 let end = bytes
1396 .iter()
1397 .position(|byte| *byte == 0)
1398 .unwrap_or(bytes.len());
1399 &bytes[..end]
1400 }
1401
1402 pub fn wire_payload(&self) -> &[u8] {
1404 self.wire_payload.as_bytes()
1405 }
1406}
1407
1408#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1414pub struct MediaFailureDetection {
1415 pub conference_id: ConferenceId,
1416 pub passthrough_party_id: u32,
1417 pub packet_millis: u32,
1418 pub codec: Codec,
1419 pub echo_cancellation: EchoCancellation,
1420 pub codec_qualifier: [u8; 4],
1421 pub call_reference: CallReference,
1422}
1423
1424#[derive(Clone, Debug, Eq, PartialEq)]
1427pub struct ExtensionDeviceCapabilities {
1428 pub unknown_1: u32,
1429 pub unknown_2: u32,
1430 pub unknown_3: u32,
1431 pub description: String,
1432}
1433
1434#[derive(Clone, Debug, Eq, PartialEq)]
1435pub struct ConfigurationStatus {
1437 pub device_name: String,
1438 pub station_user_id: u32,
1439 pub station_instance: u32,
1440 pub line_count: u32,
1441 pub speed_dial_count: u32,
1442 pub user_name: String,
1443 pub server_name: String,
1444}
1445
1446#[derive(Clone, Debug, Eq, PartialEq)]
1451pub enum ControlMessage {
1452 MediaResourceNotification(MediaResourceNotification),
1453 PortResponse(PortEndpoint),
1454 StartSessionTransmission(SessionTransmission),
1455 StopSessionTransmission(SessionTransmission),
1456 ClearConference {
1457 conference_id: ConferenceId,
1458 service_number: u32,
1459 },
1460 CreateConferenceRequest(CreateConferenceRequest),
1461 DeleteConferenceRequest {
1462 conference_id: ConferenceId,
1463 },
1464 ModifyConferenceRequest(ModifyConferenceRequest),
1465 AddParticipantRequest(AddParticipantRequest),
1466 DropParticipantRequest {
1467 conference_id: ConferenceId,
1468 call_reference: CallReference,
1469 },
1470 AuditConferenceRequest,
1471 AuditParticipantRequest {
1472 conference_id: ConferenceId,
1473 },
1474 ChangeParticipantRequest(ChangeParticipantRequest),
1475 CreateConferenceResponse(CreateConferenceResponse),
1476 DeleteConferenceResponse {
1477 conference_id: ConferenceId,
1478 result: DeleteConferenceResult,
1479 },
1480 ModifyConferenceResponse(ModifyConferenceResponse),
1481 AddParticipantResponse(AddParticipantResponse),
1482 AuditConferenceResponse(AuditConferenceResponse),
1483 AuditParticipantResponse(AuditParticipantResponse),
1484 StartAnnouncement {
1486 announcements: Vec<AnnouncementEntry>,
1487 end_of_ack: EndOfAnnouncementAck,
1489 conference_id: u32,
1490 matrix_conference_party_ids: Vec<u32>,
1492 hearing_conference_party_mask: u32,
1494 play_mode: AnnouncementPlayMode,
1495 },
1496 StopAnnouncement {
1497 conference_id: u32,
1498 },
1499 AnnouncementFinish {
1500 conference_id: u32,
1501 play_status: AnnouncementPlayStatus,
1502 },
1503 QosReservationNotify {
1504 flow: QosFlow,
1505 direction: QosDirection,
1506 },
1507 QosErrorNotify {
1509 flow: QosFlow,
1510 direction: QosDirection,
1511 error_code: QosErrorCode,
1512 failure_node: Ipv4Addr,
1514 rsvp_error_code: RsvpErrorCode,
1515 rsvp_error_subcode: u32,
1516 rsvp_error_flags: u32,
1517 },
1518 QosListen {
1520 flow: QosFlow,
1521 reservation_style: QosReservationStyle,
1522 maximum_retries: u32,
1523 retry_timer: u32,
1524 confirmation_required: bool,
1526 preemption_priority: u32,
1528 defending_priority: u32,
1530 traffic: QosTrafficSpecification,
1531 application: QosApplicationIdentifier,
1532 },
1533 QosPath {
1535 flow: QosFlow,
1536 reservation_style: QosReservationStyle,
1537 maximum_retries: u32,
1538 retry_timer: u32,
1539 preemption_priority: u32,
1540 defending_priority: u32,
1541 traffic: QosTrafficSpecification,
1542 application: QosApplicationIdentifier,
1543 },
1544 QosTeardown {
1546 flow: QosFlow,
1547 direction: QosDirection,
1548 },
1549 UpdateDscp {
1551 flow: QosFlow,
1552 dscp: u8,
1553 },
1554 QosModify {
1556 flow: QosFlow,
1557 direction: QosDirection,
1558 traffic: QosTrafficSpecification,
1559 application: QosApplicationIdentifier,
1560 },
1561 MessageWaitingNotification(MessageWaitingNotification),
1562 MessageWaitingResponse {
1563 target_number: String,
1564 result: MessageWaitingResult,
1565 },
1566 KnownOpaque(KnownOpaqueMessage),
1568}
1569
1570pub const CONNECTION_QUALITY_MAX_BYTES: usize = 600;
1572
1573#[derive(Clone, Eq, PartialEq)]
1578pub struct ConnectionQualityStatistics(Vec<u8>);
1579
1580impl ConnectionQualityStatistics {
1581 pub fn new(bytes: impl Into<Vec<u8>>) -> Result<Self, CodecError> {
1583 let bytes = bytes.into();
1584 if bytes.len() > CONNECTION_QUALITY_MAX_BYTES {
1585 return Err(CodecError::CountTooLarge {
1586 message_id: id::CONNECTION_STATISTICS_RES,
1587 field: "quality statistics",
1588 count: bytes.len(),
1589 maximum: CONNECTION_QUALITY_MAX_BYTES,
1590 });
1591 }
1592 Ok(Self(bytes))
1593 }
1594
1595 pub fn as_bytes(&self) -> &[u8] {
1596 &self.0
1597 }
1598}
1599
1600impl fmt::Debug for ConnectionQualityStatistics {
1601 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1602 formatter
1603 .debug_struct("ConnectionQualityStatistics")
1604 .field("byte_count", &self.0.len())
1605 .finish()
1606 }
1607}
1608
1609#[derive(Clone, Eq, PartialEq)]
1610pub struct ConnectionStatistics {
1614 pub directory_number: String,
1615 pub call_reference: u32,
1616 pub processing: StatisticsProcessing,
1617 pub packets_sent: u32,
1618 pub octets_sent: u32,
1619 pub packets_received: u32,
1620 pub octets_received: u32,
1621 pub packets_lost: u32,
1622 pub jitter_millis: u32,
1624 pub latency_millis: u32,
1626 pub quality: ConnectionQualityStatistics,
1627}
1628
1629impl fmt::Debug for ConnectionStatistics {
1630 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1631 formatter
1632 .debug_struct("ConnectionStatistics")
1633 .field("directory_number", &"<redacted>")
1634 .field("call_reference", &self.call_reference)
1635 .field("processing", &self.processing)
1636 .field("packets_sent", &self.packets_sent)
1637 .field("octets_sent", &self.octets_sent)
1638 .field("packets_received", &self.packets_received)
1639 .field("octets_received", &self.octets_received)
1640 .field("packets_lost", &self.packets_lost)
1641 .field("jitter_millis", &self.jitter_millis)
1642 .field("latency_millis", &self.latency_millis)
1643 .field("quality", &self.quality)
1644 .finish()
1645 }
1646}
1647
1648#[derive(Clone, Debug, Eq, PartialEq)]
1649pub struct MediaTransmissionAckWire {
1651 pub extension: Option<[u8; 8]>,
1653}
1654
1655#[derive(Clone, Debug, Eq, PartialEq)]
1656pub struct MediaTransmissionAck {
1658 pub conference_id: u32,
1659 pub passthrough_party_id: u32,
1660 pub call_reference: u32,
1661 pub status: MediaStatus,
1662 pub address: IpAddr,
1663 pub port: u16,
1664 pub wire: Option<MediaTransmissionAckWire>,
1666}
1667
1668#[derive(Clone, Debug, Eq, PartialEq)]
1671pub struct OpenReceiveChannelWire {
1672 pub conference_id: u32,
1673 pub g723_bitrate: u32,
1675 pub stream_passthrough_id: u32,
1677 pub associated_stream_id: u32,
1679 pub dtmf_type: u32,
1681 pub mixing_mode: u32,
1683 pub direction: u32,
1685 pub requested_address_type: u32,
1687 pub audio_level_adjustment: u32,
1689 pub latent_capabilities: [u8; 36],
1691}
1692
1693#[derive(Clone, Debug, Eq, PartialEq)]
1696pub struct StartMediaTransmissionWire {
1697 pub conference_id: u32,
1698 pub g723_bitrate: u32,
1700 pub stream_passthrough_id: u32,
1702 pub associated_stream_id: u32,
1704 pub dtmf_type: u32,
1706 pub mixing_mode: u32,
1708 pub direction: u32,
1710 pub latent_capabilities: [u8; 36],
1712}
1713
1714#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1718pub enum KeypadButtonWireLayout {
1719 LegacyButtonOnly,
1721 WithCallIdentity,
1723}
1724
1725#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1727pub struct ButtonTemplateEntry {
1728 pub instance: u32,
1729 pub button_type: ButtonType,
1730}
1731
1732#[derive(Clone, Debug, Eq, PartialEq)]
1733pub enum ClientMessage {
1741 KeepAlive,
1742 Register(RegistrationMessage),
1743 IpPort {
1744 rtp_port: u16,
1745 },
1746 KeypadButton {
1747 button: Digit,
1748 line_instance: u32,
1749 call_reference: u32,
1750 wire_layout: Option<KeypadButtonWireLayout>,
1751 },
1752 EnblocCall {
1753 called_party: String,
1754 line_instance: u32,
1755 },
1756 Stimulus {
1757 stimulus: Stimulus,
1758 instance: u32,
1759 call_reference: u32,
1760 status: u32,
1761 },
1762 OffHook {
1763 line_instance: u32,
1764 call_reference: u32,
1765 },
1766 OnHook {
1767 line_instance: u32,
1768 call_reference: u32,
1769 },
1770 OffHookWithCallingParty {
1771 calling_party_number: String,
1772 voice_mailbox: String,
1773 line_instance: u32,
1774 },
1775 LineStatRequest {
1776 line_instance: u32,
1777 },
1778 ConfigStatRequest,
1779 TimeDateRequest,
1780 ButtonTemplateRequest,
1781 VersionRequest,
1782 CapabilitiesResponse(Vec<MediaCapability>),
1783 CapabilitiesUpdate(CapabilityUpdate),
1784 OpenMultimediaReceiveChannelAck(OpenMultimediaReceiveChannelAck),
1785 ServerRequest,
1786 Alarm {
1787 severity: AlarmSeverity,
1788 text: String,
1789 parameters: Option<[u32; 2]>,
1792 },
1793 MulticastMediaReceptionAck {
1794 status: MediaStatus,
1795 passthrough_party_id: crate::types::PassthroughPartyId,
1796 call_reference: CallReference,
1797 },
1798 OpenReceiveChannelAck {
1799 status: MediaStatus,
1800 address: IpAddr,
1801 port: u16,
1802 passthrough_party_id: u32,
1803 call_reference: u32,
1804 },
1805 SoftKeySetRequest,
1806 SoftKeyTemplateRequest,
1807 SoftKeyEvent {
1808 event: u32,
1809 line_instance: u32,
1810 call_reference: u32,
1811 },
1812 Unregister {
1813 reason: u32,
1814 },
1815 RegisterToken(RegisterTokenMessage),
1816 HookFlash {
1817 line_instance: u32,
1818 call_reference: u32,
1819 },
1820 ForwardStatusRequest {
1821 line_instance: u32,
1822 },
1823 SpeedDialStatusRequest {
1824 speed_dial_instance: u32,
1825 },
1826 ConnectionStatisticsResponse(ConnectionStatistics),
1827 HeadsetStatus {
1828 enabled: bool,
1829 },
1830 MediaResourceNotification(MediaResourceNotification),
1831 MediaPathEvent {
1832 path: MediaPathId,
1833 event: MediaPathEvent,
1834 },
1835 MediaPathCapability {
1836 path: MediaPathId,
1837 capability: MediaPathCapability,
1838 },
1839 MediaTransmissionFailure {
1840 conference_id: u32,
1841 passthrough_party_id: u32,
1842 address: IpAddr,
1843 port: u16,
1844 call_reference: u32,
1845 status: MediaStatus,
1846 },
1847 RegisterAvailableLines {
1848 lines: u32,
1849 },
1850 ServiceUrlStatusRequest {
1851 index: u32,
1852 },
1853 FeatureStatusRequest {
1854 index: u32,
1855 capabilities: u32,
1857 },
1858 StartMediaTransmissionAck(MediaTransmissionAck),
1859 StartMultimediaTransmissionAck(StartMultimediaTransmissionAck),
1860 ExtensionDeviceCapabilities(ExtensionDeviceCapabilities),
1861 DeviceToUserData(UserDataMessage),
1862 DeviceToUserDataResponse(UserDataMessage),
1863 DeviceToUserDataV1(UserDataV1Message),
1864 DeviceToUserDataResponseV1(UserDataV1Message),
1865 PortResponse(PortEndpoint),
1866 SubscriptionStatusRequest(SubscriptionRequest),
1867 SubscribeDtmfPayloadResponse(DtmfPayloadIdentity),
1868 UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity),
1869 LocationInfo {
1870 xml: String,
1872 },
1873 XmlAlarm(XmlAlarmMessage),
1874 CallCountRequest {
1875 value: u32,
1877 },
1878 CreateConferenceResponse(CreateConferenceResponse),
1879 DeleteConferenceResponse {
1880 conference_id: ConferenceId,
1881 result: DeleteConferenceResult,
1882 },
1883 ModifyConferenceResponse(ModifyConferenceResponse),
1884 AuditConferenceResponse(AuditConferenceResponse),
1885 AddParticipantResponse(AddParticipantResponse),
1886 AuditParticipantResponse(AuditParticipantResponse),
1887 KnownOpaque(KnownOpaqueMessage),
1888 Unknown(RawMessage),
1889}
1890
1891#[derive(Clone, Debug, Eq, PartialEq)]
1892pub enum ServerMessage {
1899 RegisterAck {
1900 keepalive_seconds: u32,
1901 secondary_keepalive_seconds: u32,
1902 protocol: ProtocolVersion,
1903 features: PhoneFeatures,
1904 date_template: DateTemplate,
1905 },
1906 RegisterReject {
1907 reason: String,
1908 },
1909 KeepAliveAck,
1910 UnregisterAck,
1911 CapabilitiesRequest,
1912 ConfigStatus(ConfigurationStatus),
1913 LineStatus {
1914 instance: u32,
1915 number: String,
1916 display_name: String,
1917 },
1918 ButtonTemplate {
1919 buttons: Vec<ButtonTemplateEntry>,
1920 },
1921 Version {
1922 firmware: String,
1923 },
1924 ServerResponse {
1925 servers: Vec<SignalingServerEndpoint>,
1926 },
1927 TimeDate {
1928 year: u32,
1929 month: u32,
1930 weekday: u32,
1931 day: u32,
1932 hour: u32,
1933 minute: u32,
1934 second: u32,
1935 milliseconds: u32,
1936 unix_seconds: u32,
1937 },
1938 SoftKeyTemplate {
1939 actions: Vec<values::SoftKey>,
1940 },
1941 SoftKeySet {
1942 profile: SoftKeyProfile,
1943 },
1944 SelectSoftKeys {
1945 line_instance: u32,
1946 call_reference: u32,
1947 set: KeyMode,
1948 valid_mask: u32,
1950 },
1951 CallState {
1952 state: CallState,
1953 line_instance: u32,
1954 call_reference: u32,
1955 },
1956 CallInfo {
1957 info: CallInfo,
1958 line_instance: u32,
1959 call_reference: u32,
1960 },
1961 DisplayPrompt {
1962 timeout_seconds: u32,
1963 text: String,
1964 line_instance: u32,
1965 call_reference: u32,
1966 },
1967 ClearPrompt {
1968 line_instance: u32,
1969 call_reference: u32,
1970 },
1971 DisplayNotify {
1972 timeout_seconds: u32,
1973 text: String,
1974 },
1975 ClearNotify,
1976 DisplayPriorityNotify {
1977 timeout_seconds: u32,
1978 priority: NotificationPriority,
1979 text: String,
1980 },
1981 ClearPriorityNotify {
1982 priority: NotificationPriority,
1983 },
1984 NotifyDtmfTone(DtmfToneControl),
1985 SendDtmfTone(DtmfToneControl),
1986 StartAnnouncement {
1987 announcements: Vec<AnnouncementEntry>,
1988 end_of_ack: u32,
1989 conference_id: u32,
1990 matrix_conference_party_ids: Vec<u32>,
1991 hearing_conference_party_mask: u32,
1992 play_mode: u32,
1993 },
1994 StopAnnouncement {
1995 conference_id: u32,
1996 },
1997 AnnouncementFinish {
1998 conference_id: u32,
1999 play_status: u32,
2000 },
2001 ClearConference {
2002 conference_id: ConferenceId,
2003 service_number: u32,
2004 },
2005 CreateConferenceRequest(CreateConferenceRequest),
2006 DeleteConferenceRequest {
2007 conference_id: ConferenceId,
2008 },
2009 ModifyConferenceRequest(ModifyConferenceRequest),
2010 AuditConferenceRequest,
2011 AddParticipantRequest(AddParticipantRequest),
2012 DropParticipantRequest {
2013 conference_id: ConferenceId,
2014 call_reference: CallReference,
2015 },
2016 AuditParticipantRequest {
2017 conference_id: ConferenceId,
2018 },
2019 ChangeParticipantRequest(ChangeParticipantRequest),
2020 StopMultimediaTransmission(MultimediaStreamControl),
2021 FlowControlCommand(VideoFlowControl),
2022 CloseMultimediaReceiveChannel(MultimediaStreamControl),
2023 VideoDisplayCommand {
2024 conference_id: ConferenceId,
2025 call_reference: CallReference,
2026 layout_id: u32,
2027 },
2028 FlowControlNotify(VideoFlowControl),
2029 ActivateCallPlane {
2030 line_instance: u32,
2031 },
2032 DeactivateCallPlane,
2033 BackspaceResponse {
2034 line_instance: u32,
2035 call_reference: u32,
2036 },
2037 RegisterTokenAck,
2038 RegisterTokenReject {
2039 backoff_seconds: u32,
2040 },
2041 SetRinger {
2042 mode: RingerMode,
2043 duration: RingDuration,
2044 line_instance: u32,
2045 call_reference: u32,
2046 },
2047 SetLamp {
2048 stimulus: ButtonType,
2049 instance: u32,
2050 mode: LampMode,
2051 },
2052 StartTone {
2053 tone: Tone,
2054 direction: ToneDirection,
2055 line_instance: u32,
2056 call_reference: u32,
2057 },
2058 StopTone {
2059 line_instance: u32,
2060 call_reference: u32,
2061 },
2062 StartMulticastMediaReception(MulticastMediaReception),
2063 StartMulticastMediaTransmission(MulticastMediaTransmission),
2064 StopMulticastMediaReception {
2065 conference_id: ConferenceId,
2066 passthrough_party_id: crate::types::PassthroughPartyId,
2067 call_reference: CallReference,
2068 },
2069 StopMulticastMediaTransmission {
2070 conference_id: ConferenceId,
2071 passthrough_party_id: crate::types::PassthroughPartyId,
2072 call_reference: CallReference,
2073 },
2074 OpenReceiveChannel {
2075 call_reference: u32,
2076 passthrough_party_id: u32,
2077 packet_ms: u32,
2078 codec: Codec,
2079 echo_cancellation: EchoCancellation,
2080 telephone_event_payload: u8,
2082 source_address: IpAddr,
2083 source_port: u16,
2084 encryption: Option<MediaEncryption>,
2085 wire: Option<OpenReceiveChannelWire>,
2088 },
2089 CloseReceiveChannel(AudioStreamControl),
2090 ConnectionStatisticsRequest {
2091 directory_number: String,
2092 call_reference: u32,
2093 processing: StatisticsProcessing,
2094 },
2095 StartMediaTransmission {
2096 call_reference: u32,
2097 passthrough_party_id: u32,
2098 endpoint: MediaEndpoint,
2099 silence_suppression: SilenceSuppression,
2100 traffic_class: crate::types::MediaTrafficClass,
2102 encryption: Option<MediaEncryption>,
2103 wire: Option<StartMediaTransmissionWire>,
2106 },
2107 StopMediaTransmission(AudioStreamControl),
2108 SubscribeDtmfPayloadRequest(DtmfPayloadRequest),
2109 SubscribeDtmfPayloadError(DtmfPayloadIdentity),
2110 UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest),
2111 UnsubscribeDtmfPayloadError(DtmfPayloadIdentity),
2112 SetSpeakerMode(SpeakerMode),
2113 SetMicrophoneMode(MicrophoneMode),
2114 Reset(ResetType),
2115 DisplayText {
2116 text: String,
2117 },
2118 ClearDisplay,
2119 ForwardStatus {
2120 line_instance: u32,
2121 forward_all: Option<String>,
2122 forward_busy: Option<String>,
2123 forward_no_answer: Option<String>,
2124 },
2125 SpeedDialStatus {
2126 instance: u32,
2127 number: String,
2128 display_name: String,
2129 },
2130 DialedNumber {
2131 number: String,
2132 line_instance: u32,
2133 call_reference: u32,
2134 },
2135 StartMediaFailureDetection(MediaFailureDetection),
2136 UserToDeviceData(UserDataMessage),
2137 UserToDeviceDataV1(UserDataV1Message),
2138 FeatureStatus {
2139 instance: u32,
2140 button_type: ButtonType,
2141 label: String,
2142 state: u32,
2144 },
2145 ServiceUrlStatus {
2146 index: u32,
2147 url: String,
2148 label: String,
2149 extension_text: String,
2151 },
2152 CallSelectStatus {
2153 status: u32,
2155 call_reference: u32,
2156 line_instance: u32,
2157 },
2158 PortRequest(PortRequest),
2159 PortClose(PortClose),
2160 OpenMultimediaChannel(OpenMultimediaChannel),
2161 StartMultimediaTransmission(StartMultimediaTransmission),
2162 MiscellaneousCommand(MiscellaneousCommand),
2163 SubscriptionStatus {
2164 transaction_id: u32,
2165 feature_id: u32,
2166 timer_seconds: u32,
2167 cause: SubscriptionCause,
2168 },
2169 Notification {
2170 transaction_id: u32,
2171 feature_id: u32,
2172 status: BusyLampFieldState,
2173 text: String,
2174 },
2175 CallHistoryDisposition {
2176 disposition: CallHistoryDisposition,
2177 line_instance: u32,
2178 call_reference: u32,
2179 },
2180 CallCountResponse,
2181 RecordingStatus {
2182 call_reference: u32,
2183 active: bool,
2184 },
2185 KnownOpaque(KnownOpaqueMessage),
2186 Unknown(RawMessage),
2187}
2188
2189#[cfg(test)]
2190mod tests {
2191 use super::wire::{CodecError, Frame, FrameDecoder};
2192 use super::*;
2193
2194 const fn test_rtp_payload_number(value: u32) -> RtpPayloadNumber {
2195 match RtpPayloadNumber::new(value) {
2196 Ok(value) => value,
2197 Err(_) => panic!("test RTP payload number is out of range"),
2198 }
2199 }
2200
2201 fn decode_frame(bytes: &[u8]) -> Frame {
2202 FrameDecoder::new().push(bytes).unwrap().remove(0)
2203 }
2204
2205 fn assert_contract_alignment(frame: &Frame) {
2206 use super::catalog::PayloadLayout;
2207
2208 let contract = frame.message_type().contract().unwrap();
2209 if !matches!(
2210 contract.payload_layout,
2211 PayloadLayout::Opaque
2212 | PayloadLayout::BoundedOpaque
2213 | PayloadLayout::BoundedPreserved
2214 | PayloadLayout::VersionAndLengthSelected
2215 | PayloadLayout::MinimumLengthPreserved
2216 ) {
2217 assert_eq!(frame.payload.len() % 4, 0, "{}", contract.id);
2218 }
2219 }
2220
2221 fn assert_client_round_trip(message: ClientMessage, protocol: ProtocolVersion) {
2222 let frame = decode_frame(&message.encode(protocol).unwrap());
2223 assert_contract_alignment(&frame);
2224 assert_eq!(
2225 ClientMessage::decode_with_version(frame, protocol).unwrap(),
2226 message
2227 );
2228 }
2229
2230 fn assert_server_round_trip(message: ServerMessage, protocol: ProtocolVersion) {
2231 let frame = decode_frame(&message.encode(protocol).unwrap());
2232 assert_contract_alignment(&frame);
2233 assert_eq!(ServerMessage::decode(frame, protocol).unwrap(), message);
2234 }
2235
2236 fn assert_control_round_trip(message: ControlMessage, protocol: ProtocolVersion) {
2237 let frame = decode_frame(&message.encode(protocol).unwrap());
2238 assert_contract_alignment(&frame);
2239 assert_eq!(ControlMessage::decode(frame, protocol).unwrap(), message);
2240 }
2241
2242 #[test]
2243 fn multimedia_payload_exposes_only_typed_construction() {
2244 let capability = MultimediaVideoCapability::new(
2245 1_024,
2246 [MultimediaPictureFormat {
2247 format: VideoFormat::Cif4,
2248 minimum_picture_interval: 2,
2249 }],
2250 7,
2251 MultimediaVideoCapabilityArm::H264 {
2252 profile: 100,
2253 level: 42,
2254 custom_max_mbps: 40_500,
2255 custom_max_fs: 1_620,
2256 custom_max_dpb: 8_100,
2257 custom_max_br_and_cpb: 10_000,
2258 },
2259 )
2260 .unwrap();
2261 let payload = MultimediaPayload::new(test_rtp_payload_number(97), capability.clone());
2262 assert_eq!(payload.payload_number().get(), 97);
2263 assert_eq!(payload.descriptor().rfc_number(), 0);
2264 assert_eq!(payload.codec(), Codec::H264);
2265 assert_eq!(payload.video_capability(), Some(&capability));
2266
2267 let packetized = MultimediaPayload::with_descriptor(
2268 MultimediaPayloadDescriptor::new(4, payload.payload_number()),
2269 capability.clone(),
2270 );
2271 assert_eq!(packetized.descriptor().rfc_number(), 4);
2272 assert_eq!(packetized.payload_number(), payload.payload_number());
2273
2274 let debug = format!("{capability:?}");
2275 assert!(debug.contains("bit_rate: 1024"));
2276 assert!(!debug.contains("preserved_wire"));
2277 assert_eq!(
2278 RtpPayloadNumber::new(128),
2279 Err(RtpPayloadNumberError { actual: 128 })
2280 );
2281 }
2282
2283 #[test]
2284 fn multimedia_picture_formats_are_bounded_before_payload_construction() {
2285 let formats = [MultimediaPictureFormat {
2286 format: VideoFormat::Cif,
2287 minimum_picture_interval: 1,
2288 }; MAX_MULTIMEDIA_PICTURE_FORMATS + 1];
2289 assert_eq!(
2290 MultimediaVideoCapability::new(
2291 1_024,
2292 formats,
2293 0,
2294 MultimediaVideoCapabilityArm::H261 {
2295 temporal_spatial_trade_off_capability: 0,
2296 still_image_transmission: 0,
2297 },
2298 )
2299 .unwrap_err(),
2300 MultimediaCapabilityError {
2301 maximum: MAX_MULTIMEDIA_PICTURE_FORMATS,
2302 actual: MAX_MULTIMEDIA_PICTURE_FORMATS + 1,
2303 }
2304 );
2305 }
2306
2307 #[test]
2308 fn media_request_identity_is_nonzero_and_exhaustion_never_wraps() {
2309 assert_eq!(MediaRequestToken::new(0), None);
2310 let token = MediaRequestToken::new(7).unwrap();
2311 assert_eq!(MediaRequestIdentity::new(0, token), None);
2312
2313 let first = MediaRequestIdentity::new(1, token).unwrap();
2314 let second = first.checked_next().unwrap();
2315 assert_eq!(second.generation(), 2);
2316 assert_eq!(second.token().get(), 8);
2317
2318 assert_eq!(
2319 MediaRequestToken::new(u32::MAX).unwrap().checked_next(),
2320 None
2321 );
2322 let exhausted_generation =
2323 MediaRequestIdentity::new(u64::MAX, MediaRequestToken::new(1).unwrap()).unwrap();
2324 assert_eq!(exhausted_generation.checked_next(), None);
2325 }
2326
2327 #[test]
2328 fn media_request_identity_matches_only_the_current_wire_token() {
2329 let identity =
2330 MediaRequestIdentity::new(2, MediaRequestToken::new(0x1020_3040).unwrap()).unwrap();
2331
2332 assert!(identity.accepts_ack(0x1020_3040, 0, 77));
2333 assert!(identity.accepts_ack(0x1020_3040, 77, 77));
2334 assert!(!identity.accepts_ack(0x1020_3040, 78, 77));
2335 assert!(!identity.accepts_ack(0x1020_303f, 77, 77));
2336 }
2337
2338 #[test]
2339 fn zero_party_fallback_cannot_settle_a_reopened_media_generation() {
2340 let first = MediaRequestIdentity::new(1, MediaRequestToken::new(700).unwrap()).unwrap();
2341 let reopened = first.checked_next().unwrap();
2342
2343 assert!(first.accepts_ack(0, 42, 42));
2345 assert!(!first.accepts_ack(0, 0, 42));
2346
2347 assert!(!reopened.accepts_ack(0, 42, 42));
2349 assert!(!reopened.accepts_ack(first.token().get(), 42, 42));
2350 assert!(reopened.accepts_ack(reopened.token().get(), 42, 42));
2351 }
2352
2353 #[test]
2354 fn decodes_7962_off_hook_capture_shape() {
2355 let frame = Frame::new(22, id::OFF_HOOK, vec![1, 0, 0, 0, 42, 0, 0, 0]);
2356 assert_eq!(
2357 ClientMessage::decode(frame).unwrap(),
2358 ClientMessage::OffHook {
2359 line_instance: 1,
2360 call_reference: 42
2361 }
2362 );
2363 }
2364
2365 #[test]
2366 fn decodes_7961_v22_three_word_keypad_capture_shape() {
2367 let payload: Vec<_> = [8_u32, 1, 1]
2368 .into_iter()
2369 .flat_map(u32::to_le_bytes)
2370 .collect();
2371 let frame = Frame::new(22, id::KEYPAD_BUTTON, payload.clone());
2372 let decoded = ClientMessage::decode(frame).unwrap();
2373 assert_eq!(
2374 decoded,
2375 ClientMessage::KeypadButton {
2376 button: Digit::Number(8),
2377 line_instance: 1,
2378 call_reference: 1,
2379 wire_layout: Some(KeypadButtonWireLayout::WithCallIdentity),
2380 }
2381 );
2382 let encoded = FrameDecoder::new()
2383 .push(&decoded.encode(ProtocolVersion::V22).unwrap())
2384 .unwrap()
2385 .remove(0);
2386 assert_eq!(encoded.payload, payload);
2387 }
2388
2389 #[test]
2390 fn register_ack_is_protocol_zero_and_has_expected_fields() {
2391 let bytes = ServerMessage::RegisterAck {
2392 keepalive_seconds: 30,
2393 secondary_keepalive_seconds: 45,
2394 protocol: ProtocolVersion::V22,
2395 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2396 date_template: DateTemplate::default(),
2397 }
2398 .encode(ProtocolVersion::V22)
2399 .unwrap();
2400 let frame = FrameDecoder::new().push(&bytes).unwrap().remove(0);
2401 assert_eq!(frame.protocol_version, 0);
2402 assert_eq!(frame.message_id, id::REGISTER_ACK);
2403 assert_eq!(
2404 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
2405 ServerMessage::RegisterAck {
2406 keepalive_seconds: 30,
2407 secondary_keepalive_seconds: 45,
2408 protocol: ProtocolVersion::V22,
2409 features: PhoneFeatures::UTF8 | PhoneFeatures::DYNAMIC_MESSAGES,
2410 date_template: DateTemplate::default(),
2411 }
2412 );
2413 }
2414
2415 #[test]
2416 fn media_layout_sizes_match_supported_wire_specs() {
2417 let endpoint = MediaEndpoint {
2418 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2419 rtp_port: 4000,
2420 rtcp_port: 4001,
2421 codec: Codec::Pcmu,
2422 packet_ms: 20,
2423 max_frames_per_packet: 1,
2424 telephone_event_payload: 101,
2425 };
2426 let start = ServerMessage::StartMediaTransmission {
2427 call_reference: 7,
2428 passthrough_party_id: 9,
2429 endpoint,
2430 silence_suppression: SilenceSuppression::Off,
2431 traffic_class: crate::types::MediaTrafficClass::from_wire(184),
2432 encryption: None,
2433 wire: None,
2434 }
2435 .encode(ProtocolVersion::V17)
2436 .unwrap();
2437 assert_eq!(start.len(), 144); assert_eq!(&start[52..56], &184_u32.to_le_bytes());
2439 assert_eq!(&start[140..144], &1_u32.to_le_bytes());
2440 let open = ServerMessage::OpenReceiveChannel {
2441 call_reference: 7,
2442 passthrough_party_id: 9,
2443 packet_ms: 20,
2444 codec: Codec::Pcmu,
2445 echo_cancellation: EchoCancellation::On,
2446 telephone_event_payload: 101,
2447 source_address: endpoint.address,
2448 source_port: endpoint.rtp_port,
2449 encryption: None,
2450 wire: None,
2451 }
2452 .encode(ProtocolVersion::V17)
2453 .unwrap();
2454 assert_eq!(open.len(), 140); assert_eq!(&open[108..112], &1_u32.to_le_bytes());
2456
2457 let start_v3 = ServerMessage::StartMediaTransmission {
2458 call_reference: 7,
2459 passthrough_party_id: 9,
2460 endpoint,
2461 silence_suppression: SilenceSuppression::Off,
2462 traffic_class: crate::types::MediaTrafficClass::default(),
2463 encryption: None,
2464 wire: None,
2465 }
2466 .encode(ProtocolVersion::V3)
2467 .unwrap();
2468 assert_eq!(start_v3.len(), 120); let open_v3 = ServerMessage::OpenReceiveChannel {
2470 call_reference: 7,
2471 passthrough_party_id: 9,
2472 packet_ms: 20,
2473 codec: Codec::Pcmu,
2474 echo_cancellation: EchoCancellation::On,
2475 telephone_event_payload: 101,
2476 source_address: endpoint.address,
2477 source_port: endpoint.rtp_port,
2478 encryption: None,
2479 wire: None,
2480 }
2481 .encode(ProtocolVersion::V3)
2482 .unwrap();
2483 assert_eq!(open_v3.len(), 104); let start_v22 = ServerMessage::StartMediaTransmission {
2486 call_reference: 7,
2487 passthrough_party_id: 9,
2488 endpoint,
2489 silence_suppression: SilenceSuppression::Off,
2490 traffic_class: crate::types::MediaTrafficClass::default(),
2491 encryption: None,
2492 wire: None,
2493 }
2494 .encode(ProtocolVersion::V22)
2495 .unwrap();
2496 assert_eq!(start_v22.len(), 180); let open_v22 = ServerMessage::OpenReceiveChannel {
2498 call_reference: 7,
2499 passthrough_party_id: 9,
2500 packet_ms: 20,
2501 codec: Codec::Pcmu,
2502 echo_cancellation: EchoCancellation::On,
2503 telephone_event_payload: 101,
2504 source_address: endpoint.address,
2505 source_port: endpoint.rtp_port,
2506 encryption: None,
2507 wire: None,
2508 }
2509 .encode(ProtocolVersion::V22)
2510 .unwrap();
2511 assert_eq!(open_v22.len(), 180); }
2513
2514 #[test]
2515 fn media_close_layouts_consume_the_reference_fields_exactly() {
2516 let close = ServerMessage::CloseReceiveChannel(AudioStreamControl {
2517 conference_id: 6.into(),
2518 passthrough_party_id: 9.into(),
2519 call_reference: 7.into(),
2520 port_handling_flag: 11,
2521 });
2522 let close_v3 = close.encode(ProtocolVersion::V3).unwrap();
2523 assert_eq!(close_v3.len(), 28);
2524 assert_eq!(
2525 ServerMessage::decode(decode_frame(&close_v3), ProtocolVersion::V3).unwrap(),
2526 close
2527 );
2528 let close_v5 = close.encode(ProtocolVersion::V5).unwrap();
2529 assert_eq!(close_v5.len(), 28);
2530 assert_eq!(
2531 ServerMessage::decode(decode_frame(&close_v5), ProtocolVersion::V5).unwrap(),
2532 close
2533 );
2534
2535 let stop = ServerMessage::StopMediaTransmission(AudioStreamControl {
2536 conference_id: 6.into(),
2537 passthrough_party_id: 9.into(),
2538 call_reference: 7.into(),
2539 port_handling_flag: 11,
2540 });
2541 let bytes = stop.encode(ProtocolVersion::V22).unwrap();
2542 assert_eq!(bytes.len(), 28);
2543 assert_eq!(
2544 ServerMessage::decode(decode_frame(&bytes), ProtocolVersion::V22).unwrap(),
2545 stop
2546 );
2547
2548 let mut trailing = decode_frame(&bytes);
2549 trailing.payload.extend_from_slice(&[0; 4]);
2550 assert!(matches!(
2551 ServerMessage::decode(trailing, ProtocolVersion::V22),
2552 Err(CodecError::TrailingBytes { count: 4, .. })
2553 ));
2554 }
2555
2556 #[test]
2557 fn audio_packetization_round_trips_without_default_substitution() {
2558 let endpoint = MediaEndpoint {
2559 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2560 rtp_port: 4000,
2561 rtcp_port: 4001,
2562 codec: Codec::G72264k,
2563 packet_ms: 30,
2564 max_frames_per_packet: 2,
2565 telephone_event_payload: 101,
2566 };
2567 for protocol in [
2568 ProtocolVersion::V3,
2569 ProtocolVersion::V17,
2570 ProtocolVersion::V22,
2571 ] {
2572 let (source_address, source_port) = if protocol.wire() < 12 {
2573 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2574 } else {
2575 (endpoint.address, endpoint.rtp_port)
2576 };
2577 assert_server_round_trip(
2578 ServerMessage::OpenReceiveChannel {
2579 call_reference: 7,
2580 passthrough_party_id: 9,
2581 packet_ms: 30,
2582 codec: Codec::G72264k,
2583 echo_cancellation: EchoCancellation::On,
2584 telephone_event_payload: 101,
2585 source_address,
2586 source_port,
2587 encryption: None,
2588 wire: None,
2589 },
2590 protocol,
2591 );
2592 assert_server_round_trip(
2593 ServerMessage::StartMediaTransmission {
2594 call_reference: 7,
2595 passthrough_party_id: 9,
2596 endpoint,
2597 silence_suppression: SilenceSuppression::On,
2598 traffic_class: crate::types::MediaTrafficClass::default(),
2599 encryption: None,
2600 wire: None,
2601 },
2602 protocol,
2603 );
2604 assert_client_round_trip(
2605 ClientMessage::MediaTransmissionFailure {
2606 conference_id: 7,
2607 passthrough_party_id: 9,
2608 address: endpoint.address,
2609 port: endpoint.rtp_port,
2610 call_reference: 7,
2611 status: MediaStatus::UnspecifiedError,
2612 },
2613 protocol,
2614 );
2615 }
2616 }
2617
2618 #[test]
2619 fn ipv6_audio_endpoints_require_and_round_trip_extended_layouts() {
2620 let address: IpAddr = "2001:db8::42".parse().unwrap();
2621 let endpoint = MediaEndpoint {
2622 address,
2623 rtp_port: 40_000,
2624 rtcp_port: 40_001,
2625 codec: Codec::G72264k,
2626 packet_ms: 20,
2627 max_frames_per_packet: 1,
2628 telephone_event_payload: 101,
2629 };
2630 let start = ServerMessage::StartMediaTransmission {
2631 call_reference: 7,
2632 passthrough_party_id: 9,
2633 endpoint,
2634 silence_suppression: SilenceSuppression::Off,
2635 traffic_class: crate::types::MediaTrafficClass::default(),
2636 encryption: None,
2637 wire: None,
2638 };
2639 let receive_ack = ClientMessage::OpenReceiveChannelAck {
2640 status: MediaStatus::Ok,
2641 address,
2642 port: endpoint.rtp_port,
2643 passthrough_party_id: 9,
2644 call_reference: 7,
2645 };
2646 let transmit_ack = ClientMessage::StartMediaTransmissionAck(MediaTransmissionAck {
2647 conference_id: 6,
2648 passthrough_party_id: 9,
2649 call_reference: 7,
2650 status: MediaStatus::Ok,
2651 address,
2652 port: endpoint.rtp_port,
2653 wire: None,
2654 });
2655 let failure = ClientMessage::MediaTransmissionFailure {
2656 conference_id: 7,
2657 passthrough_party_id: 9,
2658 address,
2659 port: endpoint.rtp_port,
2660 call_reference: 7,
2661 status: MediaStatus::UnspecifiedError,
2662 };
2663
2664 for protocol in [ProtocolVersion::V17, ProtocolVersion::V22] {
2665 assert_server_round_trip(start.clone(), protocol);
2666 assert_client_round_trip(receive_ack.clone(), protocol);
2667 assert_client_round_trip(transmit_ack.clone(), protocol);
2668 assert_client_round_trip(failure.clone(), protocol);
2669 }
2670 for result in [
2671 start.encode(ProtocolVersion::V16),
2672 receive_ack.encode(ProtocolVersion::V16),
2673 transmit_ack.encode(ProtocolVersion::V16),
2674 failure.encode(ProtocolVersion::V16),
2675 failure.encode(ProtocolVersion::V3),
2676 ] {
2677 assert!(matches!(
2678 result,
2679 Err(CodecError::InvalidValue {
2680 field: "IP address family for pre-v17 protocol"
2681 | "IP address family for this protocol version",
2682 ..
2683 })
2684 ));
2685 }
2686 }
2687
2688 #[test]
2689 fn skinny_dtmf_disables_the_telephone_event_payload_in_both_directions() {
2690 let endpoint = MediaEndpoint {
2691 address: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2692 rtp_port: 4000,
2693 rtcp_port: 4001,
2694 codec: Codec::Pcmu,
2695 packet_ms: 20,
2696 max_frames_per_packet: 1,
2697 telephone_event_payload: 0,
2698 };
2699 for protocol in [
2700 ProtocolVersion::V3,
2701 ProtocolVersion::V17,
2702 ProtocolVersion::V22,
2703 ] {
2704 let (source_address, source_port) = if protocol.wire() < 12 {
2705 (IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
2706 } else {
2707 (endpoint.address, endpoint.rtp_port)
2708 };
2709 assert_server_round_trip(
2710 ServerMessage::OpenReceiveChannel {
2711 call_reference: 7,
2712 passthrough_party_id: 9,
2713 packet_ms: 20,
2714 codec: Codec::Pcmu,
2715 echo_cancellation: EchoCancellation::On,
2716 telephone_event_payload: 0,
2717 source_address,
2718 source_port,
2719 encryption: None,
2720 wire: None,
2721 },
2722 protocol,
2723 );
2724 assert_server_round_trip(
2725 ServerMessage::StartMediaTransmission {
2726 call_reference: 7,
2727 passthrough_party_id: 9,
2728 endpoint,
2729 silence_suppression: SilenceSuppression::Off,
2730 traffic_class: crate::types::MediaTrafficClass::default(),
2731 encryption: None,
2732 wire: None,
2733 },
2734 protocol,
2735 );
2736 }
2737 }
2738
2739 #[test]
2740 fn open_receive_wildcard_source_round_trips_for_all_supported_layouts() {
2741 for protocol in [
2742 ProtocolVersion::V3,
2743 ProtocolVersion::V17,
2744 ProtocolVersion::V22,
2745 ] {
2746 assert_server_round_trip(
2747 ServerMessage::OpenReceiveChannel {
2748 call_reference: 1,
2749 passthrough_party_id: 1,
2750 packet_ms: 20,
2751 codec: Codec::Pcma,
2752 echo_cancellation: EchoCancellation::Off,
2753 telephone_event_payload: 101,
2754 source_address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
2755 source_port: 0,
2756 encryption: None,
2757 wire: None,
2758 },
2759 protocol,
2760 );
2761 }
2762 }
2763
2764 #[test]
2765 fn media_encryption_round_trips_without_exposing_key_material() {
2766 let key = b"private-key-1234";
2767 let salt = b"private-salt-123";
2768 let encryption =
2769 MediaEncryption::new(EncryptionMethod::Aes128HmacSha1_80, key, salt, 1, 64).unwrap();
2770 assert_eq!(encryption.key(), key);
2771 assert_eq!(encryption.salt(), salt);
2772
2773 let debug = format!("{encryption:?}");
2774 assert!(debug.contains("<redacted>"));
2775 assert!(!debug.contains("112, 114, 105, 118, 97, 116, 101"));
2776 assert!(!debug.contains("private-key"));
2777 let endpoint = MediaEndpoint {
2778 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
2779 rtp_port: 40_000,
2780 rtcp_port: 40_001,
2781 codec: Codec::Pcmu,
2782 packet_ms: 20,
2783 max_frames_per_packet: 1,
2784 telephone_event_payload: 101,
2785 };
2786
2787 for protocol in [
2788 ProtocolVersion::new(12).unwrap(),
2789 ProtocolVersion::V17,
2790 ProtocolVersion::V22,
2791 ] {
2792 let open = ServerMessage::OpenReceiveChannel {
2793 call_reference: 7,
2794 passthrough_party_id: 9,
2795 packet_ms: 20,
2796 codec: Codec::Pcmu,
2797 echo_cancellation: EchoCancellation::On,
2798 telephone_event_payload: 101,
2799 source_address: endpoint.address,
2800 source_port: endpoint.rtp_port,
2801 encryption: Some(encryption.clone()),
2802 wire: None,
2803 };
2804 let open_debug = format!("{open:?}");
2805 assert!(open_debug.contains("<redacted>"));
2806 assert!(!open_debug.contains("112, 114, 105, 118, 97, 116, 101"));
2807 assert_server_round_trip(open, protocol);
2808 assert_server_round_trip(
2809 ServerMessage::StartMediaTransmission {
2810 call_reference: 7,
2811 passthrough_party_id: 9,
2812 endpoint,
2813 silence_suppression: SilenceSuppression::Off,
2814 traffic_class: crate::types::MediaTrafficClass::default(),
2815 encryption: Some(encryption.clone()),
2816 wire: None,
2817 },
2818 protocol,
2819 );
2820 }
2821 }
2822
2823 #[test]
2824 fn media_encryption_rejects_oversized_secrets_with_metadata_only_errors() {
2825 let oversized_key = [0xa5; 17];
2826 let error = MediaEncryption::new(
2827 EncryptionMethod::Aes128HmacSha1_32,
2828 &oversized_key,
2829 &[],
2830 0,
2831 0,
2832 )
2833 .unwrap_err();
2834 assert!(matches!(
2835 error,
2836 CodecError::SecretTooLong {
2837 field: "media encryption key",
2838 actual: 17,
2839 maximum: 16,
2840 }
2841 ));
2842 assert!(!error.to_string().contains("165"));
2843
2844 let oversized_salt = [0x5a; 17];
2845 let error = MediaEncryption::new(
2846 EncryptionMethod::Aes128HmacSha1_32,
2847 &[],
2848 &oversized_salt,
2849 0,
2850 0,
2851 )
2852 .unwrap_err();
2853 assert!(matches!(
2854 error,
2855 CodecError::SecretTooLong {
2856 field: "media encryption salt",
2857 actual: 17,
2858 maximum: 16,
2859 }
2860 ));
2861 assert!(!error.to_string().contains("90"));
2862 }
2863
2864 #[test]
2865 fn common_client_messages_round_trip_semantically() {
2866 assert_client_round_trip(
2867 ClientMessage::FeatureStatusRequest {
2868 index: 7,
2869 capabilities: 1,
2870 },
2871 ProtocolVersion::V22,
2872 );
2873 assert_client_round_trip(
2874 ClientMessage::OffHookWithCallingParty {
2875 calling_party_number: "1001".into(),
2876 voice_mailbox: "5001".into(),
2877 line_instance: 1,
2878 },
2879 ProtocolVersion::V3,
2880 );
2881 assert_client_round_trip(
2882 ClientMessage::RegisterToken(RegisterTokenMessage {
2883 device_id: DeviceId::new("SEP001122334455").unwrap(),
2884 device_instance: 2,
2885 address: "2001:db8::42".parse().unwrap(),
2886 device_type: DeviceType::Cisco7962,
2887 flags: 6,
2888 }),
2889 ProtocolVersion::V22,
2890 );
2891 assert_control_round_trip(
2892 ControlMessage::MediaResourceNotification(MediaResourceNotification {
2893 device_type: DeviceType::Unknown(0xfeed),
2894 in_service_streams: 2,
2895 max_streams_per_conference: 4,
2896 out_of_service_streams: 1,
2897 }),
2898 ProtocolVersion::V17,
2899 );
2900 assert_client_round_trip(
2901 ClientMessage::SubscriptionStatusRequest(SubscriptionRequest {
2902 transaction_id: 0x4b,
2903 feature_id: 1,
2904 timer_seconds: 30,
2905 subscription_id: "4000".into(),
2906 }),
2907 ProtocolVersion::V22,
2908 );
2909 for message in [
2910 ClientMessage::SubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
2911 payload_type: 101,
2912 conference_id: 42,
2913 passthrough_party_id: 7,
2914 }),
2915 ClientMessage::UnsubscribeDtmfPayloadResponse(DtmfPayloadIdentity {
2916 payload_type: 102,
2917 conference_id: 43,
2918 passthrough_party_id: 8,
2919 }),
2920 ] {
2921 let encoded = message.encode(ProtocolVersion::V22).unwrap();
2922 let frame = decode_frame(&encoded);
2923 assert_eq!(frame.payload.len(), 12);
2924 assert_eq!(
2925 ClientMessage::decode_with_version(frame, ProtocolVersion::V22).unwrap(),
2926 message
2927 );
2928 }
2929 assert_client_round_trip(
2930 ClientMessage::DeviceToUserDataV1(UserDataV1Message {
2931 application_id: 7,
2932 line_instance: 1,
2933 call_reference: 42,
2934 transaction_id: 9,
2935 sequence_flag: 1,
2936 display_priority: 2,
2937 conference_id: 42,
2938 application_instance_id: 3,
2939 routing: 4,
2940 data: b"<CiscoIPPhoneText/>".to_vec(),
2941 }),
2942 ProtocolVersion::V17,
2943 );
2944 assert_client_round_trip(
2945 ClientMessage::DeviceToUserDataResponse(UserDataMessage {
2946 application_id: 8,
2947 line_instance: 2,
2948 call_reference: 43,
2949 transaction_id: 10,
2950 data: b"<CiscoIPPhoneResponse/>".to_vec(),
2951 }),
2952 ProtocolVersion::V17,
2953 );
2954 assert_client_round_trip(
2955 ClientMessage::DeviceToUserData(UserDataMessage {
2956 application_id: 9,
2957 line_instance: 2,
2958 call_reference: 44,
2959 transaction_id: 11,
2960 data: b"<CiscoIPPhoneInput/>".to_vec(),
2961 }),
2962 ProtocolVersion::V17,
2963 );
2964 assert_client_round_trip(
2965 ClientMessage::DeviceToUserDataResponseV1(UserDataV1Message {
2966 application_id: 9,
2967 line_instance: 2,
2968 call_reference: 44,
2969 transaction_id: 11,
2970 sequence_flag: 2,
2971 display_priority: 1,
2972 conference_id: 44,
2973 application_instance_id: 9,
2974 routing: 1,
2975 data: b"<CiscoIPPhoneResponse/>".to_vec(),
2976 }),
2977 ProtocolVersion::V17,
2978 );
2979 assert_client_round_trip(
2980 ClientMessage::LocationInfo {
2981 xml: "<location><building>west</building></location>".into(),
2982 },
2983 ProtocolVersion::V22,
2984 );
2985 assert_client_round_trip(
2986 ClientMessage::XmlAlarm(
2987 XmlAlarmMessage::from_xml(b"<alarm><severity>warning</severity></alarm>").unwrap(),
2988 ),
2989 ProtocolVersion::V22,
2990 );
2991 assert_client_round_trip(
2992 ClientMessage::CallCountRequest { value: 2 },
2993 ProtocolVersion::V22,
2994 );
2995 assert_control_round_trip(
2996 ControlMessage::PortResponse(PortEndpoint {
2997 conference_id: 42,
2998 call_reference: 42,
2999 passthrough_party_id: 8,
3000 address: "2001:db8::8".parse().unwrap(),
3001 rtp_port: 16_000,
3002 rtcp_port: 16_001,
3003 media_type: Some(MediaType::Audio),
3004 }),
3005 ProtocolVersion::V22,
3006 );
3007 assert_control_round_trip(
3008 ControlMessage::CreateConferenceResponse(CreateConferenceResponse {
3009 conference_id: ConferenceId::new(42),
3010 result: CreateConferenceResult::Ok,
3011 passthrough_data: vec![1, 2, 3],
3012 }),
3013 ProtocolVersion::V22,
3014 );
3015 assert_control_round_trip(
3016 ControlMessage::DeleteConferenceResponse {
3017 conference_id: ConferenceId::new(42),
3018 result: DeleteConferenceResult::ConferenceDoesNotExist,
3019 },
3020 ProtocolVersion::V22,
3021 );
3022 assert_control_round_trip(
3023 ControlMessage::ModifyConferenceResponse(ModifyConferenceResponse {
3024 conference_id: ConferenceId::new(42),
3025 result: ModifyConferenceResult::MoreActiveCallsThanReserved,
3026 passthrough_data: vec![4, 5],
3027 }),
3028 ProtocolVersion::V22,
3029 );
3030 assert_control_round_trip(
3031 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3032 last: 1,
3033 entries: vec![AuditConferenceEntry {
3034 conference_id: ConferenceId::new(42),
3035 resource_type: ConferenceResourceType::Conference,
3036 reserved_participants: 8,
3037 active_participants: 3,
3038 application_id: ApplicationId::new(7),
3039 application_conference_id: "festival-42".into(),
3040 application_data: "main-stage".into(),
3041 }],
3042 }),
3043 ProtocolVersion::V22,
3044 );
3045 assert_control_round_trip(
3046 ControlMessage::AddParticipantResponse(AddParticipantResponse {
3047 conference_id: ConferenceId::new(42),
3048 call_reference: CallReference::new(100),
3049 result: AddParticipantResult::Ok,
3050 bridge_participant_id: BoundedBytes::try_from(vec![3; 257]).unwrap(),
3051 }),
3052 ProtocolVersion::V22,
3053 );
3054 assert_control_round_trip(
3055 ControlMessage::AuditParticipantResponse(AuditParticipantResponse {
3056 result: AuditParticipantResult::Ok,
3057 last: 1,
3058 conference_id: ConferenceId::new(42),
3059 number_of_entries: 2,
3060 participant_entries: vec![1, 2, 3, 4],
3061 }),
3062 ProtocolVersion::V22,
3063 );
3064 }
3065
3066 #[test]
3067 fn common_server_messages_round_trip_semantically() {
3068 assert_server_round_trip(
3069 ServerMessage::SpeedDialStatus {
3070 instance: 7,
3071 number: "2001".into(),
3072 display_name: "Reception".into(),
3073 },
3074 ProtocolVersion::V3,
3075 );
3076 assert_server_round_trip(
3077 ServerMessage::ServiceUrlStatus {
3078 index: 4,
3079 url: "http://services.invalid/directory".into(),
3080 label: "Directory".into(),
3081 extension_text: String::new(),
3082 },
3083 ProtocolVersion::V3,
3084 );
3085 for protocol in [
3086 ProtocolVersion::V3,
3087 ProtocolVersion::V17,
3088 ProtocolVersion::V22,
3089 ] {
3090 assert_server_round_trip(
3091 ServerMessage::ConnectionStatisticsRequest {
3092 directory_number: "1001".into(),
3093 call_reference: 42,
3094 processing: StatisticsProcessing::DoNotClear,
3095 },
3096 protocol,
3097 );
3098 }
3099 assert_server_round_trip(
3100 ServerMessage::DisplayPriorityNotify {
3101 timeout_seconds: 5,
3102 priority: NotificationPriority::Voicemail,
3103 text: "Incoming call".into(),
3104 },
3105 ProtocolVersion::V17,
3106 );
3107 assert_server_round_trip(
3108 ServerMessage::FeatureStatus {
3109 instance: 2,
3110 button_type: ButtonType::BlfSpeedDial,
3111 label: "Support".into(),
3112 state: 0x0002_0101,
3113 },
3114 ProtocolVersion::V22,
3115 );
3116 assert_server_round_trip(
3117 ServerMessage::PortRequest(PortRequest {
3118 conference_id: 42.into(),
3119 call_reference: 42.into(),
3120 passthrough_party_id: 9.into(),
3121 transport: MediaTransport::Rtp,
3122 address_type: Some(IpAddressType::Ipv4AndIpv6),
3123 media_type: Some(MediaType::Audio),
3124 }),
3125 ProtocolVersion::V22,
3126 );
3127 assert_server_round_trip(
3128 ServerMessage::Notification {
3129 transaction_id: 3,
3130 feature_id: 1,
3131 status: BusyLampFieldState::Unknown(77),
3132 text: "4000".into(),
3133 },
3134 ProtocolVersion::V22,
3135 );
3136 assert_server_round_trip(
3137 ServerMessage::SubscriptionStatus {
3138 transaction_id: 3,
3139 feature_id: 1,
3140 timer_seconds: 30,
3141 cause: SubscriptionCause::Ok,
3142 },
3143 ProtocolVersion::V22,
3144 );
3145 assert_server_round_trip(
3146 ServerMessage::UserToDeviceData(UserDataMessage {
3147 application_id: 7,
3148 line_instance: 1,
3149 call_reference: 42,
3150 transaction_id: 9,
3151 data: b"<CiscoIPPhoneText/>".to_vec(),
3152 }),
3153 ProtocolVersion::V17,
3154 );
3155 assert_server_round_trip(
3156 ServerMessage::UserToDeviceDataV1(UserDataV1Message {
3157 application_id: 7,
3158 line_instance: 1,
3159 call_reference: 42,
3160 transaction_id: 9,
3161 sequence_flag: 2,
3162 display_priority: 1,
3163 conference_id: 42,
3164 application_instance_id: 7,
3165 routing: 1,
3166 data: b"<CiscoIPPhoneMenu/>".to_vec(),
3167 }),
3168 ProtocolVersion::V17,
3169 );
3170 assert_server_round_trip(
3171 ServerMessage::CallHistoryDisposition {
3172 disposition: CallHistoryDisposition::Missed,
3173 line_instance: 1,
3174 call_reference: 42,
3175 },
3176 ProtocolVersion::V22,
3177 );
3178 assert_server_round_trip(ServerMessage::CallCountResponse, ProtocolVersion::V22);
3179 for message in [
3180 ServerMessage::SubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3181 payload_type: 101,
3182 conference_id: 42,
3183 passthrough_party_id: 7,
3184 dtmf_type: 2,
3185 }),
3186 ServerMessage::SubscribeDtmfPayloadError(DtmfPayloadIdentity {
3187 payload_type: 102,
3188 conference_id: 43,
3189 passthrough_party_id: 8,
3190 }),
3191 ServerMessage::UnsubscribeDtmfPayloadRequest(DtmfPayloadRequest {
3192 payload_type: 103,
3193 conference_id: 44,
3194 passthrough_party_id: 9,
3195 dtmf_type: 3,
3196 }),
3197 ServerMessage::UnsubscribeDtmfPayloadError(DtmfPayloadIdentity {
3198 payload_type: 104,
3199 conference_id: 45,
3200 passthrough_party_id: 10,
3201 }),
3202 ] {
3203 let encoded = message.encode(ProtocolVersion::V22).unwrap();
3204 let frame = decode_frame(&encoded);
3205 assert!(matches!(frame.payload.len(), 12 | 16));
3206 assert_eq!(
3207 ServerMessage::decode(frame, ProtocolVersion::V22).unwrap(),
3208 message
3209 );
3210 }
3211 assert_server_round_trip(
3212 ServerMessage::RecordingStatus {
3213 call_reference: 42,
3214 active: true,
3215 },
3216 ProtocolVersion::V22,
3217 );
3218 assert_control_round_trip(
3219 ControlMessage::StartAnnouncement {
3220 announcements: vec![
3221 AnnouncementEntry {
3222 locale: 1,
3223 country: 46,
3224 tone: Tone::Zip,
3225 },
3226 AnnouncementEntry {
3227 locale: 0,
3228 country: 0,
3229 tone: Tone::Silence,
3230 },
3231 AnnouncementEntry {
3232 locale: 2,
3233 country: 1,
3234 tone: Tone::RecorderWarning,
3235 },
3236 ],
3237 end_of_ack: EndOfAnnouncementAck::Required,
3238 conference_id: 42,
3239 matrix_conference_party_ids: vec![7, 0, 9],
3240 hearing_conference_party_mask: 0b101,
3241 play_mode: AnnouncementPlayMode::Continuous,
3242 },
3243 ProtocolVersion::V22,
3244 );
3245 assert_control_round_trip(
3246 ControlMessage::StopAnnouncement { conference_id: 42 },
3247 ProtocolVersion::V22,
3248 );
3249 assert_control_round_trip(
3250 ControlMessage::AnnouncementFinish {
3251 conference_id: 42,
3252 play_status: AnnouncementPlayStatus::Unknown(3),
3253 },
3254 ProtocolVersion::V22,
3255 );
3256 assert_control_round_trip(
3257 ControlMessage::ClearConference {
3258 conference_id: ConferenceId::new(42),
3259 service_number: 3,
3260 },
3261 ProtocolVersion::V22,
3262 );
3263 assert_control_round_trip(
3264 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3265 conference_id: ConferenceId::new(42),
3266 reserved_participants: 8,
3267 resource_type: ConferenceResourceType::Conference,
3268 application_id: ApplicationId::new(7),
3269 application_conference_id: "festival-42".into(),
3270 application_data: "main-stage".into(),
3271 passthrough_data: vec![1, 2, 3],
3272 }),
3273 ProtocolVersion::V22,
3274 );
3275 assert_control_round_trip(
3276 ControlMessage::DeleteConferenceRequest {
3277 conference_id: ConferenceId::new(42),
3278 },
3279 ProtocolVersion::V22,
3280 );
3281 assert_control_round_trip(
3282 ControlMessage::ModifyConferenceRequest(ModifyConferenceRequest {
3283 conference_id: ConferenceId::new(42),
3284 reserved_participants: 12,
3285 application_id: ApplicationId::new(7),
3286 application_conference_id: "festival-42".into(),
3287 application_data: "main-stage".into(),
3288 passthrough_data: vec![4, 5],
3289 }),
3290 ProtocolVersion::V22,
3291 );
3292 assert_control_round_trip(ControlMessage::AuditConferenceRequest, ProtocolVersion::V22);
3293 assert_control_round_trip(
3294 ControlMessage::AddParticipantRequest(AddParticipantRequest {
3295 conference_id: ConferenceId::new(42),
3296 participant: ConferenceParticipant {
3297 call_reference: CallReference::new(100),
3298 presentation_restrictions: PartyInformationRestrictions::CALLING_NUMBER,
3299 name: "Festival Caller".into(),
3300 number: "1001".into(),
3301 conference_name: "Main Stage".into(),
3302 },
3303 }),
3304 ProtocolVersion::V22,
3305 );
3306 assert_control_round_trip(
3307 ControlMessage::DropParticipantRequest {
3308 conference_id: ConferenceId::new(42),
3309 call_reference: CallReference::new(100),
3310 },
3311 ProtocolVersion::V22,
3312 );
3313 assert_control_round_trip(
3314 ControlMessage::AuditParticipantRequest {
3315 conference_id: ConferenceId::new(42),
3316 },
3317 ProtocolVersion::V22,
3318 );
3319 }
3320
3321 #[test]
3322 fn connection_statistics_round_trip_all_layouts_and_redact_opaque_fields() {
3323 let statistics = ConnectionStatistics {
3324 directory_number: "2002".into(),
3325 call_reference: 42,
3326 processing: StatisticsProcessing::Clear,
3327 packets_sent: 100,
3328 octets_sent: 8_000,
3329 packets_received: 98,
3330 octets_received: 7_840,
3331 packets_lost: 2,
3332 jitter_millis: 7,
3333 latency_millis: 18,
3334 quality: ConnectionQualityStatistics::new(b"MLQK=4.5;Secret=opaque".to_vec()).unwrap(),
3335 };
3336 for protocol in [
3337 ProtocolVersion::V3,
3338 ProtocolVersion::V19,
3339 ProtocolVersion::V22,
3340 ] {
3341 assert_client_round_trip(
3342 ClientMessage::ConnectionStatisticsResponse(statistics.clone()),
3343 protocol,
3344 );
3345 }
3346 let debug = format!("{statistics:?}");
3347 assert!(!debug.contains("2002"));
3348 assert!(!debug.contains("Secret"));
3349 assert!(debug.contains("byte_count"));
3350 assert!(matches!(
3351 ConnectionQualityStatistics::new(vec![0; CONNECTION_QUALITY_MAX_BYTES + 1]),
3352 Err(CodecError::CountTooLarge {
3353 field: "quality statistics",
3354 maximum: CONNECTION_QUALITY_MAX_BYTES,
3355 ..
3356 })
3357 ));
3358 }
3359
3360 #[test]
3361 fn dtmf_subscription_messages_require_their_exact_word_layouts() {
3362 for message_id in [
3363 id::SUBSCRIBE_DTMF_PAYLOAD_RES,
3364 id::UNSUBSCRIBE_DTMF_PAYLOAD_RES,
3365 ] {
3366 assert!(ClientMessage::decode(Frame::new(22, message_id, Vec::new())).is_err());
3367 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 11])).is_err());
3368 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 12])).is_ok());
3369 assert!(ClientMessage::decode(Frame::new(22, message_id, vec![0; 13])).is_err());
3370 }
3371 for (message_id, size) in [
3372 (id::SUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3373 (id::SUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3374 (id::UNSUBSCRIBE_DTMF_PAYLOAD_REQ, 16),
3375 (id::UNSUBSCRIBE_DTMF_PAYLOAD_ERR, 12),
3376 ] {
3377 assert!(
3378 ServerMessage::decode(
3379 Frame::new(22, message_id, vec![0; size - 1]),
3380 ProtocolVersion::V22,
3381 )
3382 .is_err()
3383 );
3384 assert!(
3385 ServerMessage::decode(
3386 Frame::new(22, message_id, vec![0; size]),
3387 ProtocolVersion::V22,
3388 )
3389 .is_ok()
3390 );
3391 assert!(
3392 ServerMessage::decode(
3393 Frame::new(22, message_id, vec![0; size + 1]),
3394 ProtocolVersion::V22,
3395 )
3396 .is_err()
3397 );
3398 }
3399 }
3400
3401 #[test]
3402 fn announcement_lists_enforce_station_bounds() {
3403 let error = ServerMessage::StartAnnouncement {
3404 announcements: vec![
3405 AnnouncementEntry {
3406 locale: 1,
3407 country: 1,
3408 tone: Tone::Zip,
3409 };
3410 33
3411 ],
3412 end_of_ack: 0,
3413 conference_id: 1,
3414 matrix_conference_party_ids: Vec::new(),
3415 hearing_conference_party_mask: 0,
3416 play_mode: 0,
3417 }
3418 .encode(ProtocolVersion::V22)
3419 .unwrap_err();
3420 assert!(matches!(
3421 error,
3422 CodecError::CountTooLarge {
3423 field: "announcements",
3424 count: 33,
3425 maximum: 32,
3426 ..
3427 }
3428 ));
3429
3430 let error = ServerMessage::StartAnnouncement {
3431 announcements: Vec::new(),
3432 end_of_ack: 0,
3433 conference_id: 1,
3434 matrix_conference_party_ids: (1..=17).collect(),
3435 hearing_conference_party_mask: 0,
3436 play_mode: 0,
3437 }
3438 .encode(ProtocolVersion::V22)
3439 .unwrap_err();
3440 assert!(matches!(
3441 error,
3442 CodecError::CountTooLarge {
3443 field: "matrix conference party identifiers",
3444 count: 17,
3445 maximum: 16,
3446 ..
3447 }
3448 ));
3449 }
3450
3451 #[test]
3452 fn enbloc_uses_the_protocol_19_alignment_boundary() {
3453 for (protocol, payload_len, line_offset) in [
3454 (ProtocolVersion::V18, 28, 24),
3455 (ProtocolVersion::V19, 32, 28),
3456 ] {
3457 let message = ClientMessage::EnblocCall {
3458 called_party: "9801".into(),
3459 line_instance: 3,
3460 };
3461 let frame = FrameDecoder::new()
3462 .push(&message.encode(protocol).unwrap())
3463 .unwrap()
3464 .remove(0);
3465 assert_eq!(frame.payload.len(), payload_len);
3466 assert_eq!(
3467 &frame.payload[line_offset..line_offset + 4],
3468 &3_u32.to_le_bytes()
3469 );
3470 assert_eq!(
3471 ClientMessage::decode_with_version(frame, protocol).unwrap(),
3472 message
3473 );
3474 }
3475 }
3476
3477 #[test]
3478 fn opaque_and_unknown_messages_are_byte_lossless() {
3479 let known_payload = vec![0, 1, 2, 0xff, 4];
3480 for id in [
3481 MessageId::MediaPortList,
3482 MessageId::SpcpRegisterTokenRequest,
3483 ] {
3484 let known = ClientMessage::decode_with_version(
3485 Frame::new(19, id.wire_value(), known_payload.clone()),
3486 ProtocolVersion::V22,
3487 )
3488 .unwrap();
3489 assert!(matches!(known, ClientMessage::KnownOpaque(_)));
3490 let known_frame = decode_frame(&known.encode(ProtocolVersion::V22).unwrap());
3491 assert_eq!(known_frame.protocol_version, 19);
3492 assert_eq!(known_frame.message_id, id.wire_value());
3493 assert_eq!(known_frame.payload, known_payload);
3494 }
3495
3496 for id in [
3497 MessageId::SetHookFlashDetect,
3498 MessageId::StartMediaReception,
3499 MessageId::StopMediaReception,
3500 MessageId::EnunciatorCommand,
3501 MessageId::SpcpRegisterTokenAck,
3502 MessageId::SpcpRegisterTokenReject,
3503 ] {
3504 let known = ServerMessage::decode(
3505 Frame::new(19, id.wire_value(), known_payload.clone()),
3506 ProtocolVersion::V22,
3507 )
3508 .unwrap();
3509 assert!(matches!(known, ServerMessage::KnownOpaque(_)));
3510 let known_frame = decode_frame(&known.encode(ProtocolVersion::V22).unwrap());
3511 assert_eq!(known_frame.protocol_version, 19);
3512 assert_eq!(known_frame.message_id, id.wire_value());
3513 assert_eq!(known_frame.payload, known_payload);
3514 }
3515
3516 let unknown_payload = vec![9, 8, 7, 6];
3517 let unknown = ServerMessage::decode(
3518 Frame::new(19, 0xdead_beef, unknown_payload.clone()),
3519 ProtocolVersion::V19,
3520 )
3521 .unwrap();
3522 assert!(matches!(unknown, ServerMessage::Unknown(_)));
3523 let unknown_frame = decode_frame(&unknown.encode(ProtocolVersion::V22).unwrap());
3524 assert_eq!(unknown_frame.message_id, 0xdead_beef);
3525 assert_eq!(unknown_frame.protocol_version, 19);
3526 assert_eq!(unknown_frame.payload, unknown_payload);
3527 }
3528
3529 #[test]
3530 fn preserve_only_payloads_obey_the_frame_bound() {
3531 let error = ClientMessage::decode_with_version(
3532 Frame::new(
3533 ProtocolVersion::V22.wire(),
3534 MessageId::MediaPortList.wire_value(),
3535 vec![0; MAX_OPAQUE_MESSAGE_BYTES + 1],
3536 ),
3537 ProtocolVersion::V22,
3538 )
3539 .unwrap_err();
3540
3541 assert_eq!(error, CodecError::FrameTooLarge(wire::MAX_FRAME_SIZE + 1));
3542 }
3543
3544 #[test]
3545 fn opaque_encoding_cannot_bypass_a_typed_contract() {
3546 let message = ClientMessage::KnownOpaque(KnownOpaqueMessage {
3547 id: MessageId::IpPort,
3548 protocol_version: ProtocolVersion::V22.wire(),
3549 payload: BoundedBytes::default(),
3550 });
3551
3552 assert!(matches!(
3553 message.encode(ProtocolVersion::V22),
3554 Err(CodecError::InvalidValue {
3555 message_id: id::IP_PORT,
3556 field: "opaque preservation requires an opaque-only contract",
3557 ..
3558 })
3559 ));
3560 }
3561
3562 #[test]
3563 fn malformed_counts_and_oversized_text_are_rejected() {
3564 assert!(matches!(
3565 ClientMessage::decode(Frame::new(
3566 22,
3567 id::CAPABILITIES_RES,
3568 19_u32.to_le_bytes().to_vec(),
3569 )),
3570 Err(CodecError::CountTooLarge { .. })
3571 ));
3572 assert!(matches!(
3573 ServerMessage::DisplayText {
3574 text: "x".repeat(32),
3575 }
3576 .encode(ProtocolVersion::V22),
3577 Err(CodecError::TextTooLong { .. })
3578 ));
3579 assert!(matches!(
3580 ClientMessage::DeviceToUserData(UserDataMessage {
3581 application_id: 1,
3582 line_instance: 1,
3583 call_reference: 1,
3584 transaction_id: 1,
3585 data: vec![0; 2001],
3586 })
3587 .encode(ProtocolVersion::V22),
3588 Err(CodecError::CountTooLarge { .. })
3589 ));
3590 assert!(matches!(
3591 ClientMessage::decode(Frame::new(
3592 22,
3593 id::IP_PORT,
3594 70_000_u32.to_le_bytes().to_vec(),
3595 )),
3596 Err(CodecError::InvalidValue { .. })
3597 ));
3598 assert!(matches!(
3599 ServerMessage::StartMediaTransmission {
3600 call_reference: 1,
3601 passthrough_party_id: 1,
3602 endpoint: MediaEndpoint {
3603 address: "2001:db8::1".parse().unwrap(),
3604 rtp_port: 4000,
3605 rtcp_port: 4001,
3606 codec: Codec::Pcmu,
3607 packet_ms: 20,
3608 max_frames_per_packet: 1,
3609 telephone_event_payload: 101,
3610 },
3611 silence_suppression: SilenceSuppression::Off,
3612 traffic_class: crate::types::MediaTrafficClass::default(),
3613 encryption: None,
3614 wire: None,
3615 }
3616 .encode(ProtocolVersion::V3),
3617 Err(CodecError::InvalidValue { .. })
3618 ));
3619 assert!(matches!(
3620 ControlMessage::CreateConferenceRequest(CreateConferenceRequest {
3621 conference_id: ConferenceId::new(1),
3622 reserved_participants: 2,
3623 resource_type: ConferenceResourceType::Conference,
3624 application_id: ApplicationId::new(1),
3625 application_conference_id: "conference-1".into(),
3626 application_data: String::new(),
3627 passthrough_data: vec![0; 2001],
3628 })
3629 .encode(ProtocolVersion::V22),
3630 Err(CodecError::CountTooLarge {
3631 field: "conference passthrough data",
3632 count: 2001,
3633 maximum: 2000,
3634 ..
3635 })
3636 ));
3637 assert!(matches!(
3638 ControlMessage::AuditConferenceResponse(AuditConferenceResponse {
3639 last: 1,
3640 entries: vec![
3641 AuditConferenceEntry {
3642 conference_id: ConferenceId::new(1),
3643 resource_type: ConferenceResourceType::Conference,
3644 reserved_participants: 2,
3645 active_participants: 1,
3646 application_id: ApplicationId::new(1),
3647 application_conference_id: String::new(),
3648 application_data: String::new(),
3649 };
3650 33
3651 ],
3652 })
3653 .encode(ProtocolVersion::V22),
3654 Err(CodecError::CountTooLarge {
3655 field: "conference audit entries",
3656 count: 33,
3657 maximum: 32,
3658 ..
3659 })
3660 ));
3661
3662 let mut oversized_conference_data = vec![0; 12];
3663 oversized_conference_data[8..12].copy_from_slice(&2001_u32.to_le_bytes());
3664 assert!(matches!(
3665 ControlMessage::decode(
3666 Frame::new(22, id::CREATE_CONFERENCE_RES, oversized_conference_data),
3667 ProtocolVersion::V22,
3668 ),
3669 Err(CodecError::CountTooLarge {
3670 field: "conference passthrough data",
3671 count: 2001,
3672 maximum: 2000,
3673 ..
3674 })
3675 ));
3676
3677 let mut oversized_audit = vec![0; 8];
3678 oversized_audit[4..8].copy_from_slice(&33_u32.to_le_bytes());
3679 assert!(matches!(
3680 ControlMessage::decode(
3681 Frame::new(22, id::AUDIT_CONFERENCE_RES, oversized_audit),
3682 ProtocolVersion::V22,
3683 ),
3684 Err(CodecError::CountTooLarge {
3685 field: "conference audit entries",
3686 count: 33,
3687 maximum: 32,
3688 ..
3689 })
3690 ));
3691 }
3692
3693 #[test]
3694 fn server_response_uses_the_negotiated_address_layout() {
3695 let message = ServerMessage::ServerResponse {
3696 servers: vec![
3697 SignalingServerEndpoint {
3698 name: "primary".into(),
3699 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
3700 port: NonZeroU16::new(2000).unwrap(),
3701 },
3702 SignalingServerEndpoint {
3703 name: "secondary".into(),
3704 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 20)),
3705 port: NonZeroU16::new(2001).unwrap(),
3706 },
3707 ],
3708 };
3709 let v3 = message.encode(ProtocolVersion::V3).unwrap();
3710 let v17 = message.encode(ProtocolVersion::V17).unwrap();
3711 assert_eq!(v3.len(), 292);
3712 assert_eq!(v17.len(), 372);
3713 assert_server_round_trip(message.clone(), ProtocolVersion::V3);
3714 assert_server_round_trip(message, ProtocolVersion::V17);
3715
3716 let mut zero_port = v3;
3717 zero_port[12 + 5 * 48..12 + 5 * 48 + 4].fill(0);
3718 assert!(matches!(
3719 ServerMessage::decode(decode_frame(&zero_port), ProtocolVersion::V3),
3720 Err(CodecError::InvalidValue {
3721 field: "server endpoint",
3722 value: 0,
3723 ..
3724 })
3725 ));
3726 assert_server_round_trip(
3727 ServerMessage::ServerResponse {
3728 servers: vec![SignalingServerEndpoint {
3729 name: "sccp-v6".into(),
3730 address: "2001:db8::20".parse().unwrap(),
3731 port: NonZeroU16::new(2000).unwrap(),
3732 }],
3733 },
3734 ProtocolVersion::V17,
3735 );
3736
3737 let unspecified = ServerMessage::ServerResponse {
3738 servers: vec![SignalingServerEndpoint {
3739 name: "unroutable".into(),
3740 address: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
3741 port: NonZeroU16::new(2000).unwrap(),
3742 }],
3743 };
3744 assert!(matches!(
3745 unspecified.encode(ProtocolVersion::V17),
3746 Err(CodecError::InvalidValue {
3747 field: "server address",
3748 value: 0,
3749 ..
3750 })
3751 ));
3752
3753 let endpoints = |count: u8| {
3754 (0..count)
3755 .map(|index| SignalingServerEndpoint {
3756 name: format!("node-{index}"),
3757 address: IpAddr::V4(Ipv4Addr::new(192, 0, 2, index + 1)),
3758 port: NonZeroU16::new(2000).unwrap(),
3759 })
3760 .collect()
3761 };
3762 let empty = ServerMessage::ServerResponse {
3763 servers: Vec::new(),
3764 };
3765 assert!(matches!(
3766 empty.encode(ProtocolVersion::V17),
3767 Err(CodecError::InvalidValue {
3768 field: "server endpoints",
3769 value: 0,
3770 ..
3771 })
3772 ));
3773 assert_server_round_trip(
3774 ServerMessage::ServerResponse {
3775 servers: endpoints(5),
3776 },
3777 ProtocolVersion::V17,
3778 );
3779 let too_many = ServerMessage::ServerResponse {
3780 servers: endpoints(6),
3781 };
3782 assert!(matches!(
3783 too_many.encode(ProtocolVersion::V17),
3784 Err(CodecError::CountTooLarge {
3785 field: "server endpoints",
3786 count: 6,
3787 maximum: 5,
3788 ..
3789 })
3790 ));
3791 }
3792}