1use std::fmt;
13
14use super::wire::{HEADER_SIZE, MAX_FRAME_SIZE};
15
16#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24pub enum MessageRoute {
25 StationToControl,
26 ControlToStation,
27 ControlToServiceNode,
28 ServiceNodeToControl,
29 IntraControl,
30}
31
32#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37pub enum MessageDirection {
38 DeviceToServer,
39 ServerToDevice,
40}
41
42#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44pub enum CodecSupport {
45 Typed,
47 OpaqueOnly,
49}
50
51#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
53pub enum PayloadLayout {
54 Empty,
56 Fixed,
58 VersionSelected,
60 VersionAndLengthSelected,
62 MinimumLengthPreserved,
64 LengthPrefixed,
66 BoundedPreserved,
68 BoundedOpaque,
71 DynamicWordPadded,
74 Opaque,
76}
77
78#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
81pub enum EmissionSupport {
82 Typed,
84 PreserveOnly,
87}
88
89#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
91pub enum RuntimeUse {
92 DeviceInput,
94 RequiredResponse,
96 ConditionalServerOutput,
99 ServiceNodeInput,
101 ConditionalServiceNodeOutput,
103 TypedButNotEmitted,
106 CatalogOnly,
108}
109
110#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
112pub enum FieldFidelity {
113 Lossless,
116 CanonicalServerOutput(&'static str),
119 SemanticProjection(&'static str),
121 OpaquePreserved,
123}
124
125#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
127pub enum ResponseExpectation {
128 None,
129 Message(MessageId),
130 VersionSelected {
132 before: MessageId,
134 from: MessageId,
136 minimum_protocol: u8,
138 },
139 SessionSelected {
141 before: MessageId,
143 from: MessageId,
145 selector: SessionResponseSelector,
147 },
148 OneOf(&'static [MessageId]),
150}
151
152#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
154pub enum SessionResponseSelector {
155 DynamicMessagesOrProtocol { minimum_protocol: u8 },
158 DynamicMessages,
160}
161
162#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
164pub enum ContractVerification {
165 Structural,
166 StructuralAndValidated,
167}
168
169#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
172pub enum ContractScope {
173 Base,
174 Supplemental,
175}
176
177#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
179pub struct PayloadSizeBounds {
180 pub minimum: usize,
182 pub maximum: usize,
184}
185
186#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
194pub struct MessageContract {
195 pub id: MessageId,
196 pub scope: ContractScope,
197 pub route: MessageRoute,
198 pub codec: CodecSupport,
199 pub payload_layout: PayloadLayout,
200 pub fixed_payload_bytes: Option<usize>,
204 pub payload_size_bounds: Option<PayloadSizeBounds>,
206 pub emission: EmissionSupport,
208 pub runtime_use: RuntimeUse,
210 pub field_fidelity: FieldFidelity,
212 pub response: ResponseExpectation,
214 pub verification: ContractVerification,
216}
217
218macro_rules! message_catalog {
219 ($(($variant:ident, $value:expr, $route:ident)),+ $(,)?) => {
220 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
224 pub enum MessageId {
225 $($variant,)+
226 Unknown(u32),
227 }
228
229 impl MessageId {
230 pub const ALL_KNOWN: &'static [Self] = &[$(Self::$variant,)+];
231
232 pub const fn wire_value(self) -> u32 {
233 match self {
234 $(Self::$variant => $value,)+
235 Self::Unknown(value) => value,
236 }
237 }
238
239 pub const fn route(self) -> Option<MessageRoute> {
244 match self {
245 $(Self::$variant => Some(MessageRoute::$route),)+
246 Self::Unknown(_) => None,
247 }
248 }
249
250 pub const fn direction(self) -> Option<MessageDirection> {
252 match self.route() {
253 Some(MessageRoute::StationToControl) => {
254 Some(MessageDirection::DeviceToServer)
255 }
256 Some(MessageRoute::ControlToStation) => {
257 Some(MessageDirection::ServerToDevice)
258 }
259 Some(MessageRoute::ControlToServiceNode)
260 | Some(MessageRoute::ServiceNodeToControl)
261 | Some(MessageRoute::IntraControl)
262 | None => None,
263 }
264 }
265
266 pub const fn name(self) -> &'static str {
267 match self {
268 $(Self::$variant => stringify!($variant),)+
269 Self::Unknown(_) => "Unknown",
270 }
271 }
272
273 pub const fn is_known(self) -> bool {
274 !matches!(self, Self::Unknown(_))
275 }
276
277 pub const fn contract(self) -> Option<MessageContract> {
279 let route = match self.route() {
280 Some(route) => route,
281 None => return None,
282 };
283 let codec = codec_support(self);
284 Some(MessageContract {
285 id: self,
286 scope: contract_scope(self),
287 route,
288 codec,
289 payload_layout: payload_layout(self, codec),
290 fixed_payload_bytes: fixed_payload_bytes(self),
291 payload_size_bounds: payload_size_bounds(self),
292 emission: match codec {
293 CodecSupport::Typed => EmissionSupport::Typed,
294 CodecSupport::OpaqueOnly => EmissionSupport::PreserveOnly,
295 },
296 runtime_use: runtime_use(self, route, codec),
297 field_fidelity: field_fidelity(self),
298 response: primary_response(self),
299 verification: verification(self),
300 })
301 }
302 }
303
304 impl From<u32> for MessageId {
305 fn from(value: u32) -> Self {
306 match value {
307 $($value => Self::$variant,)+
308 value => Self::Unknown(value),
309 }
310 }
311 }
312 };
313}
314
315const fn contract_scope(id: MessageId) -> ContractScope {
316 use MessageId::*;
317 match id {
318 IpPort
319 | MediaPortList
320 | SetHookFlashDetect
321 | StartMediaReception
322 | StopMediaReception
323 | EnunciatorCommand
324 | ExtensionDeviceCapabilities
325 | SpcpRegisterTokenRequest
326 | SpcpRegisterTokenAck
327 | SpcpRegisterTokenReject => ContractScope::Supplemental,
328 _ => ContractScope::Base,
329 }
330}
331
332const fn field_fidelity(id: MessageId) -> FieldFidelity {
336 use MessageId::*;
337 match id {
338 MediaPortList
339 | SetHookFlashDetect
340 | StartMediaReception
341 | StopMediaReception
342 | EnunciatorCommand
343 | SpcpRegisterTokenRequest
344 | SpcpRegisterTokenAck
345 | SpcpRegisterTokenReject
346 | Unknown(_) => FieldFidelity::OpaquePreserved,
347
348 KeepAlive
349 | ConfigStatusRequest
350 | TimeDateRequest
351 | VersionRequest
352 | ServerRequest
353 | SoftKeySetRequest
354 | SoftKeyTemplateRequest => FieldFidelity::SemanticProjection(
355 "nominally empty request; bounded extension bytes are accepted but not modeled",
356 ),
357 ButtonTemplateRequest => FieldFidelity::SemanticProjection(
358 "the optional total-button-count request word is accepted but not modeled",
359 ),
360 Register => FieldFidelity::Lossless,
361 CapabilitiesResponse => FieldFidelity::SemanticProjection(
362 "advertised capability count; inactive fixed-array entries are not modeled",
363 ),
364 RegisterTokenRequest => FieldFidelity::SemanticProjection(
365 "simultaneously populated IPv4 and IPv6 station addresses collapse to one address",
366 ),
367 Unregister => FieldFidelity::SemanticProjection(
368 "an empty reason-zero body is accepted and normalized to the typed reason",
369 ),
370 MediaTransmissionFailure => FieldFidelity::SemanticProjection(
371 "the public status is synthesized because the failure wire layouts carry no status",
372 ),
373 HeadsetStatus => FieldFidelity::SemanticProjection(
374 "non-canonical raw states are projected onto a boolean",
375 ),
376 RegisterAvailableLines => FieldFidelity::SemanticProjection(
377 "an absent or short legacy body is projected onto zero available lines",
378 ),
379 PortResponse => FieldFidelity::SemanticProjection(
380 "pre-v20 bodies omit media type, which is synthesized on decode",
381 ),
382 Alarm => FieldFidelity::Lossless,
383
384 RegisterAck | ConfigStatus | ConfigStatusDynamic => FieldFidelity::Lossless,
385 LineStatus | LineStatusDynamic => FieldFidelity::CanonicalServerOutput(
386 "display label/fully-qualified display name and display-options word",
387 ),
388 ServerResponse => {
389 FieldFidelity::SemanticProjection("empty server-list slot positions are not retained")
390 }
391 DefineTimeDate => FieldFidelity::Lossless,
392 CallState => FieldFidelity::CanonicalServerOutput("visibility, precedence and domain"),
393 CallInfo | CallInfoDynamic => FieldFidelity::CanonicalServerOutput(
394 "mailboxes, call instance/security and version-selected party metadata",
395 ),
396 ForwardStatus => FieldFidelity::CanonicalServerOutput(
397 "aggregate active flag and inactive forwarding-number slots",
398 ),
399 StopTone => FieldFidelity::CanonicalServerOutput("post-v11 tone word"),
400 ButtonTemplate | SoftKeyTemplateResponse | SoftKeySetResponse => {
401 FieldFidelity::CanonicalServerOutput(
402 "template offsets/count metadata and unused fixed-array entries",
403 )
404 }
405 ConnectionStatisticsRequest => {
406 FieldFidelity::CanonicalServerOutput("post-v18 directory-number alignment bytes")
407 }
408 ClearDisplay => FieldFidelity::CanonicalServerOutput("display-control word"),
409 KeepAliveAck | CapabilitiesRequest | ClearNotify | DeactivateCallPlane
410 | RegisterTokenAck | CallCountResponse => FieldFidelity::CanonicalServerOutput(
411 "nominally empty response; accepted extension bytes are not modeled",
412 ),
413 UnregisterAck => FieldFidelity::CanonicalServerOutput("acknowledgement body word"),
414 StartAnnouncement => FieldFidelity::CanonicalServerOutput(
415 "unused announcement and conference-party array entries",
416 ),
417
418 IpPort
419 | KeypadButton
420 | EnblocCall
421 | Stimulus
422 | OffHook
423 | OnHook
424 | HookFlash
425 | ForwardStatusRequest
426 | SpeedDialStatusRequest
427 | LineStatusRequest
428 | MulticastMediaReceptionAck
429 | OpenReceiveChannelAck
430 | ConnectionStatisticsResponse
431 | OffHookWithCallingParty
432 | SoftKeyEvent
433 | MediaResourceNotification
434 | DeviceToUserData
435 | DeviceToUserDataResponse
436 | UpdateCapabilities
437 | ClearConference
438 | ServiceUrlStatusRequest
439 | FeatureStatusRequest
440 | CreateConferenceResponse
441 | DeleteConferenceResponse
442 | ModifyConferenceResponse
443 | AddParticipantResponse
444 | AuditConferenceResponse
445 | AuditParticipantResponse
446 | DeviceToUserDataV1
447 | DeviceToUserDataResponseV1
448 | UpdateCapabilitiesV2
449 | UpdateCapabilitiesV3
450 | QosReservationNotify
451 | QosErrorNotify
452 | SubscriptionStatusRequest
453 | MediaPathEvent
454 | StartTone
455 | SetRinger
456 | SetLamp
457 | SetSpeakerMode
458 | SetMicrophoneMode
459 | StartMediaTransmission
460 | CloseReceiveChannel
461 | StopMediaTransmission
462 | SpeedDialStatus
463 | Version
464 | DisplayText
465 | RegisterReject
466 | Reset
467 | StartMulticastMediaReception
468 | StartMulticastMediaTransmission
469 | StopMulticastMediaReception
470 | StopMulticastMediaTransmission
471 | OpenReceiveChannel
472 | SelectSoftKeys
473 | DisplayPromptStatus
474 | ClearPromptStatus
475 | DisplayNotify
476 | ActivateCallPlane
477 | BackspaceResponse
478 | RegisterTokenReject
479 | DialedNumber
480 | UserToDeviceData
481 | FeatureStatus
482 | DisplayPriorityNotify
483 | ClearPriorityNotify
484 | StopAnnouncement
485 | AnnouncementFinish
486 | SubscribeDtmfPayloadRequest
487 | SubscribeDtmfPayloadResponse
488 | SubscribeDtmfPayloadError
489 | UnsubscribeDtmfPayloadRequest
490 | UnsubscribeDtmfPayloadResponse
491 | UnsubscribeDtmfPayloadError
492 | ServiceUrlStatus
493 | CallSelectStatus
494 | CreateConferenceRequest
495 | DeleteConferenceRequest
496 | ModifyConferenceRequest
497 | AddParticipantRequest
498 | DropParticipantRequest
499 | AuditConferenceRequest
500 | AuditParticipantRequest
501 | ChangeParticipantRequest
502 | UserToDeviceDataV1
503 | DisplayDynamicNotify
504 | DisplayDynamicPriorityNotify
505 | DisplayDynamicPromptStatus
506 | FeatureStatusDynamic
507 | ServiceUrlStatusDynamic
508 | SpeedDialStatusDynamic
509 | PortRequest
510 | PortClose
511 | QosListen
512 | QosPath
513 | QosTeardown
514 | UpdateDscp
515 | QosModify
516 | SubscriptionStatus
517 | Notification
518 | StartMediaTransmissionAck
519 | CallHistoryDisposition
520 | LocationInfo
521 | XmlAlarm
522 | CallCountRequest
523 | RecordingStatus
524 | MediaPathCapability => FieldFidelity::Lossless,
525 StartSessionTransmission
526 | StopSessionTransmission
527 | OpenMultimediaChannel
528 | StartMultimediaTransmission
529 | MiscellaneousCommand => FieldFidelity::Lossless,
530 StartMediaFailureDetection => FieldFidelity::Lossless,
531 MwiNotification
532 | MwiResponse
533 | OpenMultimediaReceiveChannelAck
534 | StartMultimediaTransmissionAck
535 | ExtensionDeviceCapabilities
536 | NotifyDtmfTone
537 | SendDtmfTone
538 | StopMultimediaTransmission
539 | FlowControlCommand
540 | CloseMultimediaReceiveChannel
541 | VideoDisplayCommand
542 | FlowControlNotify => FieldFidelity::Lossless,
543 }
544}
545
546const fn runtime_use(id: MessageId, route: MessageRoute, support: CodecSupport) -> RuntimeUse {
547 use MessageId::*;
548 if matches!(support, CodecSupport::OpaqueOnly) {
549 return RuntimeUse::CatalogOnly;
550 }
551 if matches!(route, MessageRoute::StationToControl) {
552 return RuntimeUse::DeviceInput;
553 }
554 if matches!(route, MessageRoute::ServiceNodeToControl) {
555 return match id {
556 QosReservationNotify | QosErrorNotify => RuntimeUse::ServiceNodeInput,
557 _ => RuntimeUse::TypedButNotEmitted,
558 };
559 }
560 if matches!(route, MessageRoute::ControlToServiceNode) {
561 return match id {
562 QosListen | QosPath | QosTeardown | UpdateDscp | QosModify => {
563 RuntimeUse::ConditionalServiceNodeOutput
564 }
565 _ => RuntimeUse::TypedButNotEmitted,
566 };
567 }
568 if !matches!(route, MessageRoute::ControlToStation) {
569 return RuntimeUse::TypedButNotEmitted;
570 }
571 match id {
572 RegisterAck
573 | RegisterReject
574 | KeepAliveAck
575 | UnregisterAck
576 | CapabilitiesRequest
577 | ConfigStatus
578 | LineStatus
579 | LineStatusDynamic
580 | ButtonTemplate
581 | Version
582 | ServerResponse
583 | DefineTimeDate
584 | SoftKeyTemplateResponse
585 | SoftKeySetResponse
586 | RegisterTokenAck
587 | RegisterTokenReject
588 | FeatureStatus
589 | FeatureStatusDynamic
590 | ServiceUrlStatus
591 | ServiceUrlStatusDynamic
592 | CallCountResponse => RuntimeUse::RequiredResponse,
593
594 StartMulticastMediaReception
595 | StartMulticastMediaTransmission
596 | StopMulticastMediaReception
597 | StopMulticastMediaTransmission
598 | StartSessionTransmission
599 | StopSessionTransmission
600 | ClearConference
601 | DisplayNotify
602 | DisplayDynamicNotify
603 | ClearNotify
604 | DeactivateCallPlane
605 | UserToDeviceData
606 | SubscribeDtmfPayloadRequest
607 | SubscribeDtmfPayloadError
608 | UnsubscribeDtmfPayloadRequest
609 | UnsubscribeDtmfPayloadError
610 | CreateConferenceRequest
611 | DeleteConferenceRequest
612 | ModifyConferenceRequest
613 | AddParticipantRequest
614 | DropParticipantRequest
615 | AuditConferenceRequest
616 | AuditParticipantRequest
617 | ChangeParticipantRequest => RuntimeUse::TypedButNotEmitted,
618
619 _ => RuntimeUse::ConditionalServerOutput,
620 }
621}
622
623const fn codec_support(id: MessageId) -> CodecSupport {
624 use MessageId::*;
625 match id {
626 MediaPortList
627 | SetHookFlashDetect
628 | StartMediaReception
629 | StopMediaReception
630 | EnunciatorCommand
631 | SpcpRegisterTokenRequest
632 | SpcpRegisterTokenAck
633 | SpcpRegisterTokenReject => CodecSupport::OpaqueOnly,
634 KeepAlive
635 | Register
636 | IpPort
637 | KeypadButton
638 | EnblocCall
639 | Stimulus
640 | OffHook
641 | OnHook
642 | HookFlash
643 | ForwardStatusRequest
644 | SpeedDialStatusRequest
645 | LineStatusRequest
646 | ConfigStatusRequest
647 | TimeDateRequest
648 | ButtonTemplateRequest
649 | VersionRequest
650 | CapabilitiesResponse
651 | ServerRequest
652 | Alarm
653 | MulticastMediaReceptionAck
654 | OpenReceiveChannelAck
655 | ConnectionStatisticsResponse
656 | OffHookWithCallingParty
657 | SoftKeySetRequest
658 | SoftKeyEvent
659 | Unregister
660 | SoftKeyTemplateRequest
661 | RegisterTokenRequest
662 | MediaTransmissionFailure
663 | HeadsetStatus
664 | MediaResourceNotification
665 | RegisterAvailableLines
666 | DeviceToUserData
667 | DeviceToUserDataResponse
668 | UpdateCapabilities
669 | ClearConference
670 | ServiceUrlStatusRequest
671 | FeatureStatusRequest
672 | CreateConferenceResponse
673 | DeleteConferenceResponse
674 | ModifyConferenceResponse
675 | AddParticipantResponse
676 | AuditConferenceResponse
677 | AuditParticipantResponse
678 | DeviceToUserDataV1
679 | DeviceToUserDataResponseV1
680 | UpdateCapabilitiesV2
681 | UpdateCapabilitiesV3
682 | PortResponse
683 | QosReservationNotify
684 | QosErrorNotify
685 | SubscriptionStatusRequest
686 | MediaPathEvent
687 | StartMediaFailureDetection
688 | RegisterAck
689 | StartTone
690 | StopTone
691 | SetRinger
692 | SetLamp
693 | SetSpeakerMode
694 | SetMicrophoneMode
695 | StartMediaTransmission
696 | StopMediaTransmission
697 | CallInfo
698 | ForwardStatus
699 | SpeedDialStatus
700 | LineStatus
701 | ConfigStatus
702 | DefineTimeDate
703 | ButtonTemplate
704 | Version
705 | DisplayText
706 | ClearDisplay
707 | CapabilitiesRequest
708 | RegisterReject
709 | ServerResponse
710 | Reset
711 | KeepAliveAck
712 | StartMulticastMediaReception
713 | StartMulticastMediaTransmission
714 | StopMulticastMediaReception
715 | StopMulticastMediaTransmission
716 | OpenReceiveChannel
717 | CloseReceiveChannel
718 | ConnectionStatisticsRequest
719 | SoftKeyTemplateResponse
720 | SoftKeySetResponse
721 | SelectSoftKeys
722 | CallState
723 | DisplayPromptStatus
724 | ClearPromptStatus
725 | DisplayNotify
726 | ClearNotify
727 | ActivateCallPlane
728 | DeactivateCallPlane
729 | UnregisterAck
730 | BackspaceResponse
731 | RegisterTokenAck
732 | RegisterTokenReject
733 | DialedNumber
734 | UserToDeviceData
735 | FeatureStatus
736 | DisplayPriorityNotify
737 | ClearPriorityNotify
738 | StartAnnouncement
739 | StopAnnouncement
740 | AnnouncementFinish
741 | SubscribeDtmfPayloadRequest
742 | SubscribeDtmfPayloadResponse
743 | SubscribeDtmfPayloadError
744 | UnsubscribeDtmfPayloadRequest
745 | UnsubscribeDtmfPayloadResponse
746 | UnsubscribeDtmfPayloadError
747 | ServiceUrlStatus
748 | CallSelectStatus
749 | CreateConferenceRequest
750 | DeleteConferenceRequest
751 | ModifyConferenceRequest
752 | AddParticipantRequest
753 | DropParticipantRequest
754 | AuditConferenceRequest
755 | AuditParticipantRequest
756 | ChangeParticipantRequest
757 | UserToDeviceDataV1
758 | DisplayDynamicNotify
759 | DisplayDynamicPriorityNotify
760 | DisplayDynamicPromptStatus
761 | FeatureStatusDynamic
762 | LineStatusDynamic
763 | ServiceUrlStatusDynamic
764 | SpeedDialStatusDynamic
765 | CallInfoDynamic
766 | PortRequest
767 | PortClose
768 | QosListen
769 | QosPath
770 | QosTeardown
771 | UpdateDscp
772 | QosModify
773 | SubscriptionStatus
774 | Notification
775 | StartMediaTransmissionAck
776 | CallHistoryDisposition
777 | LocationInfo
778 | XmlAlarm
779 | CallCountRequest
780 | CallCountResponse
781 | RecordingStatus
782 | MediaPathCapability => CodecSupport::Typed,
783 StartSessionTransmission
784 | StopSessionTransmission
785 | OpenMultimediaChannel
786 | StartMultimediaTransmission
787 | MiscellaneousCommand => CodecSupport::Typed,
788 MwiNotification
789 | MwiResponse
790 | OpenMultimediaReceiveChannelAck
791 | StartMultimediaTransmissionAck
792 | ExtensionDeviceCapabilities
793 | NotifyDtmfTone
794 | SendDtmfTone
795 | StopMultimediaTransmission
796 | FlowControlCommand
797 | CloseMultimediaReceiveChannel
798 | VideoDisplayCommand
799 | FlowControlNotify => CodecSupport::Typed,
800 ConfigStatusDynamic => CodecSupport::Typed,
801 Unknown(_) => CodecSupport::OpaqueOnly,
802 }
803}
804
805const fn payload_layout(id: MessageId, support: CodecSupport) -> PayloadLayout {
806 use MessageId::*;
807 if matches!(support, CodecSupport::OpaqueOnly) {
808 return PayloadLayout::Opaque;
809 }
810 match id {
811 KeepAlive
812 | ConfigStatusRequest
813 | TimeDateRequest
814 | ButtonTemplateRequest
815 | VersionRequest
816 | ServerRequest
817 | SoftKeySetRequest
818 | SoftKeyTemplateRequest
819 | KeepAliveAck
820 | CapabilitiesRequest
821 | ClearDisplay
822 | ClearNotify
823 | DeactivateCallPlane
824 | RegisterTokenAck
825 | CallCountResponse => PayloadLayout::Empty,
826
827 UpdateCapabilities | KeypadButton | EnblocCall => PayloadLayout::VersionAndLengthSelected,
828
829 Register | UpdateCapabilitiesV3 | AddParticipantResponse => {
830 PayloadLayout::MinimumLengthPreserved
831 }
832
833 CapabilitiesResponse => PayloadLayout::LengthPrefixed,
834
835 UpdateCapabilitiesV2 => PayloadLayout::Fixed,
836
837 XmlAlarm => PayloadLayout::BoundedPreserved,
838
839 OpenReceiveChannelAck
840 | ConnectionStatisticsResponse
841 | MediaTransmissionFailure
842 | PortResponse
843 | ServerResponse
844 | ForwardStatus
845 | DialedNumber
846 | OpenReceiveChannel
847 | ConnectionStatisticsRequest
848 | StartMediaTransmission
849 | StartMulticastMediaReception
850 | StartMulticastMediaTransmission
851 | OpenMultimediaReceiveChannelAck
852 | StartMultimediaTransmissionAck
853 | StartSessionTransmission
854 | StopSessionTransmission
855 | OpenMultimediaChannel
856 | StartMultimediaTransmission
857 | PortRequest
858 | PortClose => PayloadLayout::VersionSelected,
859
860 StartMediaTransmissionAck => PayloadLayout::VersionAndLengthSelected,
861
862 DeviceToUserData
863 | DeviceToUserDataResponse
864 | DeviceToUserDataV1
865 | DeviceToUserDataResponseV1
866 | CreateConferenceResponse
867 | ModifyConferenceResponse
868 | AuditConferenceResponse
869 | UserToDeviceData
870 | UserToDeviceDataV1
871 | CreateConferenceRequest
872 | ModifyConferenceRequest => PayloadLayout::LengthPrefixed,
873
874 AuditParticipantResponse => PayloadLayout::BoundedOpaque,
875
876 DisplayDynamicNotify
877 | DisplayDynamicPriorityNotify
878 | DisplayDynamicPromptStatus
879 | ConfigStatusDynamic
880 | LineStatusDynamic
881 | ServiceUrlStatusDynamic
882 | CallInfoDynamic => PayloadLayout::DynamicWordPadded,
883
884 _ => PayloadLayout::Fixed,
885 }
886}
887
888const fn fixed_payload_bytes(id: MessageId) -> Option<usize> {
889 use MessageId::*;
890 match id {
891 KeepAlive
892 | ConfigStatusRequest
893 | TimeDateRequest
894 | ButtonTemplateRequest
895 | VersionRequest
896 | ServerRequest
897 | SoftKeySetRequest
898 | SoftKeyTemplateRequest
899 | KeepAliveAck
900 | CapabilitiesRequest
901 | ClearDisplay
902 | ClearNotify
903 | DeactivateCallPlane
904 | RegisterTokenAck
905 | CallCountResponse => Some(0),
906 RegisterAck => Some(20),
907 UpdateCapabilitiesV2 => Some(2_000),
908 MwiNotification => Some(88),
909 MwiResponse => Some(32),
910 LineStatus => Some(112),
911 DefineTimeDate => Some(36),
912 AddParticipantResponse => Some(272),
913 AuditConferenceRequest => Some(0),
914 SubscribeDtmfPayloadRequest | UnsubscribeDtmfPayloadRequest => Some(16),
915 SubscribeDtmfPayloadResponse
916 | SubscribeDtmfPayloadError
917 | UnsubscribeDtmfPayloadResponse
918 | UnsubscribeDtmfPayloadError => Some(12),
919 ButtonTemplate => Some(96),
920 LocationInfo => Some(2_404),
921 SoftKeyTemplateResponse => Some(652),
922 SoftKeySetResponse => Some(780),
923 MulticastMediaReceptionAck => Some(12),
924 NotifyDtmfTone | SendDtmfTone | VideoDisplayCommand => Some(12),
925 StopMediaTransmission
926 | CloseReceiveChannel
927 | StopMultimediaTransmission
928 | FlowControlCommand
929 | CloseMultimediaReceiveChannel
930 | FlowControlNotify => Some(16),
931 MiscellaneousCommand => Some(52),
932 ExtensionDeviceCapabilities => Some(164),
933 StartMediaFailureDetection => Some(28),
934 QosReservationNotify | QosTeardown | UpdateDscp => Some(24),
935 QosErrorNotify => Some(44),
936 QosListen => Some(172),
937 QosPath => Some(168),
938 QosModify => Some(152),
939 _ => None,
940 }
941}
942
943const fn payload_size_bounds(id: MessageId) -> Option<PayloadSizeBounds> {
944 use MessageId::*;
945 const MAX_PAYLOAD_BYTES: usize = MAX_FRAME_SIZE - HEADER_SIZE;
946 match id {
947 KeepAlive
948 | ConfigStatusRequest
949 | TimeDateRequest
950 | ButtonTemplateRequest
951 | VersionRequest
952 | ServerRequest
953 | SoftKeySetRequest
954 | SoftKeyTemplateRequest
955 | KeepAliveAck
956 | CapabilitiesRequest
957 | ClearDisplay
958 | ClearNotify
959 | DeactivateCallPlane
960 | RegisterTokenAck
961 | CallCountResponse => Some(PayloadSizeBounds {
962 minimum: 0,
963 maximum: MAX_PAYLOAD_BYTES,
964 }),
965 Register => Some(PayloadSizeBounds {
966 minimum: 124,
967 maximum: 172,
968 }),
969 XmlAlarm => Some(PayloadSizeBounds {
970 minimum: 0,
971 maximum: 2_048,
972 }),
973 AddParticipantResponse => Some(PayloadSizeBounds {
974 minimum: 12,
975 maximum: 272,
976 }),
977 CapabilitiesResponse => Some(PayloadSizeBounds {
978 minimum: 4,
979 maximum: 292,
980 }),
981 UpdateCapabilitiesV3 => Some(PayloadSizeBounds {
982 minimum: 20,
983 maximum: 2_380,
984 }),
985 _ => match fixed_payload_bytes(id) {
986 Some(size) => Some(PayloadSizeBounds {
987 minimum: size,
988 maximum: size,
989 }),
990 None => None,
991 },
992 }
993}
994
995const CAPABILITY_RESPONSES: &[MessageId] = &[
996 MessageId::CapabilitiesResponse,
997 MessageId::UpdateCapabilities,
998 MessageId::UpdateCapabilitiesV2,
999 MessageId::UpdateCapabilitiesV3,
1000];
1001
1002const REGISTER_TOKEN_RESPONSES: &[MessageId] =
1003 &[MessageId::RegisterTokenAck, MessageId::RegisterTokenReject];
1004
1005const fn primary_response(id: MessageId) -> ResponseExpectation {
1006 use MessageId::*;
1007 match id {
1008 Register => ResponseExpectation::Message(RegisterAck),
1009 KeepAlive => ResponseExpectation::Message(KeepAliveAck),
1010 Unregister => ResponseExpectation::Message(UnregisterAck),
1011 ConfigStatusRequest => ResponseExpectation::SessionSelected {
1012 before: ConfigStatus,
1013 from: ConfigStatusDynamic,
1014 selector: SessionResponseSelector::DynamicMessagesOrProtocol {
1015 minimum_protocol: 9,
1016 },
1017 },
1018 TimeDateRequest => ResponseExpectation::Message(DefineTimeDate),
1019 ButtonTemplateRequest => ResponseExpectation::Message(ButtonTemplate),
1020 VersionRequest => ResponseExpectation::Message(Version),
1021 CapabilitiesRequest => ResponseExpectation::OneOf(CAPABILITY_RESPONSES),
1022 ServerRequest => ResponseExpectation::Message(ServerResponse),
1023 OpenReceiveChannel => ResponseExpectation::Message(OpenReceiveChannelAck),
1024 ConnectionStatisticsRequest => ResponseExpectation::Message(ConnectionStatisticsResponse),
1025 SoftKeySetRequest => ResponseExpectation::Message(SoftKeySetResponse),
1026 SoftKeyTemplateRequest => ResponseExpectation::Message(SoftKeyTemplateResponse),
1027 RegisterTokenRequest => ResponseExpectation::OneOf(REGISTER_TOKEN_RESPONSES),
1028 LineStatusRequest => ResponseExpectation::SessionSelected {
1029 before: LineStatus,
1030 from: LineStatusDynamic,
1031 selector: SessionResponseSelector::DynamicMessagesOrProtocol {
1032 minimum_protocol: 9,
1033 },
1034 },
1035 SpeedDialStatusRequest => ResponseExpectation::VersionSelected {
1036 before: SpeedDialStatus,
1037 from: SpeedDialStatusDynamic,
1038 minimum_protocol: 15,
1039 },
1040 ServiceUrlStatusRequest => ResponseExpectation::SessionSelected {
1041 before: ServiceUrlStatus,
1042 from: ServiceUrlStatusDynamic,
1043 selector: SessionResponseSelector::DynamicMessagesOrProtocol {
1044 minimum_protocol: 9,
1045 },
1046 },
1047 FeatureStatusRequest => ResponseExpectation::SessionSelected {
1048 before: FeatureStatus,
1049 from: FeatureStatusDynamic,
1050 selector: SessionResponseSelector::DynamicMessages,
1051 },
1052 CreateConferenceRequest => ResponseExpectation::Message(CreateConferenceResponse),
1053 DeleteConferenceRequest => ResponseExpectation::Message(DeleteConferenceResponse),
1054 ModifyConferenceRequest => ResponseExpectation::Message(ModifyConferenceResponse),
1055 AddParticipantRequest => ResponseExpectation::Message(AddParticipantResponse),
1056 AuditConferenceRequest => ResponseExpectation::Message(AuditConferenceResponse),
1057 AuditParticipantRequest => ResponseExpectation::Message(AuditParticipantResponse),
1058 PortRequest => ResponseExpectation::Message(PortResponse),
1059 SubscribeDtmfPayloadRequest => ResponseExpectation::Message(SubscribeDtmfPayloadResponse),
1060 UnsubscribeDtmfPayloadRequest => {
1061 ResponseExpectation::Message(UnsubscribeDtmfPayloadResponse)
1062 }
1063 StartMediaTransmission => ResponseExpectation::Message(StartMediaTransmissionAck),
1064 OpenMultimediaChannel => ResponseExpectation::Message(OpenMultimediaReceiveChannelAck),
1065 StartMultimediaTransmission => ResponseExpectation::Message(StartMultimediaTransmissionAck),
1066 CallCountRequest => ResponseExpectation::Message(CallCountResponse),
1067 _ => ResponseExpectation::None,
1068 }
1069}
1070
1071const fn verification(id: MessageId) -> ContractVerification {
1072 use MessageId::*;
1073 match id {
1074 KeepAlive
1075 | Register
1076 | OffHook
1077 | OnHook
1078 | SoftKeyEvent
1079 | UpdateCapabilities
1080 | MediaPathEvent
1081 | OpenReceiveChannelAck
1082 | RegisterAck
1083 | SetRinger
1084 | SetLamp
1085 | StartMediaTransmission
1086 | ButtonTemplate
1087 | DefineTimeDate
1088 | SoftKeyTemplateResponse
1089 | SoftKeySetResponse
1090 | SelectSoftKeys
1091 | CallState
1092 | ActivateCallPlane
1093 | ClearPromptStatus
1094 | DisplayDynamicPromptStatus
1095 | LineStatusDynamic
1096 | CallInfoDynamic
1097 | OpenReceiveChannel
1098 | CloseReceiveChannel
1099 | StartMediaTransmissionAck => ContractVerification::StructuralAndValidated,
1100 _ => ContractVerification::Structural,
1101 }
1102}
1103
1104message_catalog! {
1105 (KeepAlive, 0x0000, StationToControl),
1106 (Register, 0x0001, StationToControl),
1107 (IpPort, 0x0002, StationToControl),
1108 (KeypadButton, 0x0003, StationToControl),
1109 (EnblocCall, 0x0004, StationToControl),
1110 (Stimulus, 0x0005, StationToControl),
1111 (OffHook, 0x0006, StationToControl),
1112 (OnHook, 0x0007, StationToControl),
1113 (HookFlash, 0x0008, StationToControl),
1114 (ForwardStatusRequest, 0x0009, StationToControl),
1115 (SpeedDialStatusRequest, 0x000a, StationToControl),
1116 (LineStatusRequest, 0x000b, StationToControl),
1117 (ConfigStatusRequest, 0x000c, StationToControl),
1118 (TimeDateRequest, 0x000d, StationToControl),
1119 (ButtonTemplateRequest, 0x000e, StationToControl),
1120 (VersionRequest, 0x000f, StationToControl),
1121 (CapabilitiesResponse, 0x0010, StationToControl),
1122 (MediaPortList, 0x0011, StationToControl),
1123 (ServerRequest, 0x0012, StationToControl),
1124 (Alarm, 0x0020, StationToControl),
1125 (MulticastMediaReceptionAck, 0x0021, StationToControl),
1126 (OpenReceiveChannelAck, 0x0022, StationToControl),
1127 (ConnectionStatisticsResponse, 0x0023, StationToControl),
1128 (OffHookWithCallingParty, 0x0024, StationToControl),
1129 (SoftKeySetRequest, 0x0025, StationToControl),
1130 (SoftKeyEvent, 0x0026, StationToControl),
1131 (Unregister, 0x0027, StationToControl),
1132 (SoftKeyTemplateRequest, 0x0028, StationToControl),
1133 (RegisterTokenRequest, 0x0029, StationToControl),
1134 (MediaTransmissionFailure, 0x002a, StationToControl),
1135 (HeadsetStatus, 0x002b, StationToControl),
1136 (MediaResourceNotification, 0x002c, ServiceNodeToControl),
1137 (RegisterAvailableLines, 0x002d, StationToControl),
1138 (DeviceToUserData, 0x002e, StationToControl),
1139 (DeviceToUserDataResponse, 0x002f, StationToControl),
1140 (UpdateCapabilities, 0x0030, StationToControl),
1141 (OpenMultimediaReceiveChannelAck, 0x0031, StationToControl),
1142 (ClearConference, 0x0032, ServiceNodeToControl),
1143 (ServiceUrlStatusRequest, 0x0033, StationToControl),
1144 (FeatureStatusRequest, 0x0034, StationToControl),
1145 (CreateConferenceResponse, 0x0035, ServiceNodeToControl),
1146 (DeleteConferenceResponse, 0x0036, ServiceNodeToControl),
1147 (ModifyConferenceResponse, 0x0037, ServiceNodeToControl),
1148 (AddParticipantResponse, 0x0038, ServiceNodeToControl),
1149 (AuditConferenceResponse, 0x0039, ServiceNodeToControl),
1150 (AuditParticipantResponse, 0x0040, ServiceNodeToControl),
1151 (DeviceToUserDataV1, 0x0041, StationToControl),
1152 (DeviceToUserDataResponseV1, 0x0042, StationToControl),
1153 (UpdateCapabilitiesV2, 0x0043, StationToControl),
1154 (UpdateCapabilitiesV3, 0x0044, StationToControl),
1155 (PortResponse, 0x0045, ServiceNodeToControl),
1156 (QosReservationNotify, 0x0046, ServiceNodeToControl),
1157 (QosErrorNotify, 0x0047, ServiceNodeToControl),
1158 (SubscriptionStatusRequest, 0x0048, StationToControl),
1159 (MediaPathEvent, 0x0049, StationToControl),
1160 (MediaPathCapability, 0x004a, StationToControl),
1161 (MwiNotification, 0x004c, ServiceNodeToControl),
1162
1163 (RegisterAck, 0x0081, ControlToStation),
1164 (StartTone, 0x0082, ControlToStation),
1165 (StopTone, 0x0083, ControlToStation),
1166 (SetRinger, 0x0085, ControlToStation),
1167 (SetLamp, 0x0086, ControlToStation),
1168 (SetHookFlashDetect, 0x0087, ControlToStation),
1169 (SetSpeakerMode, 0x0088, ControlToStation),
1170 (SetMicrophoneMode, 0x0089, ControlToStation),
1171 (StartMediaTransmission, 0x008a, ControlToStation),
1172 (StopMediaTransmission, 0x008b, ControlToStation),
1173 (StartMediaReception, 0x008c, ControlToStation),
1174 (StopMediaReception, 0x008d, ControlToStation),
1175 (CallInfo, 0x008f, ControlToStation),
1176 (ForwardStatus, 0x0090, ControlToStation),
1177 (SpeedDialStatus, 0x0091, ControlToStation),
1178 (LineStatus, 0x0092, ControlToStation),
1179 (ConfigStatus, 0x0093, ControlToStation),
1180 (DefineTimeDate, 0x0094, ControlToStation),
1181 (StartSessionTransmission, 0x0095, ControlToServiceNode),
1182 (StopSessionTransmission, 0x0096, ControlToServiceNode),
1183 (ButtonTemplate, 0x0097, ControlToStation),
1184 (Version, 0x0098, ControlToStation),
1185 (DisplayText, 0x0099, ControlToStation),
1186 (ClearDisplay, 0x009a, ControlToStation),
1187 (CapabilitiesRequest, 0x009b, ControlToStation),
1188 (EnunciatorCommand, 0x009c, ControlToStation),
1189 (RegisterReject, 0x009d, ControlToStation),
1190 (ServerResponse, 0x009e, ControlToStation),
1191 (Reset, 0x009f, ControlToStation),
1192 (KeepAliveAck, 0x0100, ControlToStation),
1193 (StartMulticastMediaReception, 0x0101, ControlToStation),
1194 (StartMulticastMediaTransmission, 0x0102, ControlToStation),
1195 (StopMulticastMediaReception, 0x0103, ControlToStation),
1196 (StopMulticastMediaTransmission, 0x0104, ControlToStation),
1197 (OpenReceiveChannel, 0x0105, ControlToStation),
1198 (CloseReceiveChannel, 0x0106, ControlToStation),
1199 (ConnectionStatisticsRequest, 0x0107, ControlToStation),
1200 (SoftKeyTemplateResponse, 0x0108, ControlToStation),
1201 (SoftKeySetResponse, 0x0109, ControlToStation),
1202 (SelectSoftKeys, 0x0110, ControlToStation),
1203 (CallState, 0x0111, ControlToStation),
1204 (DisplayPromptStatus, 0x0112, ControlToStation),
1205 (ClearPromptStatus, 0x0113, ControlToStation),
1206 (DisplayNotify, 0x0114, ControlToStation),
1207 (ClearNotify, 0x0115, ControlToStation),
1208 (ActivateCallPlane, 0x0116, ControlToStation),
1209 (DeactivateCallPlane, 0x0117, ControlToStation),
1210 (UnregisterAck, 0x0118, ControlToStation),
1211 (BackspaceResponse, 0x0119, ControlToStation),
1212 (RegisterTokenAck, 0x011a, ControlToStation),
1213 (RegisterTokenReject, 0x011b, ControlToStation),
1214 (StartMediaFailureDetection, 0x011c, ControlToStation),
1215 (DialedNumber, 0x011d, ControlToStation),
1216 (UserToDeviceData, 0x011e, ControlToStation),
1217 (FeatureStatus, 0x011f, ControlToStation),
1218 (DisplayPriorityNotify, 0x0120, ControlToStation),
1219 (ClearPriorityNotify, 0x0121, ControlToStation),
1220 (StartAnnouncement, 0x0122, IntraControl),
1221 (StopAnnouncement, 0x0123, IntraControl),
1222 (AnnouncementFinish, 0x0124, IntraControl),
1223 (NotifyDtmfTone, 0x0127, ControlToStation),
1224 (SendDtmfTone, 0x0128, ControlToStation),
1225 (SubscribeDtmfPayloadRequest, 0x0129, ControlToStation),
1226 (SubscribeDtmfPayloadResponse, 0x012a, StationToControl),
1227 (SubscribeDtmfPayloadError, 0x012b, ControlToStation),
1228 (UnsubscribeDtmfPayloadRequest, 0x012c, ControlToStation),
1229 (UnsubscribeDtmfPayloadResponse, 0x012d, StationToControl),
1230 (UnsubscribeDtmfPayloadError, 0x012e, ControlToStation),
1231 (ServiceUrlStatus, 0x012f, ControlToStation),
1232 (CallSelectStatus, 0x0130, ControlToStation),
1233 (OpenMultimediaChannel, 0x0131, ControlToStation),
1234 (StartMultimediaTransmission, 0x0132, ControlToStation),
1235 (StopMultimediaTransmission, 0x0133, ControlToStation),
1236 (MiscellaneousCommand, 0x0134, ControlToStation),
1237 (FlowControlCommand, 0x0135, ControlToStation),
1238 (CloseMultimediaReceiveChannel, 0x0136, ControlToStation),
1239 (CreateConferenceRequest, 0x0137, ControlToServiceNode),
1240 (DeleteConferenceRequest, 0x0138, ControlToServiceNode),
1241 (ModifyConferenceRequest, 0x0139, ControlToServiceNode),
1242 (AddParticipantRequest, 0x013a, ControlToServiceNode),
1243 (DropParticipantRequest, 0x013b, ControlToServiceNode),
1244 (AuditConferenceRequest, 0x013c, ControlToServiceNode),
1245 (AuditParticipantRequest, 0x013d, ControlToServiceNode),
1246 (ChangeParticipantRequest, 0x013e, ControlToServiceNode),
1247 (UserToDeviceDataV1, 0x013f, ControlToStation),
1248 (VideoDisplayCommand, 0x0140, ControlToStation),
1249 (FlowControlNotify, 0x0141, ControlToStation),
1250 (ConfigStatusDynamic, 0x0142, ControlToStation),
1251 (DisplayDynamicNotify, 0x0143, ControlToStation),
1252 (DisplayDynamicPriorityNotify, 0x0144, ControlToStation),
1253 (DisplayDynamicPromptStatus, 0x0145, ControlToStation),
1254 (FeatureStatusDynamic, 0x0146, ControlToStation),
1255 (LineStatusDynamic, 0x0147, ControlToStation),
1256 (ServiceUrlStatusDynamic, 0x0148, ControlToStation),
1257 (SpeedDialStatusDynamic, 0x0149, ControlToStation),
1258 (CallInfoDynamic, 0x014a, ControlToStation),
1259 (PortRequest, 0x014b, ControlToStation),
1260 (PortClose, 0x014c, ControlToStation),
1261 (QosListen, 0x014d, ControlToServiceNode),
1262 (QosPath, 0x014e, ControlToServiceNode),
1263 (QosTeardown, 0x014f, ControlToServiceNode),
1264 (UpdateDscp, 0x0150, ControlToServiceNode),
1265 (QosModify, 0x0151, ControlToServiceNode),
1266
1267 (SubscriptionStatus, 0x0152, ControlToStation),
1268 (Notification, 0x0153, ControlToStation),
1269 (StartMediaTransmissionAck, 0x0154, StationToControl),
1270 (StartMultimediaTransmissionAck, 0x0155, StationToControl),
1271 (CallHistoryDisposition, 0x0156, ControlToStation),
1272 (LocationInfo, 0x0157, StationToControl),
1273 (MwiResponse, 0x0158, ControlToServiceNode),
1274 (ExtensionDeviceCapabilities, 0x0159, StationToControl),
1275 (XmlAlarm, 0x015a, StationToControl),
1276 (CallCountRequest, 0x015e, StationToControl),
1277 (CallCountResponse, 0x015f, ControlToStation),
1278 (RecordingStatus, 0x0160, ControlToStation),
1279
1280 (SpcpRegisterTokenRequest, 0x8000, StationToControl),
1281 (SpcpRegisterTokenAck, 0x8100, ControlToStation),
1282 (SpcpRegisterTokenReject, 0x8101, ControlToStation),
1283}
1284
1285impl fmt::Display for MessageId {
1286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1287 match self {
1288 Self::Unknown(value) => write!(f, "Unknown(0x{value:04x})"),
1289 known => f.write_str(known.name()),
1290 }
1291 }
1292}
1293
1294pub fn implemented_message_contracts() -> impl Iterator<Item = MessageContract> {
1300 MessageId::ALL_KNOWN
1301 .iter()
1302 .filter_map(|id| id.contract())
1303 .filter(|contract| contract.codec == CodecSupport::Typed)
1304}
1305
1306#[cfg(test)]
1307mod tests {
1308 use super::*;
1309 use std::collections::HashSet;
1310
1311 #[test]
1312 fn known_catalog_values_are_unique_and_round_trip() {
1313 let mut values = HashSet::new();
1314 for id in MessageId::ALL_KNOWN {
1315 assert!(values.insert(id.wire_value()), "duplicate {id}");
1316 assert_eq!(MessageId::from(id.wire_value()), *id);
1317 assert!(id.route().is_some());
1318 assert!(id.is_known());
1319 }
1320 assert!(MessageId::ALL_KNOWN.len() > 140);
1321 }
1322
1323 #[test]
1324 fn supplemental_contract_scope_is_explicit_and_closed() {
1325 let supplemental = MessageId::ALL_KNOWN
1326 .iter()
1327 .copied()
1328 .filter(|id| id.contract().unwrap().scope == ContractScope::Supplemental)
1329 .collect::<Vec<_>>();
1330
1331 assert_eq!(
1332 supplemental,
1333 [
1334 MessageId::IpPort,
1335 MessageId::MediaPortList,
1336 MessageId::SetHookFlashDetect,
1337 MessageId::StartMediaReception,
1338 MessageId::StopMediaReception,
1339 MessageId::EnunciatorCommand,
1340 MessageId::ExtensionDeviceCapabilities,
1341 MessageId::SpcpRegisterTokenRequest,
1342 MessageId::SpcpRegisterTokenAck,
1343 MessageId::SpcpRegisterTokenReject,
1344 ]
1345 );
1346 }
1347
1348 #[test]
1349 fn unknown_identifiers_remain_lossless() {
1350 let id = MessageId::from(0xdead_beef);
1351 assert_eq!(id, MessageId::Unknown(0xdead_beef));
1352 assert_eq!(id.wire_value(), 0xdead_beef);
1353 assert_eq!(id.direction(), None);
1354 }
1355
1356 #[test]
1357 fn dtmf_subscription_responses_have_the_device_to_server_direction() {
1358 assert_eq!(
1359 MessageId::SubscribeDtmfPayloadRequest.direction(),
1360 Some(MessageDirection::ServerToDevice)
1361 );
1362 assert_eq!(
1363 MessageId::SubscribeDtmfPayloadResponse.direction(),
1364 Some(MessageDirection::DeviceToServer)
1365 );
1366 assert_eq!(
1367 MessageId::UnsubscribeDtmfPayloadRequest.direction(),
1368 Some(MessageDirection::ServerToDevice)
1369 );
1370 assert_eq!(
1371 MessageId::UnsubscribeDtmfPayloadResponse.direction(),
1372 Some(MessageDirection::DeviceToServer)
1373 );
1374 }
1375
1376 #[test]
1377 fn every_known_id_has_an_explicit_support_and_runtime_contract() {
1378 for id in MessageId::ALL_KNOWN {
1379 let contract = id.contract().expect("known ID has a contract");
1380 assert_eq!(contract.id, *id);
1381 assert_eq!(contract.route, id.route().unwrap());
1382 match contract.codec {
1383 CodecSupport::Typed => {
1384 assert_eq!(contract.emission, EmissionSupport::Typed);
1385 assert_ne!(contract.runtime_use, RuntimeUse::CatalogOnly);
1386 }
1387 CodecSupport::OpaqueOnly => {
1388 assert_eq!(contract.emission, EmissionSupport::PreserveOnly);
1389 assert_eq!(contract.runtime_use, RuntimeUse::CatalogOnly);
1390 assert_eq!(contract.payload_layout, PayloadLayout::Opaque);
1391 }
1392 }
1393 match contract.field_fidelity {
1394 FieldFidelity::CanonicalServerOutput(detail) => {
1395 assert!(matches!(
1396 contract.route,
1397 MessageRoute::ControlToStation
1398 | MessageRoute::ControlToServiceNode
1399 | MessageRoute::IntraControl
1400 ));
1401 assert!(!detail.is_empty());
1402 }
1403 FieldFidelity::SemanticProjection(detail) => {
1404 assert_eq!(contract.codec, CodecSupport::Typed);
1405 assert!(!detail.is_empty());
1406 }
1407 FieldFidelity::OpaquePreserved => {
1408 assert_eq!(contract.codec, CodecSupport::OpaqueOnly);
1409 }
1410 FieldFidelity::Lossless => {}
1411 }
1412 }
1413 assert!(implemented_message_contracts().count() > 100);
1414
1415 for id in [
1416 MessageId::OpenReceiveChannel,
1417 MessageId::StartMediaTransmission,
1418 MessageId::StartMediaTransmissionAck,
1419 MessageId::KeypadButton,
1420 MessageId::EnblocCall,
1421 MessageId::Register,
1422 MessageId::Alarm,
1423 MessageId::DefineTimeDate,
1424 ] {
1425 assert_eq!(
1426 id.contract().unwrap().field_fidelity,
1427 FieldFidelity::Lossless
1428 );
1429 }
1430 }
1431
1432 #[test]
1433 fn semantic_field_fidelity_overclaims_are_explicitly_excluded() {
1434 for (id, omitted) in [
1435 (MessageId::LineStatus, "display label"),
1436 (MessageId::CallInfo, "mailboxes"),
1437 (MessageId::CallState, "visibility"),
1438 ] {
1439 let FieldFidelity::CanonicalServerOutput(detail) =
1440 id.contract().unwrap().field_fidelity
1441 else {
1442 panic!("{id} must not claim lossless field fidelity");
1443 };
1444 assert!(detail.contains(omitted), "{id}: {detail}");
1445 }
1446
1447 for id in [MessageId::ConfigStatus, MessageId::ConfigStatusDynamic] {
1448 assert_eq!(
1449 id.contract().unwrap().field_fidelity,
1450 FieldFidelity::Lossless
1451 );
1452 }
1453
1454 for id in [
1455 MessageId::CapabilitiesResponse,
1456 MessageId::MediaTransmissionFailure,
1457 MessageId::PortResponse,
1458 ] {
1459 assert!(matches!(
1460 id.contract().unwrap().field_fidelity,
1461 FieldFidelity::SemanticProjection(_)
1462 ));
1463 }
1464 }
1465
1466 #[test]
1467 fn variable_layout_messages_never_claim_one_fixed_payload_size() {
1468 for contract in MessageId::ALL_KNOWN.iter().filter_map(|id| id.contract()) {
1469 if matches!(
1470 contract.payload_layout,
1471 PayloadLayout::VersionSelected
1472 | PayloadLayout::VersionAndLengthSelected
1473 | PayloadLayout::BoundedPreserved
1474 ) {
1475 assert_eq!(
1476 contract.fixed_payload_bytes, None,
1477 "{} has a variable payload layout",
1478 contract.id
1479 );
1480 }
1481 }
1482 }
1483
1484 #[test]
1485 fn bounded_and_counted_payload_contracts_report_their_wire_limits() {
1486 for id in [
1487 MessageId::KeepAlive,
1488 MessageId::ConfigStatusRequest,
1489 MessageId::ButtonTemplateRequest,
1490 MessageId::KeepAliveAck,
1491 ] {
1492 assert_eq!(
1493 id.contract().unwrap().payload_size_bounds,
1494 Some(PayloadSizeBounds {
1495 minimum: 0,
1496 maximum: MAX_FRAME_SIZE - HEADER_SIZE,
1497 }),
1498 "{id}"
1499 );
1500 }
1501
1502 let capabilities = MessageId::CapabilitiesResponse.contract().unwrap();
1503 assert_eq!(capabilities.payload_layout, PayloadLayout::LengthPrefixed);
1504 assert_eq!(
1505 capabilities.payload_size_bounds,
1506 Some(PayloadSizeBounds {
1507 minimum: 4,
1508 maximum: 292,
1509 })
1510 );
1511
1512 let version_two = MessageId::UpdateCapabilitiesV2.contract().unwrap();
1513 assert_eq!(version_two.payload_layout, PayloadLayout::Fixed);
1514 assert_eq!(version_two.fixed_payload_bytes, Some(2_000));
1515
1516 let version_three = MessageId::UpdateCapabilitiesV3.contract().unwrap();
1517 assert_eq!(
1518 version_three.payload_layout,
1519 PayloadLayout::MinimumLengthPreserved
1520 );
1521 assert_eq!(
1522 version_three.payload_size_bounds,
1523 Some(PayloadSizeBounds {
1524 minimum: 20,
1525 maximum: 2_380,
1526 })
1527 );
1528 }
1529
1530 #[test]
1531 fn service_message_payload_bounds_are_explicit() {
1532 assert_eq!(
1533 MessageId::XmlAlarm.contract().unwrap().payload_size_bounds,
1534 Some(PayloadSizeBounds {
1535 minimum: 0,
1536 maximum: 2_048,
1537 })
1538 );
1539 assert_eq!(
1540 MessageId::AddParticipantResponse
1541 .contract()
1542 .unwrap()
1543 .payload_size_bounds,
1544 Some(PayloadSizeBounds {
1545 minimum: 12,
1546 maximum: 272,
1547 })
1548 );
1549
1550 for (id, size) in [
1551 (MessageId::AuditConferenceRequest, 0),
1552 (MessageId::SubscribeDtmfPayloadRequest, 16),
1553 (MessageId::SubscribeDtmfPayloadResponse, 12),
1554 (MessageId::SubscribeDtmfPayloadError, 12),
1555 (MessageId::UnsubscribeDtmfPayloadRequest, 16),
1556 (MessageId::UnsubscribeDtmfPayloadResponse, 12),
1557 (MessageId::UnsubscribeDtmfPayloadError, 12),
1558 ] {
1559 assert_eq!(
1560 id.contract().unwrap().payload_size_bounds,
1561 Some(PayloadSizeBounds {
1562 minimum: size,
1563 maximum: size,
1564 }),
1565 "{id}"
1566 );
1567 }
1568 }
1569
1570 #[test]
1571 fn media_contracts_record_fixed_and_version_selected_sizes() {
1572 for id in [
1573 MessageId::OpenMultimediaReceiveChannelAck,
1574 MessageId::StartMultimediaTransmissionAck,
1575 MessageId::StartSessionTransmission,
1576 MessageId::StopSessionTransmission,
1577 MessageId::OpenMultimediaChannel,
1578 MessageId::StartMultimediaTransmission,
1579 MessageId::PortRequest,
1580 MessageId::PortClose,
1581 ] {
1582 let contract = id.contract().unwrap();
1583 assert_eq!(contract.payload_layout, PayloadLayout::VersionSelected);
1584 assert_eq!(contract.fixed_payload_bytes, None);
1585 }
1586
1587 for (id, size) in [
1588 (MessageId::MulticastMediaReceptionAck, 12),
1589 (MessageId::CloseReceiveChannel, 16),
1590 (MessageId::StopMediaTransmission, 16),
1591 (MessageId::MiscellaneousCommand, 52),
1592 (MessageId::QosReservationNotify, 24),
1593 (MessageId::QosErrorNotify, 44),
1594 (MessageId::QosListen, 172),
1595 (MessageId::QosPath, 168),
1596 (MessageId::QosTeardown, 24),
1597 (MessageId::UpdateDscp, 24),
1598 (MessageId::QosModify, 152),
1599 ] {
1600 assert_eq!(id.contract().unwrap().fixed_payload_bytes, Some(size));
1601 }
1602
1603 assert_eq!(
1604 MessageId::StartMediaTransmissionAck
1605 .contract()
1606 .unwrap()
1607 .payload_layout,
1608 PayloadLayout::VersionAndLengthSelected
1609 );
1610 assert_eq!(
1611 MessageId::LocationInfo
1612 .contract()
1613 .unwrap()
1614 .payload_size_bounds,
1615 Some(PayloadSizeBounds {
1616 minimum: 2_404,
1617 maximum: 2_404,
1618 })
1619 );
1620 for id in [
1621 MessageId::CloseReceiveChannel,
1622 MessageId::StopMediaTransmission,
1623 ] {
1624 assert_eq!(
1625 id.contract().unwrap().field_fidelity,
1626 FieldFidelity::Lossless
1627 );
1628 }
1629 }
1630
1631 #[test]
1632 fn session_transmission_contracts_use_the_service_node_codec() {
1633 for id in [
1634 MessageId::StartSessionTransmission,
1635 MessageId::StopSessionTransmission,
1636 ] {
1637 let contract = id.contract().unwrap();
1638 assert_eq!(contract.route, MessageRoute::ControlToServiceNode);
1639 assert_eq!(contract.codec, CodecSupport::Typed);
1640 assert_eq!(contract.emission, EmissionSupport::Typed);
1641 assert_eq!(contract.runtime_use, RuntimeUse::TypedButNotEmitted);
1642 assert_eq!(contract.field_fidelity, FieldFidelity::Lossless);
1643 assert_eq!(contract.payload_layout, PayloadLayout::VersionSelected);
1644 }
1645 }
1646
1647 #[test]
1648 fn supplemental_token_messages_remain_preserve_only() {
1649 for id in [
1650 MessageId::SpcpRegisterTokenRequest,
1651 MessageId::SpcpRegisterTokenAck,
1652 MessageId::SpcpRegisterTokenReject,
1653 ] {
1654 let contract = id.contract().unwrap();
1655 assert_eq!(contract.codec, CodecSupport::OpaqueOnly);
1656 assert_eq!(contract.emission, EmissionSupport::PreserveOnly);
1657 assert_eq!(contract.runtime_use, RuntimeUse::CatalogOnly);
1658 assert_eq!(contract.payload_layout, PayloadLayout::Opaque);
1659 }
1660 }
1661
1662 #[test]
1663 fn runtime_emission_is_distinct_from_typed_encodability() {
1664 let dtmf = MessageId::SubscribeDtmfPayloadRequest.contract().unwrap();
1665 assert_eq!(dtmf.codec, CodecSupport::Typed);
1666 assert_eq!(dtmf.emission, EmissionSupport::Typed);
1667 assert_eq!(dtmf.runtime_use, RuntimeUse::TypedButNotEmitted);
1668
1669 let open = MessageId::OpenReceiveChannel.contract().unwrap();
1670 assert_eq!(open.runtime_use, RuntimeUse::ConditionalServerOutput);
1671 assert_eq!(
1672 open.response,
1673 ResponseExpectation::Message(MessageId::OpenReceiveChannelAck)
1674 );
1675
1676 for id in [
1677 MessageId::MiscellaneousCommand,
1678 MessageId::FlowControlCommand,
1679 MessageId::FlowControlNotify,
1680 ] {
1681 assert_eq!(
1682 id.contract().unwrap().runtime_use,
1683 RuntimeUse::ConditionalServerOutput
1684 );
1685 }
1686
1687 for (id, response, runtime_use) in [
1688 (
1689 MessageId::OpenMultimediaChannel,
1690 MessageId::OpenMultimediaReceiveChannelAck,
1691 RuntimeUse::ConditionalServerOutput,
1692 ),
1693 (
1694 MessageId::StartMultimediaTransmission,
1695 MessageId::StartMultimediaTransmissionAck,
1696 RuntimeUse::ConditionalServerOutput,
1697 ),
1698 ] {
1699 let contract = id.contract().unwrap();
1700 assert_eq!(contract.route, MessageRoute::ControlToStation);
1701 assert_eq!(contract.codec, CodecSupport::Typed);
1702 assert_eq!(contract.emission, EmissionSupport::Typed);
1703 assert_eq!(contract.runtime_use, runtime_use);
1704 assert_eq!(contract.field_fidelity, FieldFidelity::Lossless);
1705 assert_eq!(contract.payload_layout, PayloadLayout::VersionSelected);
1706 assert_eq!(contract.response, ResponseExpectation::Message(response));
1707 }
1708 }
1709
1710 #[test]
1711 fn dynamic_response_contracts_include_every_session_selector() {
1712 for (request, before, from) in [
1713 (
1714 MessageId::ConfigStatusRequest,
1715 MessageId::ConfigStatus,
1716 MessageId::ConfigStatusDynamic,
1717 ),
1718 (
1719 MessageId::LineStatusRequest,
1720 MessageId::LineStatus,
1721 MessageId::LineStatusDynamic,
1722 ),
1723 (
1724 MessageId::ServiceUrlStatusRequest,
1725 MessageId::ServiceUrlStatus,
1726 MessageId::ServiceUrlStatusDynamic,
1727 ),
1728 ] {
1729 assert_eq!(
1730 request.contract().unwrap().response,
1731 ResponseExpectation::SessionSelected {
1732 before,
1733 from,
1734 selector: SessionResponseSelector::DynamicMessagesOrProtocol {
1735 minimum_protocol: 9,
1736 },
1737 }
1738 );
1739 }
1740
1741 assert_eq!(
1742 MessageId::FeatureStatusRequest.contract().unwrap().response,
1743 ResponseExpectation::SessionSelected {
1744 before: MessageId::FeatureStatus,
1745 from: MessageId::FeatureStatusDynamic,
1746 selector: SessionResponseSelector::DynamicMessages,
1747 }
1748 );
1749 assert_eq!(
1750 MessageId::SpeedDialStatusRequest
1751 .contract()
1752 .unwrap()
1753 .response,
1754 ResponseExpectation::VersionSelected {
1755 before: MessageId::SpeedDialStatus,
1756 from: MessageId::SpeedDialStatusDynamic,
1757 minimum_protocol: 15,
1758 }
1759 );
1760 }
1761}