1use broadcast_common::{Parse, Serialize};
14
15use crate::RtmpError;
16use crate::chunk::Message;
17
18type Result<T> = core::result::Result<T, RtmpError>;
19
20pub mod msg_type {
26 pub const SET_CHUNK_SIZE: u8 = 1;
28 pub const ABORT: u8 = 2;
30 pub const ACKNOWLEDGEMENT: u8 = 3;
32 pub const USER_CONTROL: u8 = 4;
34 pub const WINDOW_ACK_SIZE: u8 = 5;
36 pub const SET_PEER_BANDWIDTH: u8 = 6;
38 pub const AUDIO: u8 = 8;
40 pub const VIDEO: u8 = 9;
42 pub const DATA_AMF3: u8 = 15;
44 pub const COMMAND_AMF3: u8 = 17;
46 pub const DATA_AMF0: u8 = 18;
48 pub const COMMAND_AMF0: u8 = 20;
50 pub const AGGREGATE: u8 = 22;
52}
53
54pub const CONTROL_CHUNK_STREAM_ID: u32 = 2;
57pub const CONTROL_MESSAGE_STREAM_ID: u32 = 0;
60
61const U32_LEN: usize = 4;
64const SET_PEER_BANDWIDTH_LEN: usize = U32_LEN + 1;
67
68const SET_CHUNK_SIZE_RESERVED_MASK: u32 = 0x8000_0000;
71const SET_CHUNK_SIZE_VALUE_MASK: u32 = 0x7FFF_FFFF;
74
75fn read_u32_be(b: &[u8]) -> u32 {
76 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
77}
78
79fn need_u32(bytes: &[u8], what: &'static str) -> Result<u32> {
80 if bytes.len() < U32_LEN {
81 return Err(RtmpError::BufferTooShort {
82 need: U32_LEN,
83 have: bytes.len(),
84 what,
85 });
86 }
87 Ok(read_u32_be(bytes))
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum LimitType {
95 Hard,
97 Soft,
100 Dynamic,
103}
104
105impl LimitType {
106 #[must_use]
108 pub fn name(&self) -> &'static str {
109 match self {
110 LimitType::Hard => "hard",
111 LimitType::Soft => "soft",
112 LimitType::Dynamic => "dynamic",
113 }
114 }
115
116 pub const fn from_u8(v: u8) -> core::result::Result<Self, RtmpError> {
121 match v {
122 0 => Ok(LimitType::Hard),
123 1 => Ok(LimitType::Soft),
124 2 => Ok(LimitType::Dynamic),
125 _ => Err(RtmpError::Malformed {
126 what: "set peer bandwidth limit type (must be 0..=2)",
127 }),
128 }
129 }
130
131 #[must_use]
133 pub const fn to_u8(self) -> u8 {
134 match self {
135 LimitType::Hard => 0,
136 LimitType::Soft => 1,
137 LimitType::Dynamic => 2,
138 }
139 }
140}
141
142broadcast_common::impl_spec_display!(LimitType);
143
144#[non_exhaustive]
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum ProtocolControl {
157 SetChunkSize(u32),
159 Abort {
162 chunk_stream_id: u32,
165 },
166 Acknowledgement(u32),
168 WindowAckSize(u32),
171 SetPeerBandwidth {
173 ack_window_size: u32,
175 limit_type: LimitType,
177 },
178}
179
180impl ProtocolControl {
181 #[must_use]
183 pub fn name(&self) -> &'static str {
184 match self {
185 ProtocolControl::SetChunkSize(_) => "set chunk size",
186 ProtocolControl::Abort { .. } => "abort message",
187 ProtocolControl::Acknowledgement(_) => "acknowledgement",
188 ProtocolControl::WindowAckSize(_) => "window acknowledgement size",
189 ProtocolControl::SetPeerBandwidth { .. } => "set peer bandwidth",
190 }
191 }
192
193 #[must_use]
195 pub fn message_type_id(&self) -> u8 {
196 match self {
197 ProtocolControl::SetChunkSize(_) => msg_type::SET_CHUNK_SIZE,
198 ProtocolControl::Abort { .. } => msg_type::ABORT,
199 ProtocolControl::Acknowledgement(_) => msg_type::ACKNOWLEDGEMENT,
200 ProtocolControl::WindowAckSize(_) => msg_type::WINDOW_ACK_SIZE,
201 ProtocolControl::SetPeerBandwidth { .. } => msg_type::SET_PEER_BANDWIDTH,
202 }
203 }
204
205 pub fn from_message(message: &Message) -> Result<Option<Self>> {
217 Self::from_payload(message.message_type_id, &message.payload)
218 }
219
220 pub fn from_payload(message_type_id: u8, payload: &[u8]) -> Result<Option<Self>> {
230 match message_type_id {
231 msg_type::SET_CHUNK_SIZE => {
232 let raw = need_u32(payload, "set chunk size payload")?;
233 if raw & SET_CHUNK_SIZE_RESERVED_MASK != 0 {
234 return Err(RtmpError::Malformed {
235 what: "set chunk size reserved top bit (must be 0)",
236 });
237 }
238 let size = raw & SET_CHUNK_SIZE_VALUE_MASK;
239 if size == 0 {
240 return Err(RtmpError::Malformed {
241 what: "set chunk size value (must be >= 1)",
242 });
243 }
244 Ok(Some(ProtocolControl::SetChunkSize(size)))
245 }
246 msg_type::ABORT => {
247 let chunk_stream_id = need_u32(payload, "abort message payload")?;
248 Ok(Some(ProtocolControl::Abort { chunk_stream_id }))
249 }
250 msg_type::ACKNOWLEDGEMENT => {
251 let sequence_number = need_u32(payload, "acknowledgement payload")?;
252 Ok(Some(ProtocolControl::Acknowledgement(sequence_number)))
253 }
254 msg_type::WINDOW_ACK_SIZE => {
255 let window = need_u32(payload, "window acknowledgement size payload")?;
256 Ok(Some(ProtocolControl::WindowAckSize(window)))
257 }
258 msg_type::SET_PEER_BANDWIDTH => {
259 if payload.len() < SET_PEER_BANDWIDTH_LEN {
260 return Err(RtmpError::BufferTooShort {
261 need: SET_PEER_BANDWIDTH_LEN,
262 have: payload.len(),
263 what: "set peer bandwidth payload",
264 });
265 }
266 let ack_window_size = read_u32_be(&payload[0..U32_LEN]);
267 let limit_type = LimitType::from_u8(payload[U32_LEN])?;
268 Ok(Some(ProtocolControl::SetPeerBandwidth {
269 ack_window_size,
270 limit_type,
271 }))
272 }
273 _ => Ok(None),
274 }
275 }
276
277 #[must_use]
283 pub fn to_message(&self) -> Message {
284 Message {
285 chunk_stream_id: CONTROL_CHUNK_STREAM_ID,
286 timestamp: 0,
287 message_type_id: self.message_type_id(),
288 message_stream_id: CONTROL_MESSAGE_STREAM_ID,
289 payload: self.to_bytes(),
290 }
291 }
292}
293
294broadcast_common::impl_spec_display!(ProtocolControl);
295
296impl Serialize for ProtocolControl {
297 type Error = RtmpError;
298
299 fn serialized_len(&self) -> usize {
300 match self {
301 ProtocolControl::SetChunkSize(_)
302 | ProtocolControl::Abort { .. }
303 | ProtocolControl::Acknowledgement(_)
304 | ProtocolControl::WindowAckSize(_) => U32_LEN,
305 ProtocolControl::SetPeerBandwidth { .. } => SET_PEER_BANDWIDTH_LEN,
306 }
307 }
308
309 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
310 let written = self.serialized_len();
311 if buf.len() < written {
312 return Err(RtmpError::BufferTooShort {
313 need: written,
314 have: buf.len(),
315 what: "protocol control payload output",
316 });
317 }
318 match *self {
319 ProtocolControl::SetChunkSize(size) => {
320 if size == 0 || size & SET_CHUNK_SIZE_RESERVED_MASK != 0 {
321 return Err(RtmpError::Malformed {
322 what: "set chunk size value (must be 1..=0x7FFF_FFFF)",
323 });
324 }
325 buf[0..U32_LEN].copy_from_slice(&size.to_be_bytes());
326 }
327 ProtocolControl::Abort { chunk_stream_id } => {
328 buf[0..U32_LEN].copy_from_slice(&chunk_stream_id.to_be_bytes());
329 }
330 ProtocolControl::Acknowledgement(sequence_number) => {
331 buf[0..U32_LEN].copy_from_slice(&sequence_number.to_be_bytes());
332 }
333 ProtocolControl::WindowAckSize(window) => {
334 buf[0..U32_LEN].copy_from_slice(&window.to_be_bytes());
335 }
336 ProtocolControl::SetPeerBandwidth {
337 ack_window_size,
338 limit_type,
339 } => {
340 buf[0..U32_LEN].copy_from_slice(&ack_window_size.to_be_bytes());
341 buf[U32_LEN] = limit_type.to_u8();
342 }
343 }
344 Ok(written)
345 }
346}
347
348const EVENT_TYPE_LEN: usize = 2;
352
353mod event_type {
355 pub const STREAM_BEGIN: u16 = 0;
356 pub const STREAM_EOF: u16 = 1;
357 pub const STREAM_DRY: u16 = 2;
358 pub const SET_BUFFER_LENGTH: u16 = 3;
359 pub const STREAM_IS_RECORDED: u16 = 4;
360 pub const PING_REQUEST: u16 = 6;
362 pub const PING_RESPONSE: u16 = 7;
363}
364
365#[non_exhaustive]
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
375pub enum UserControl {
376 StreamBegin(u32),
380 StreamEof(u32),
383 StreamDry(u32),
386 SetBufferLength {
389 stream_id: u32,
391 buffer_ms: u32,
393 },
394 StreamIsRecorded(u32),
397 PingRequest(u32),
400 PingResponse(u32),
403}
404
405impl UserControl {
406 #[must_use]
408 pub fn name(&self) -> &'static str {
409 match self {
410 UserControl::StreamBegin(_) => "stream begin",
411 UserControl::StreamEof(_) => "stream eof",
412 UserControl::StreamDry(_) => "stream dry",
413 UserControl::SetBufferLength { .. } => "set buffer length",
414 UserControl::StreamIsRecorded(_) => "stream is recorded",
415 UserControl::PingRequest(_) => "ping request",
416 UserControl::PingResponse(_) => "ping response",
417 }
418 }
419
420 #[must_use]
422 pub fn event_type(&self) -> u16 {
423 match self {
424 UserControl::StreamBegin(_) => event_type::STREAM_BEGIN,
425 UserControl::StreamEof(_) => event_type::STREAM_EOF,
426 UserControl::StreamDry(_) => event_type::STREAM_DRY,
427 UserControl::SetBufferLength { .. } => event_type::SET_BUFFER_LENGTH,
428 UserControl::StreamIsRecorded(_) => event_type::STREAM_IS_RECORDED,
429 UserControl::PingRequest(_) => event_type::PING_REQUEST,
430 UserControl::PingResponse(_) => event_type::PING_RESPONSE,
431 }
432 }
433
434 #[must_use]
440 pub fn to_message(&self) -> Message {
441 Message {
442 chunk_stream_id: CONTROL_CHUNK_STREAM_ID,
443 timestamp: 0,
444 message_type_id: msg_type::USER_CONTROL,
445 message_stream_id: CONTROL_MESSAGE_STREAM_ID,
446 payload: self.to_bytes(),
447 }
448 }
449}
450
451broadcast_common::impl_spec_display!(UserControl);
452
453impl<'a> Parse<'a> for UserControl {
454 type Error = RtmpError;
455
456 fn parse(bytes: &'a [u8]) -> Result<Self> {
457 if bytes.len() < EVENT_TYPE_LEN {
458 return Err(RtmpError::BufferTooShort {
459 need: EVENT_TYPE_LEN,
460 have: bytes.len(),
461 what: "user control event type",
462 });
463 }
464 let event = u16::from_be_bytes([bytes[0], bytes[1]]);
465 let data = &bytes[EVENT_TYPE_LEN..];
466 match event {
467 event_type::STREAM_BEGIN => Ok(UserControl::StreamBegin(need_u32(
468 data,
469 "stream begin event data",
470 )?)),
471 event_type::STREAM_EOF => Ok(UserControl::StreamEof(need_u32(
472 data,
473 "stream eof event data",
474 )?)),
475 event_type::STREAM_DRY => Ok(UserControl::StreamDry(need_u32(
476 data,
477 "stream dry event data",
478 )?)),
479 event_type::SET_BUFFER_LENGTH => {
480 if data.len() < 2 * U32_LEN {
481 return Err(RtmpError::BufferTooShort {
482 need: 2 * U32_LEN,
483 have: data.len(),
484 what: "set buffer length event data",
485 });
486 }
487 Ok(UserControl::SetBufferLength {
488 stream_id: read_u32_be(&data[0..U32_LEN]),
489 buffer_ms: read_u32_be(&data[U32_LEN..2 * U32_LEN]),
490 })
491 }
492 event_type::STREAM_IS_RECORDED => Ok(UserControl::StreamIsRecorded(need_u32(
493 data,
494 "stream is recorded event data",
495 )?)),
496 event_type::PING_REQUEST => Ok(UserControl::PingRequest(need_u32(
497 data,
498 "ping request event data",
499 )?)),
500 event_type::PING_RESPONSE => Ok(UserControl::PingResponse(need_u32(
501 data,
502 "ping response event data",
503 )?)),
504 _ => Err(RtmpError::Unsupported {
505 what: "user control event type (unrecognised)",
506 }),
507 }
508 }
509}
510
511impl Serialize for UserControl {
512 type Error = RtmpError;
513
514 fn serialized_len(&self) -> usize {
515 let data_len = match self {
516 UserControl::SetBufferLength { .. } => 2 * U32_LEN,
517 _ => U32_LEN,
518 };
519 EVENT_TYPE_LEN + data_len
520 }
521
522 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
523 let written = self.serialized_len();
524 if buf.len() < written {
525 return Err(RtmpError::BufferTooShort {
526 need: written,
527 have: buf.len(),
528 what: "user control event output",
529 });
530 }
531 buf[0..EVENT_TYPE_LEN].copy_from_slice(&self.event_type().to_be_bytes());
532 let data = &mut buf[EVENT_TYPE_LEN..written];
533 match *self {
534 UserControl::StreamBegin(stream_id)
535 | UserControl::StreamEof(stream_id)
536 | UserControl::StreamDry(stream_id)
537 | UserControl::StreamIsRecorded(stream_id)
538 | UserControl::PingRequest(stream_id)
539 | UserControl::PingResponse(stream_id) => {
540 data[0..U32_LEN].copy_from_slice(&stream_id.to_be_bytes());
541 }
542 UserControl::SetBufferLength {
543 stream_id,
544 buffer_ms,
545 } => {
546 data[0..U32_LEN].copy_from_slice(&stream_id.to_be_bytes());
547 data[U32_LEN..2 * U32_LEN].copy_from_slice(&buffer_ms.to_be_bytes());
548 }
549 }
550 Ok(written)
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557
558 fn message(message_type_id: u8, payload: Vec<u8>) -> Message {
559 Message {
560 chunk_stream_id: CONTROL_CHUNK_STREAM_ID,
561 timestamp: 0,
562 message_type_id,
563 message_stream_id: CONTROL_MESSAGE_STREAM_ID,
564 payload,
565 }
566 }
567
568 #[test]
571 fn limit_type_round_trip_and_name() {
572 for (byte, lt, name) in [
573 (0u8, LimitType::Hard, "hard"),
574 (1, LimitType::Soft, "soft"),
575 (2, LimitType::Dynamic, "dynamic"),
576 ] {
577 let parsed = LimitType::from_u8(byte).unwrap();
578 assert_eq!(parsed, lt);
579 assert_eq!(parsed.to_u8(), byte);
580 assert_eq!(parsed.name(), name);
581 assert_eq!(parsed.to_string(), name);
582 }
583 }
584
585 #[test]
586 fn limit_type_out_of_range_is_malformed() {
587 assert!(matches!(
588 LimitType::from_u8(3),
589 Err(RtmpError::Malformed { .. })
590 ));
591 }
592
593 fn protocol_control_round_trip(pc: ProtocolControl) {
596 let bytes = pc.to_bytes();
597 let parsed = ProtocolControl::from_payload(pc.message_type_id(), &bytes)
598 .unwrap()
599 .expect("known protocol control type id");
600 assert_eq!(parsed, pc);
601
602 let msg = message(pc.message_type_id(), bytes.clone());
604 let via_message = ProtocolControl::from_message(&msg).unwrap().unwrap();
605 assert_eq!(via_message, pc);
606 assert_eq!(via_message.to_bytes(), bytes);
607 }
608
609 #[test]
610 fn set_chunk_size_round_trips() {
611 protocol_control_round_trip(ProtocolControl::SetChunkSize(4096));
612 }
613
614 #[test]
615 fn abort_round_trips() {
616 protocol_control_round_trip(ProtocolControl::Abort { chunk_stream_id: 7 });
617 }
618
619 #[test]
620 fn acknowledgement_round_trips() {
621 protocol_control_round_trip(ProtocolControl::Acknowledgement(1_048_576));
622 }
623
624 #[test]
625 fn window_ack_size_round_trips() {
626 protocol_control_round_trip(ProtocolControl::WindowAckSize(2_500_000));
627 }
628
629 #[test]
630 fn set_peer_bandwidth_round_trips_every_limit_type() {
631 for limit_type in [LimitType::Hard, LimitType::Soft, LimitType::Dynamic] {
632 protocol_control_round_trip(ProtocolControl::SetPeerBandwidth {
633 ack_window_size: 2_500_000,
634 limit_type,
635 });
636 }
637 }
638
639 #[test]
640 fn set_chunk_size_reserved_top_bit_rejected_on_parse() {
641 let bytes = 0x8000_1000u32.to_be_bytes().to_vec();
642 assert!(matches!(
643 ProtocolControl::from_payload(msg_type::SET_CHUNK_SIZE, &bytes),
644 Err(RtmpError::Malformed { .. })
645 ));
646 }
647
648 #[test]
649 fn set_chunk_size_zero_rejected() {
650 let bytes = 0u32.to_be_bytes().to_vec();
651 assert!(matches!(
652 ProtocolControl::from_payload(msg_type::SET_CHUNK_SIZE, &bytes),
653 Err(RtmpError::Malformed { .. })
654 ));
655 assert!(matches!(
656 ProtocolControl::SetChunkSize(0).serialize_into(&mut [0u8; 4]),
657 Err(RtmpError::Malformed { .. })
658 ));
659 }
660
661 #[test]
662 fn set_chunk_size_serialize_layout_matches_spec() {
663 let bytes = ProtocolControl::SetChunkSize(1).to_bytes();
665 assert_eq!(bytes, vec![0x00, 0x00, 0x00, 0x01]);
666 }
667
668 #[test]
669 fn set_peer_bandwidth_serialize_layout_matches_spec() {
670 let bytes = ProtocolControl::SetPeerBandwidth {
671 ack_window_size: 0x0002_5000,
672 limit_type: LimitType::Dynamic,
673 }
674 .to_bytes();
675 assert_eq!(bytes, vec![0x00, 0x02, 0x50, 0x00, 0x02]);
676 }
677
678 #[test]
679 fn set_peer_bandwidth_wrong_limit_type_mapping_would_fail() {
680 assert_eq!(LimitType::Hard.to_u8(), 0);
682 assert_eq!(LimitType::Dynamic.to_u8(), 2);
683 assert_ne!(LimitType::Hard.to_u8(), LimitType::Dynamic.to_u8());
684 }
685
686 #[test]
687 fn from_message_none_for_non_control_type_id() {
688 let msg = message(msg_type::AUDIO, vec![0u8; 4]);
689 assert!(ProtocolControl::from_message(&msg).unwrap().is_none());
690 }
691
692 #[test]
693 fn from_message_some_for_control_type_id() {
694 let msg = message(
695 msg_type::WINDOW_ACK_SIZE,
696 1_000_000u32.to_be_bytes().to_vec(),
697 );
698 assert!(ProtocolControl::from_message(&msg).unwrap().is_some());
699 }
700
701 #[test]
702 fn to_message_uses_control_csid_and_stream_id() {
703 let msg = ProtocolControl::SetChunkSize(4096).to_message();
704 assert_eq!(msg.chunk_stream_id, CONTROL_CHUNK_STREAM_ID);
705 assert_eq!(msg.message_stream_id, CONTROL_MESSAGE_STREAM_ID);
706 assert_eq!(msg.message_type_id, msg_type::SET_CHUNK_SIZE);
707 }
708
709 #[test]
710 fn protocol_control_display_matches_name() {
711 assert_eq!(
712 ProtocolControl::Acknowledgement(1).to_string(),
713 ProtocolControl::Acknowledgement(1).name()
714 );
715 }
716
717 fn user_control_round_trip(uc: UserControl) {
720 let bytes = uc.to_bytes();
721 let parsed = UserControl::parse(&bytes).unwrap();
722 assert_eq!(parsed, uc);
723 assert_eq!(parsed.to_bytes(), bytes);
724 }
725
726 #[test]
727 fn stream_begin_round_trips() {
728 user_control_round_trip(UserControl::StreamBegin(1));
729 }
730
731 #[test]
732 fn stream_begin_serialize_layout_matches_spec() {
733 let bytes = UserControl::StreamBegin(1).to_bytes();
735 assert_eq!(bytes, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
736 }
737
738 #[test]
739 fn stream_eof_round_trips() {
740 user_control_round_trip(UserControl::StreamEof(1));
741 }
742
743 #[test]
744 fn stream_dry_round_trips() {
745 user_control_round_trip(UserControl::StreamDry(1));
746 }
747
748 #[test]
749 fn set_buffer_length_round_trips() {
750 user_control_round_trip(UserControl::SetBufferLength {
751 stream_id: 1,
752 buffer_ms: 3000,
753 });
754 }
755
756 #[test]
757 fn stream_is_recorded_round_trips() {
758 user_control_round_trip(UserControl::StreamIsRecorded(1));
759 }
760
761 #[test]
762 fn ping_request_round_trips() {
763 user_control_round_trip(UserControl::PingRequest(0x1234_5678));
764 }
765
766 #[test]
767 fn ping_response_round_trips() {
768 user_control_round_trip(UserControl::PingResponse(0x1234_5678));
769 }
770
771 #[test]
772 fn unrecognised_event_type_is_unsupported() {
773 let bytes = [0x00, 0x05, 0x00, 0x00, 0x00, 0x01];
775 assert!(matches!(
776 UserControl::parse(&bytes),
777 Err(RtmpError::Unsupported { .. })
778 ));
779 }
780
781 #[test]
782 fn user_control_event_type_wrong_mapping_would_fail() {
783 assert_eq!(UserControl::StreamBegin(0).event_type(), 0);
786 assert_eq!(UserControl::StreamEof(0).event_type(), 1);
787 }
788
789 #[test]
790 fn user_control_display_matches_name() {
791 assert_eq!(
792 UserControl::StreamBegin(1).to_string(),
793 UserControl::StreamBegin(1).name()
794 );
795 }
796
797 #[test]
798 fn to_message_uses_control_csid_and_user_control_type_id() {
799 let msg = UserControl::StreamBegin(1).to_message();
800 assert_eq!(msg.chunk_stream_id, CONTROL_CHUNK_STREAM_ID);
801 assert_eq!(msg.message_stream_id, CONTROL_MESSAGE_STREAM_ID);
802 assert_eq!(msg.message_type_id, msg_type::USER_CONTROL);
803 }
804}