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