1use std::collections::HashMap;
17
18use broadcast_common::{Parse, Serialize};
19
20use crate::RtmpError;
21
22type Result<T> = core::result::Result<T, RtmpError>;
23
24pub const DEFAULT_CHUNK_SIZE: u32 = 128;
27
28pub const MAX_CHUNK_SIZE: u32 = 16 * 1024 * 1024;
37
38pub const MAX_MESSAGE_LEN: u32 = 8 * 1024 * 1024;
48
49pub const MAX_CSIDS: usize = 64;
57
58pub const EXTENDED_TIMESTAMP_MARKER: u32 = 0x00FF_FFFF;
64
65const U24_LEN: usize = 3;
68const EXTENDED_TIMESTAMP_LEN: usize = 4;
70
71const TYPE0_LEN: usize = 11;
74const TYPE1_LEN: usize = 7;
77const TYPE2_LEN: usize = 3;
80const TYPE3_LEN: usize = 0;
82
83const BASIC_HEADER_CSID_OFFSET: u32 = 64;
86const BASIC_HEADER_2BYTE_MARKER: u8 = 0;
88const BASIC_HEADER_3BYTE_MARKER: u8 = 1;
90const BASIC_HEADER_FMT_SHIFT: u8 = 6;
92const BASIC_HEADER_MARKER_MASK: u8 = 0x3F;
95
96const BASIC_HEADER_1BYTE_MIN_CSID: u32 = 2;
102const BASIC_HEADER_1BYTE_MAX_CSID: u32 = 63;
104const BASIC_HEADER_2BYTE_MIN_CSID: u32 = 64;
106const BASIC_HEADER_2BYTE_MAX_CSID: u32 = 319;
109const BASIC_HEADER_3BYTE_MIN_CSID: u32 = 320;
111const BASIC_HEADER_3BYTE_MAX_CSID: u32 = 65599;
115
116fn read_u24_be(b: &[u8]) -> u32 {
121 (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2])
122}
123
124fn write_u24_be(v: u32, buf: &mut [u8]) {
130 buf[0] = (v >> 16) as u8;
131 buf[1] = (v >> 8) as u8;
132 buf[2] = v as u8;
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140#[non_exhaustive]
141pub enum Fmt {
142 Type0,
144 Type1,
146 Type2,
148 Type3,
150}
151
152impl Fmt {
153 #[must_use]
155 pub fn name(&self) -> &'static str {
156 match self {
157 Fmt::Type0 => "type 0",
158 Fmt::Type1 => "type 1",
159 Fmt::Type2 => "type 2",
160 Fmt::Type3 => "type 3",
161 }
162 }
163
164 pub const fn from_bits(bits: u8) -> core::result::Result<Self, RtmpError> {
169 match bits {
170 0 => Ok(Fmt::Type0),
171 1 => Ok(Fmt::Type1),
172 2 => Ok(Fmt::Type2),
173 3 => Ok(Fmt::Type3),
174 _ => Err(RtmpError::Malformed {
175 what: "chunk fmt (must be 0..=3)",
176 }),
177 }
178 }
179
180 #[must_use]
182 pub const fn to_bits(self) -> u8 {
183 match self {
184 Fmt::Type0 => 0,
185 Fmt::Type1 => 1,
186 Fmt::Type2 => 2,
187 Fmt::Type3 => 3,
188 }
189 }
190}
191
192broadcast_common::impl_spec_display!(Fmt);
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199enum BasicHeaderForm {
200 One,
201 Two,
202 Three,
203}
204
205fn basic_header_form(csid: u32) -> Result<BasicHeaderForm> {
207 match csid {
208 BASIC_HEADER_1BYTE_MIN_CSID..=BASIC_HEADER_1BYTE_MAX_CSID => Ok(BasicHeaderForm::One),
209 BASIC_HEADER_2BYTE_MIN_CSID..=BASIC_HEADER_2BYTE_MAX_CSID => Ok(BasicHeaderForm::Two),
210 BASIC_HEADER_3BYTE_MIN_CSID..=BASIC_HEADER_3BYTE_MAX_CSID => Ok(BasicHeaderForm::Three),
211 _ => Err(RtmpError::Malformed {
212 what: "chunk stream id (must be 2..=65599)",
213 }),
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub struct BasicHeader {
223 pub fmt: Fmt,
225 pub chunk_stream_id: u32,
230}
231
232impl<'a> Parse<'a> for BasicHeader {
233 type Error = RtmpError;
234
235 fn parse(bytes: &'a [u8]) -> Result<Self> {
236 if bytes.is_empty() {
237 return Err(RtmpError::BufferTooShort {
238 need: 1,
239 have: 0,
240 what: "chunk basic header",
241 });
242 }
243 let byte0 = bytes[0];
244 let fmt = Fmt::from_bits((byte0 >> BASIC_HEADER_FMT_SHIFT) & 0x03)?;
245 let marker = byte0 & BASIC_HEADER_MARKER_MASK;
246
247 let chunk_stream_id = match marker {
248 BASIC_HEADER_2BYTE_MARKER => {
249 if bytes.len() < 2 {
250 return Err(RtmpError::BufferTooShort {
251 need: 2,
252 have: bytes.len(),
253 what: "chunk basic header (2-byte form)",
254 });
255 }
256 u32::from(bytes[1]) + BASIC_HEADER_CSID_OFFSET
257 }
258 BASIC_HEADER_3BYTE_MARKER => {
259 if bytes.len() < 3 {
260 return Err(RtmpError::BufferTooShort {
261 need: 3,
262 have: bytes.len(),
263 what: "chunk basic header (3-byte form)",
264 });
265 }
266 u32::from(bytes[1]) + u32::from(bytes[2]) * 256 + BASIC_HEADER_CSID_OFFSET
269 }
270 literal => u32::from(literal),
271 };
272
273 Ok(BasicHeader {
274 fmt,
275 chunk_stream_id,
276 })
277 }
278}
279
280impl Serialize for BasicHeader {
281 type Error = RtmpError;
282
283 fn serialized_len(&self) -> usize {
284 match basic_header_form(self.chunk_stream_id) {
285 Ok(BasicHeaderForm::One) => 1,
286 Ok(BasicHeaderForm::Two) => 2,
287 Ok(BasicHeaderForm::Three) => 3,
288 Err(_) => 3,
291 }
292 }
293
294 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
295 let form = basic_header_form(self.chunk_stream_id)?;
296 let fmt_bits = self.fmt.to_bits() << BASIC_HEADER_FMT_SHIFT;
297
298 match form {
299 BasicHeaderForm::One => {
300 if buf.is_empty() {
301 return Err(RtmpError::BufferTooShort {
302 need: 1,
303 have: 0,
304 what: "chunk basic header output (1-byte form)",
305 });
306 }
307 buf[0] = fmt_bits | (self.chunk_stream_id as u8);
308 Ok(1)
309 }
310 BasicHeaderForm::Two => {
311 if buf.len() < 2 {
312 return Err(RtmpError::BufferTooShort {
313 need: 2,
314 have: buf.len(),
315 what: "chunk basic header output (2-byte form)",
316 });
317 }
318 buf[0] = fmt_bits | BASIC_HEADER_2BYTE_MARKER;
319 buf[1] = (self.chunk_stream_id - BASIC_HEADER_CSID_OFFSET) as u8;
320 Ok(2)
321 }
322 BasicHeaderForm::Three => {
323 if buf.len() < 3 {
324 return Err(RtmpError::BufferTooShort {
325 need: 3,
326 have: buf.len(),
327 what: "chunk basic header output (3-byte form)",
328 });
329 }
330 buf[0] = fmt_bits | BASIC_HEADER_3BYTE_MARKER;
331 let rel = self.chunk_stream_id - BASIC_HEADER_CSID_OFFSET;
332 buf[1] = rel as u8;
333 buf[2] = (rel >> 8) as u8;
334 Ok(3)
335 }
336 }
337 }
338}
339
340fn needs_extended_timestamp(field: u32) -> bool {
345 field >= EXTENDED_TIMESTAMP_MARKER
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363#[non_exhaustive]
364pub enum MessageHeader {
365 Type0 {
369 timestamp: u32,
372 message_length: u32,
374 message_type_id: u8,
376 message_stream_id: u32,
379 },
380 Type1 {
383 timestamp_delta: u32,
386 message_length: u32,
388 message_type_id: u8,
390 },
391 Type2 {
394 timestamp_delta: u32,
397 },
398 Type3,
402}
403
404impl MessageHeader {
405 pub fn parse(fmt: Fmt, bytes: &[u8]) -> Result<(Self, usize)> {
416 match fmt {
417 Fmt::Type0 => {
418 if bytes.len() < TYPE0_LEN {
419 return Err(RtmpError::BufferTooShort {
420 need: TYPE0_LEN,
421 have: bytes.len(),
422 what: "type 0 message header",
423 });
424 }
425 let raw_timestamp = read_u24_be(&bytes[0..U24_LEN]);
426 let message_length = read_u24_be(&bytes[U24_LEN..2 * U24_LEN]);
427 let message_type_id = bytes[2 * U24_LEN];
428 let message_stream_id =
429 u32::from_le_bytes([bytes[7], bytes[8], bytes[9], bytes[10]]);
430
431 let (timestamp, consumed) = resolve_extended(raw_timestamp, bytes, TYPE0_LEN)?;
432
433 Ok((
434 MessageHeader::Type0 {
435 timestamp,
436 message_length,
437 message_type_id,
438 message_stream_id,
439 },
440 consumed,
441 ))
442 }
443 Fmt::Type1 => {
444 if bytes.len() < TYPE1_LEN {
445 return Err(RtmpError::BufferTooShort {
446 need: TYPE1_LEN,
447 have: bytes.len(),
448 what: "type 1 message header",
449 });
450 }
451 let raw_delta = read_u24_be(&bytes[0..U24_LEN]);
452 let message_length = read_u24_be(&bytes[U24_LEN..2 * U24_LEN]);
453 let message_type_id = bytes[2 * U24_LEN];
454
455 let (timestamp_delta, consumed) = resolve_extended(raw_delta, bytes, TYPE1_LEN)?;
456
457 Ok((
458 MessageHeader::Type1 {
459 timestamp_delta,
460 message_length,
461 message_type_id,
462 },
463 consumed,
464 ))
465 }
466 Fmt::Type2 => {
467 if bytes.len() < TYPE2_LEN {
468 return Err(RtmpError::BufferTooShort {
469 need: TYPE2_LEN,
470 have: bytes.len(),
471 what: "type 2 message header",
472 });
473 }
474 let raw_delta = read_u24_be(&bytes[0..U24_LEN]);
475 let (timestamp_delta, consumed) = resolve_extended(raw_delta, bytes, TYPE2_LEN)?;
476
477 Ok((MessageHeader::Type2 { timestamp_delta }, consumed))
478 }
479 Fmt::Type3 => Ok((MessageHeader::Type3, TYPE3_LEN)),
480 }
481 }
482}
483
484fn resolve_extended(raw: u32, bytes: &[u8], fixed_len: usize) -> Result<(u32, usize)> {
489 if raw == EXTENDED_TIMESTAMP_MARKER {
490 let need = fixed_len + EXTENDED_TIMESTAMP_LEN;
491 if bytes.len() < need {
492 return Err(RtmpError::BufferTooShort {
493 need,
494 have: bytes.len(),
495 what: "extended timestamp",
496 });
497 }
498 let ext = u32::from_be_bytes([
499 bytes[fixed_len],
500 bytes[fixed_len + 1],
501 bytes[fixed_len + 2],
502 bytes[fixed_len + 3],
503 ]);
504 Ok((ext, need))
505 } else {
506 Ok((raw, fixed_len))
507 }
508}
509
510impl Serialize for MessageHeader {
511 type Error = RtmpError;
512
513 fn serialized_len(&self) -> usize {
514 match self {
515 MessageHeader::Type0 { timestamp, .. } => TYPE0_LEN + extended_len(*timestamp),
516 MessageHeader::Type1 {
517 timestamp_delta, ..
518 } => TYPE1_LEN + extended_len(*timestamp_delta),
519 MessageHeader::Type2 { timestamp_delta } => TYPE2_LEN + extended_len(*timestamp_delta),
520 MessageHeader::Type3 => TYPE3_LEN,
521 }
522 }
523
524 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
525 match *self {
526 MessageHeader::Type0 {
527 timestamp,
528 message_length,
529 message_type_id,
530 message_stream_id,
531 } => {
532 let extended = needs_extended_timestamp(timestamp);
533 let written = TYPE0_LEN + if extended { EXTENDED_TIMESTAMP_LEN } else { 0 };
534 if buf.len() < written {
535 return Err(RtmpError::BufferTooShort {
536 need: written,
537 have: buf.len(),
538 what: "type 0 message header output",
539 });
540 }
541 let field = if extended {
542 EXTENDED_TIMESTAMP_MARKER
543 } else {
544 timestamp
545 };
546 write_u24_be(field, &mut buf[0..U24_LEN]);
547 write_u24_be(message_length, &mut buf[U24_LEN..2 * U24_LEN]);
548 buf[2 * U24_LEN] = message_type_id;
549 buf[7..11].copy_from_slice(&message_stream_id.to_le_bytes());
550 if extended {
551 buf[11..15].copy_from_slice(×tamp.to_be_bytes());
552 }
553 Ok(written)
554 }
555 MessageHeader::Type1 {
556 timestamp_delta,
557 message_length,
558 message_type_id,
559 } => {
560 let extended = needs_extended_timestamp(timestamp_delta);
561 let written = TYPE1_LEN + if extended { EXTENDED_TIMESTAMP_LEN } else { 0 };
562 if buf.len() < written {
563 return Err(RtmpError::BufferTooShort {
564 need: written,
565 have: buf.len(),
566 what: "type 1 message header output",
567 });
568 }
569 let field = if extended {
570 EXTENDED_TIMESTAMP_MARKER
571 } else {
572 timestamp_delta
573 };
574 write_u24_be(field, &mut buf[0..U24_LEN]);
575 write_u24_be(message_length, &mut buf[U24_LEN..2 * U24_LEN]);
576 buf[2 * U24_LEN] = message_type_id;
577 if extended {
578 buf[7..11].copy_from_slice(×tamp_delta.to_be_bytes());
579 }
580 Ok(written)
581 }
582 MessageHeader::Type2 { timestamp_delta } => {
583 let extended = needs_extended_timestamp(timestamp_delta);
584 let written = TYPE2_LEN + if extended { EXTENDED_TIMESTAMP_LEN } else { 0 };
585 if buf.len() < written {
586 return Err(RtmpError::BufferTooShort {
587 need: written,
588 have: buf.len(),
589 what: "type 2 message header output",
590 });
591 }
592 let field = if extended {
593 EXTENDED_TIMESTAMP_MARKER
594 } else {
595 timestamp_delta
596 };
597 write_u24_be(field, &mut buf[0..U24_LEN]);
598 if extended {
599 buf[3..7].copy_from_slice(×tamp_delta.to_be_bytes());
600 }
601 Ok(written)
602 }
603 MessageHeader::Type3 => Ok(0),
604 }
605 }
606}
607
608fn extended_len(field: u32) -> usize {
611 if needs_extended_timestamp(field) {
612 EXTENDED_TIMESTAMP_LEN
613 } else {
614 0
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Eq)]
627pub struct Message {
628 pub chunk_stream_id: u32,
630 pub timestamp: u32,
633 pub message_type_id: u8,
635 pub message_stream_id: u32,
637 pub payload: Vec<u8>,
640}
641
642#[derive(Debug, Clone, Default)]
648struct CsidState {
649 timestamp: u32,
651 timestamp_delta: u32,
658 message_length: u32,
660 message_type_id: u8,
662 message_stream_id: u32,
664 extended: bool,
669 initialized: bool,
673 in_progress: bool,
681 payload: Vec<u8>,
685}
686
687#[derive(Debug)]
694pub struct ChunkAssembler {
695 chunk_size: u32,
696 csids: HashMap<u32, CsidState>,
697 pending: Vec<u8>,
701}
702
703impl Default for ChunkAssembler {
704 fn default() -> Self {
705 Self::new()
706 }
707}
708
709impl ChunkAssembler {
710 #[must_use]
712 pub fn new() -> Self {
713 Self {
714 chunk_size: DEFAULT_CHUNK_SIZE,
715 csids: HashMap::new(),
716 pending: Vec::new(),
717 }
718 }
719
720 pub fn set_chunk_size(&mut self, n: u32) {
726 self.chunk_size = n.clamp(1, MAX_CHUNK_SIZE);
727 }
728
729 pub fn push(&mut self, input: &[u8]) -> Result<Vec<Message>> {
751 self.feed(input);
752 let mut out = Vec::new();
753 while let Some(msg) = self.next_message()? {
754 out.push(msg);
755 }
756 Ok(out)
757 }
758
759 pub(crate) fn feed(&mut self, input: &[u8]) {
764 self.pending.extend_from_slice(input);
765 }
766
767 pub(crate) fn next_message(&mut self) -> Result<Option<Message>> {
777 loop {
778 match Self::try_parse_one(&self.pending, &self.csids, self.chunk_size) {
779 Ok(Some(parsed)) => {
780 self.pending.drain(..parsed.consumed);
781 let state = self.csids.entry(parsed.csid).or_default();
782 state.timestamp = parsed.timestamp;
783 state.timestamp_delta = parsed.timestamp_delta;
784 state.message_length = parsed.message_length;
785 state.message_type_id = parsed.message_type_id;
786 state.message_stream_id = parsed.message_stream_id;
787 state.extended = parsed.extended;
788 state.initialized = true;
789 if parsed.payload.len() as u32 == parsed.message_length {
790 state.payload.clear();
791 state.in_progress = false;
792 return Ok(Some(Message {
793 chunk_stream_id: parsed.csid,
794 timestamp: parsed.timestamp,
795 message_type_id: parsed.message_type_id,
796 message_stream_id: parsed.message_stream_id,
797 payload: parsed.payload,
798 }));
799 }
800 state.payload = parsed.payload;
801 state.in_progress = true;
802 }
806 Ok(None) => return Ok(None),
807 Err(e) => return Err(e),
808 }
809 }
810 }
811
812 fn try_parse_one(
823 buf: &[u8],
824 states: &HashMap<u32, CsidState>,
825 chunk_size: u32,
826 ) -> Result<Option<ParsedChunk>> {
827 let bh = match BasicHeader::parse(buf) {
828 Ok(bh) => bh,
829 Err(RtmpError::BufferTooShort { .. }) => return Ok(None),
830 Err(e) => return Err(e),
831 };
832 let marker = buf[0] & BASIC_HEADER_MARKER_MASK;
839 let header_len = match marker {
840 BASIC_HEADER_2BYTE_MARKER => 2,
841 BASIC_HEADER_3BYTE_MARKER => 3,
842 _ => 1,
843 };
844
845 let existing = states.get(&bh.chunk_stream_id);
846
847 if existing.is_none() && states.len() >= MAX_CSIDS {
852 return Err(RtmpError::Malformed {
853 what: "too many concurrent chunk stream ids (csid flood)",
854 });
855 }
856
857 let rest = &buf[header_len..];
858 let (mh, mh_consumed) = match MessageHeader::parse(bh.fmt, rest) {
859 Ok(v) => v,
860 Err(RtmpError::BufferTooShort { .. }) => return Ok(None),
861 Err(e) => return Err(e),
862 };
863 let mut consumed = header_len + mh_consumed;
864
865 let (resolved, starts_new) = match (bh.fmt, mh) {
869 (
870 Fmt::Type0,
871 MessageHeader::Type0 {
872 timestamp,
873 message_length,
874 message_type_id,
875 message_stream_id,
876 },
877 ) => {
878 let used_extended = mh_consumed > TYPE0_LEN;
879 (
880 ResolvedHeader {
881 timestamp_delta: timestamp,
885 timestamp,
886 message_length,
887 message_type_id,
888 message_stream_id,
889 extended: used_extended,
890 },
891 true,
892 )
893 }
894 (
902 Fmt::Type1,
903 MessageHeader::Type1 {
904 timestamp_delta,
905 message_length,
906 message_type_id,
907 },
908 ) => {
909 let existing = existing.ok_or(RtmpError::Malformed {
910 what: "type 1 chunk header on a csid with no prior chunk to inherit from",
911 })?;
912 let used_extended = mh_consumed > TYPE1_LEN;
913 (
914 ResolvedHeader {
915 timestamp: existing.timestamp.wrapping_add(timestamp_delta),
916 timestamp_delta,
917 message_length,
918 message_type_id,
919 message_stream_id: existing.message_stream_id,
920 extended: used_extended,
921 },
922 true,
923 )
924 }
925 (Fmt::Type2, MessageHeader::Type2 { timestamp_delta }) => {
926 let existing = existing.ok_or(RtmpError::Malformed {
927 what: "type 2 chunk header on a csid with no prior chunk to inherit from",
928 })?;
929 let used_extended = mh_consumed > TYPE2_LEN;
930 (
931 ResolvedHeader {
932 timestamp: existing.timestamp.wrapping_add(timestamp_delta),
933 timestamp_delta,
934 message_length: existing.message_length,
935 message_type_id: existing.message_type_id,
936 message_stream_id: existing.message_stream_id,
937 extended: used_extended,
938 },
939 true,
940 )
941 }
942 (Fmt::Type3, MessageHeader::Type3) => {
943 let existing = existing.ok_or(RtmpError::Malformed {
944 what: "type 3 chunk header on a csid with no prior chunk to inherit from",
945 })?;
946 let continuation = existing.in_progress;
947 if existing.extended {
948 if buf.len() < consumed + EXTENDED_TIMESTAMP_LEN {
949 return Ok(None);
950 }
951 consumed += EXTENDED_TIMESTAMP_LEN;
958 }
959 if continuation {
960 (
961 ResolvedHeader {
962 timestamp: existing.timestamp,
963 timestamp_delta: existing.timestamp_delta,
964 message_length: existing.message_length,
965 message_type_id: existing.message_type_id,
966 message_stream_id: existing.message_stream_id,
967 extended: existing.extended,
968 },
969 false,
970 )
971 } else {
972 (
973 ResolvedHeader {
974 timestamp: existing.timestamp.wrapping_add(existing.timestamp_delta),
975 timestamp_delta: existing.timestamp_delta,
976 message_length: existing.message_length,
977 message_type_id: existing.message_type_id,
978 message_stream_id: existing.message_stream_id,
979 extended: existing.extended,
980 },
981 true,
982 )
983 }
984 }
985 _ => unreachable!("MessageHeader::parse always returns the variant for its Fmt"),
989 };
990
991 if resolved.message_length > MAX_MESSAGE_LEN {
997 return Err(RtmpError::Malformed {
998 what: "message length exceeds the maximum accepted message size",
999 });
1000 }
1001
1002 let already_accumulated = if starts_new {
1003 0
1004 } else {
1005 existing.map(|s| s.payload.len()).unwrap_or(0)
1006 };
1007 let remaining_needed =
1008 (resolved.message_length as usize).saturating_sub(already_accumulated);
1009 let take = (chunk_size as usize).min(remaining_needed);
1010
1011 if buf.len() < consumed + take {
1012 return Ok(None);
1013 }
1014
1015 let mut payload = if starts_new {
1023 Vec::new()
1024 } else {
1025 existing.map(|s| s.payload.clone()).unwrap_or_default()
1026 };
1027 payload.extend_from_slice(&buf[consumed..consumed + take]);
1028 consumed += take;
1029
1030 Ok(Some(ParsedChunk {
1031 csid: bh.chunk_stream_id,
1032 consumed,
1033 timestamp: resolved.timestamp,
1034 timestamp_delta: resolved.timestamp_delta,
1035 message_length: resolved.message_length,
1036 message_type_id: resolved.message_type_id,
1037 message_stream_id: resolved.message_stream_id,
1038 extended: resolved.extended,
1039 payload,
1040 }))
1041 }
1042}
1043
1044struct ResolvedHeader {
1047 timestamp: u32,
1048 timestamp_delta: u32,
1049 message_length: u32,
1050 message_type_id: u8,
1051 message_stream_id: u32,
1052 extended: bool,
1053}
1054
1055struct ParsedChunk {
1058 csid: u32,
1059 consumed: usize,
1060 timestamp: u32,
1061 timestamp_delta: u32,
1062 message_length: u32,
1063 message_type_id: u8,
1064 message_stream_id: u32,
1065 extended: bool,
1066 payload: Vec<u8>,
1067}
1068
1069#[derive(Debug, Clone)]
1080pub struct ChunkWriter {
1081 chunk_size: u32,
1082}
1083
1084impl Default for ChunkWriter {
1085 fn default() -> Self {
1086 Self::new()
1087 }
1088}
1089
1090impl ChunkWriter {
1091 #[must_use]
1093 pub fn new() -> Self {
1094 Self {
1095 chunk_size: DEFAULT_CHUNK_SIZE,
1096 }
1097 }
1098
1099 pub fn set_chunk_size(&mut self, n: u32) {
1104 self.chunk_size = n.min(MAX_CHUNK_SIZE);
1105 }
1106
1107 #[must_use]
1119 pub fn write(&mut self, msg: &Message) -> Vec<u8> {
1120 let chunk_size = (self.chunk_size as usize).max(1);
1121 let message_length = msg.payload.len() as u32;
1122 let extended = needs_extended_timestamp(msg.timestamp);
1123
1124 let mut out = Vec::with_capacity(TYPE0_LEN + msg.payload.len() + 16);
1125
1126 let bh0 = BasicHeader {
1127 fmt: Fmt::Type0,
1128 chunk_stream_id: msg.chunk_stream_id,
1129 };
1130 let mh0 = MessageHeader::Type0 {
1131 timestamp: msg.timestamp,
1132 message_length,
1133 message_type_id: msg.message_type_id,
1134 message_stream_id: msg.message_stream_id,
1135 };
1136 write_serialized(&mut out, &bh0);
1137 write_serialized(&mut out, &mh0);
1138
1139 let mut offset = 0usize;
1140 let take0 = chunk_size.min(msg.payload.len());
1141 out.extend_from_slice(&msg.payload[offset..offset + take0]);
1142 offset += take0;
1143
1144 while offset < msg.payload.len() {
1145 let bh = BasicHeader {
1146 fmt: Fmt::Type3,
1147 chunk_stream_id: msg.chunk_stream_id,
1148 };
1149 write_serialized(&mut out, &bh);
1150 if extended {
1151 out.extend_from_slice(&msg.timestamp.to_be_bytes());
1152 }
1153 let take = chunk_size.min(msg.payload.len() - offset);
1154 out.extend_from_slice(&msg.payload[offset..offset + take]);
1155 offset += take;
1156 }
1157
1158 out
1159 }
1160}
1161
1162fn write_serialized<T: Serialize<Error = RtmpError>>(out: &mut Vec<u8>, item: &T) {
1169 let len = item.serialized_len();
1170 let start = out.len();
1171 out.resize(start + len, 0);
1172 let n = item
1173 .serialize_into(&mut out[start..])
1174 .expect("valid chunk_stream_id (2..=65599) is a ChunkWriter::write precondition");
1175 out.truncate(start + n);
1176}
1177
1178#[cfg(test)]
1179mod tests {
1180 use super::*;
1181
1182 #[test]
1185 fn u24_round_trip_zero() {
1186 let mut buf = [0xFFu8; U24_LEN];
1187 write_u24_be(0, &mut buf);
1188 assert_eq!(buf, [0, 0, 0]);
1189 assert_eq!(read_u24_be(&buf), 0);
1190 }
1191
1192 #[test]
1193 fn u24_round_trip_max() {
1194 let mut buf = [0u8; U24_LEN];
1195 write_u24_be(0x00FF_FFFF, &mut buf);
1196 assert_eq!(buf, [0xFF, 0xFF, 0xFF]);
1197 assert_eq!(read_u24_be(&buf), 0x00FF_FFFF);
1198 }
1199
1200 #[test]
1201 fn u24_round_trip_mid_value() {
1202 let mut buf = [0u8; U24_LEN];
1203 write_u24_be(0x0012_3456, &mut buf);
1204 assert_eq!(buf, [0x12, 0x34, 0x56]);
1205 assert_eq!(read_u24_be(&buf), 0x0012_3456);
1206 }
1207
1208 #[test]
1211 fn fmt_from_bits_round_trip() {
1212 for (bits, fmt) in [
1213 (0u8, Fmt::Type0),
1214 (1, Fmt::Type1),
1215 (2, Fmt::Type2),
1216 (3, Fmt::Type3),
1217 ] {
1218 let parsed = Fmt::from_bits(bits).unwrap();
1219 assert_eq!(parsed, fmt);
1220 assert_eq!(parsed.to_bits(), bits);
1221 }
1222 }
1223
1224 #[test]
1225 fn fmt_from_bits_out_of_range_is_malformed() {
1226 assert!(matches!(
1227 Fmt::from_bits(4),
1228 Err(RtmpError::Malformed { .. })
1229 ));
1230 }
1231
1232 #[test]
1233 fn fmt_display_matches_name() {
1234 assert_eq!(Fmt::Type0.to_string(), "type 0");
1235 assert_eq!(Fmt::Type3.to_string(), "type 3");
1236 }
1237
1238 #[test]
1241 fn basic_header_one_byte_form_round_trip_build_serialize_parse() {
1242 for csid in [BASIC_HEADER_1BYTE_MIN_CSID, 5, BASIC_HEADER_1BYTE_MAX_CSID] {
1243 let bh = BasicHeader {
1244 fmt: Fmt::Type1,
1245 chunk_stream_id: csid,
1246 };
1247 let mut buf = [0u8; 1];
1248 let n = bh.serialize_into(&mut buf).unwrap();
1249 assert_eq!(n, 1, "csid {csid} must use the 1-byte form");
1250 let parsed = BasicHeader::parse(&buf).unwrap();
1251 assert_eq!(parsed, bh);
1252 }
1253 }
1254
1255 #[test]
1256 fn basic_header_one_byte_form_parse_serialize_byte_identical() {
1257 let bytes = [0x45u8];
1259 let bh = BasicHeader::parse(&bytes).unwrap();
1260 assert_eq!(bh.fmt, Fmt::Type1);
1261 assert_eq!(bh.chunk_stream_id, 5);
1262 let mut buf = [0u8; 1];
1263 bh.serialize_into(&mut buf).unwrap();
1264 assert_eq!(buf, bytes);
1265 }
1266
1267 #[test]
1270 fn basic_header_two_byte_form_round_trip_boundaries() {
1271 for csid in [
1272 BASIC_HEADER_2BYTE_MIN_CSID,
1273 200,
1274 BASIC_HEADER_2BYTE_MAX_CSID,
1275 ] {
1276 let bh = BasicHeader {
1277 fmt: Fmt::Type2,
1278 chunk_stream_id: csid,
1279 };
1280 let mut buf = [0u8; 2];
1281 let n = bh.serialize_into(&mut buf).unwrap();
1282 assert_eq!(n, 2, "csid {csid} must use the minimal 2-byte form");
1283 let parsed = BasicHeader::parse(&buf).unwrap();
1284 assert_eq!(parsed, bh);
1285 }
1286 }
1287
1288 #[test]
1289 fn basic_header_two_byte_form_parse_serialize_byte_identical() {
1290 let bytes = [0b10_000000u8, 0x00];
1292 let bh = BasicHeader::parse(&bytes).unwrap();
1293 assert_eq!(bh.fmt, Fmt::Type2);
1294 assert_eq!(bh.chunk_stream_id, 64);
1295 let mut buf = [0u8; 2];
1296 bh.serialize_into(&mut buf).unwrap();
1297 assert_eq!(buf, bytes);
1298 }
1299
1300 #[test]
1303 fn basic_header_three_byte_form_round_trip_boundaries() {
1304 for csid in [
1305 BASIC_HEADER_3BYTE_MIN_CSID,
1306 40000,
1307 BASIC_HEADER_3BYTE_MAX_CSID,
1308 ] {
1309 let bh = BasicHeader {
1310 fmt: Fmt::Type3,
1311 chunk_stream_id: csid,
1312 };
1313 let mut buf = [0u8; 3];
1314 let n = bh.serialize_into(&mut buf).unwrap();
1315 assert_eq!(n, 3, "csid {csid} must use the 3-byte form");
1316 let parsed = BasicHeader::parse(&buf).unwrap();
1317 assert_eq!(parsed, bh);
1318 }
1319 }
1320
1321 #[test]
1322 fn basic_header_three_byte_form_parse_serialize_byte_identical() {
1323 let bytes = [0b00_000001u8, 0xFF, 0xFF];
1325 let bh = BasicHeader::parse(&bytes).unwrap();
1326 assert_eq!(bh.fmt, Fmt::Type0);
1327 assert_eq!(bh.chunk_stream_id, BASIC_HEADER_3BYTE_MAX_CSID);
1328 let mut buf = [0u8; 3];
1329 bh.serialize_into(&mut buf).unwrap();
1330 assert_eq!(buf, bytes);
1331 }
1332
1333 #[test]
1334 fn basic_header_2byte_and_3byte_csid_are_little_endian() {
1335 let bh = BasicHeader {
1337 fmt: Fmt::Type0,
1338 chunk_stream_id: 64 + 0x0102,
1339 };
1340 let mut buf = [0u8; 3];
1341 bh.serialize_into(&mut buf).unwrap();
1342 assert_eq!(buf[1], 0x02, "low byte of csid-64 must come first");
1343 assert_eq!(buf[2], 0x01, "high byte of csid-64 must come second");
1344 assert_eq!(BasicHeader::parse(&buf).unwrap(), bh);
1345 }
1346
1347 #[test]
1350 fn basic_header_csid_zero_is_malformed_on_serialize() {
1351 let bh = BasicHeader {
1352 fmt: Fmt::Type0,
1353 chunk_stream_id: 0,
1354 };
1355 let mut buf = [0u8; 3];
1356 assert!(matches!(
1357 bh.serialize_into(&mut buf),
1358 Err(RtmpError::Malformed { .. })
1359 ));
1360 }
1361
1362 #[test]
1363 fn basic_header_csid_one_is_malformed_on_serialize() {
1364 let bh = BasicHeader {
1365 fmt: Fmt::Type0,
1366 chunk_stream_id: 1,
1367 };
1368 let mut buf = [0u8; 3];
1369 assert!(matches!(
1370 bh.serialize_into(&mut buf),
1371 Err(RtmpError::Malformed { .. })
1372 ));
1373 }
1374
1375 #[test]
1376 fn basic_header_csid_above_max_is_malformed_on_serialize() {
1377 let bh = BasicHeader {
1378 fmt: Fmt::Type0,
1379 chunk_stream_id: BASIC_HEADER_3BYTE_MAX_CSID + 1,
1380 };
1381 let mut buf = [0u8; 3];
1382 assert!(matches!(
1383 bh.serialize_into(&mut buf),
1384 Err(RtmpError::Malformed { .. })
1385 ));
1386 }
1387
1388 #[test]
1389 fn basic_header_empty_input_is_buffer_too_short() {
1390 assert!(matches!(
1391 BasicHeader::parse(&[]),
1392 Err(RtmpError::BufferTooShort {
1393 need: 1,
1394 have: 0,
1395 ..
1396 })
1397 ));
1398 }
1399
1400 #[test]
1401 fn basic_header_truncated_two_byte_form_is_buffer_too_short() {
1402 let bytes = [0b00_000000u8]; assert!(matches!(
1404 BasicHeader::parse(&bytes),
1405 Err(RtmpError::BufferTooShort {
1406 need: 2,
1407 have: 1,
1408 ..
1409 })
1410 ));
1411 }
1412
1413 #[test]
1414 fn basic_header_truncated_three_byte_form_is_buffer_too_short() {
1415 let bytes = [0b00_000001u8, 0xAB]; assert!(matches!(
1417 BasicHeader::parse(&bytes),
1418 Err(RtmpError::BufferTooShort {
1419 need: 3,
1420 have: 2,
1421 ..
1422 })
1423 ));
1424 }
1425
1426 #[test]
1429 fn type0_round_trip_build_serialize_parse_no_extended() {
1430 let mh = MessageHeader::Type0 {
1431 timestamp: 0x0011_2233,
1432 message_length: 0x0004_5566,
1433 message_type_id: 0x09,
1434 message_stream_id: 0xAABB_CCDD,
1435 };
1436 let mut buf = [0u8; TYPE0_LEN];
1437 let n = mh.serialize_into(&mut buf).unwrap();
1438 assert_eq!(n, TYPE0_LEN);
1439 let (parsed, consumed) = MessageHeader::parse(Fmt::Type0, &buf).unwrap();
1440 assert_eq!(consumed, TYPE0_LEN);
1441 assert_eq!(parsed, mh);
1442 }
1443
1444 #[test]
1445 fn type0_parse_serialize_byte_identical_no_extended_and_le_stream_id() {
1446 let bytes: [u8; TYPE0_LEN] = [
1451 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x09, 0xDD, 0xCC, 0xBB, 0xAA, ];
1456 let (mh, consumed) = MessageHeader::parse(Fmt::Type0, &bytes).unwrap();
1457 assert_eq!(consumed, TYPE0_LEN);
1458 assert_eq!(
1459 mh,
1460 MessageHeader::Type0 {
1461 timestamp: 0x0000_1122,
1462 message_length: 0x0033_4455,
1463 message_type_id: 0x09,
1464 message_stream_id: 0xAABB_CCDD,
1465 }
1466 );
1467 let mut buf = [0u8; TYPE0_LEN];
1468 mh.serialize_into(&mut buf).unwrap();
1469 assert_eq!(
1470 buf, bytes,
1471 "byte-identical round trip, LE stream id included"
1472 );
1473 }
1474
1475 #[test]
1476 fn type0_extended_timestamp_parse_serialize_byte_identical() {
1477 let bytes: [u8; TYPE0_LEN + 4] = [
1481 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x08, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, ];
1487 let (mh, consumed) = MessageHeader::parse(Fmt::Type0, &bytes).unwrap();
1488 assert_eq!(consumed, TYPE0_LEN + 4);
1489 assert_eq!(
1490 mh,
1491 MessageHeader::Type0 {
1492 timestamp: 0x0102_0304,
1493 message_length: 0x0000_0010,
1494 message_type_id: 0x08,
1495 message_stream_id: 1,
1496 }
1497 );
1498 let mut buf = [0u8; TYPE0_LEN + 4];
1499 let n = mh.serialize_into(&mut buf).unwrap();
1500 assert_eq!(n, TYPE0_LEN + 4);
1501 assert_eq!(
1502 buf, bytes,
1503 "extended timestamp path must round-trip byte-identically"
1504 );
1505 }
1506
1507 #[test]
1508 fn type0_timestamp_exactly_at_marker_boundary_uses_extended_path() {
1509 let mh = MessageHeader::Type0 {
1512 timestamp: EXTENDED_TIMESTAMP_MARKER,
1513 message_length: 10,
1514 message_type_id: 1,
1515 message_stream_id: 0,
1516 };
1517 assert_eq!(mh.serialized_len(), TYPE0_LEN + 4);
1518 let mut buf = [0u8; TYPE0_LEN + 4];
1519 let n = mh.serialize_into(&mut buf).unwrap();
1520 assert_eq!(n, TYPE0_LEN + 4);
1521 assert_eq!(
1522 &buf[0..3],
1523 [0xFF, 0xFF, 0xFF],
1524 "24-bit field must be the sentinel"
1525 );
1526 assert_eq!(
1527 &buf[11..15],
1528 &EXTENDED_TIMESTAMP_MARKER.to_be_bytes()[..],
1529 "extended field carries the real value"
1530 );
1531 let (parsed, consumed) = MessageHeader::parse(Fmt::Type0, &buf).unwrap();
1532 assert_eq!(consumed, TYPE0_LEN + 4);
1533 assert_eq!(parsed, mh);
1534 }
1535
1536 #[test]
1539 fn type1_round_trip_build_serialize_parse_no_extended() {
1540 let mh = MessageHeader::Type1 {
1541 timestamp_delta: 20,
1542 message_length: 32,
1543 message_type_id: 8,
1544 };
1545 let mut buf = [0u8; TYPE1_LEN];
1546 let n = mh.serialize_into(&mut buf).unwrap();
1547 assert_eq!(n, TYPE1_LEN);
1548 let (parsed, consumed) = MessageHeader::parse(Fmt::Type1, &buf).unwrap();
1549 assert_eq!(consumed, TYPE1_LEN);
1550 assert_eq!(parsed, mh);
1551 }
1552
1553 #[test]
1554 fn type1_extended_timestamp_parse_serialize_byte_identical() {
1555 let bytes: [u8; TYPE1_LEN + 4] = [
1556 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, ];
1561 let (mh, consumed) = MessageHeader::parse(Fmt::Type1, &bytes).unwrap();
1562 assert_eq!(consumed, TYPE1_LEN + 4);
1563 assert_eq!(
1564 mh,
1565 MessageHeader::Type1 {
1566 timestamp_delta: 0x0A0B_0C0D,
1567 message_length: 0x0000_0020,
1568 message_type_id: 0x09,
1569 }
1570 );
1571 let mut buf = [0u8; TYPE1_LEN + 4];
1572 mh.serialize_into(&mut buf).unwrap();
1573 assert_eq!(buf, bytes);
1574 }
1575
1576 #[test]
1579 fn type2_round_trip_build_serialize_parse_no_extended() {
1580 let mh = MessageHeader::Type2 {
1581 timestamp_delta: 20,
1582 };
1583 let mut buf = [0u8; TYPE2_LEN];
1584 let n = mh.serialize_into(&mut buf).unwrap();
1585 assert_eq!(n, TYPE2_LEN);
1586 let (parsed, consumed) = MessageHeader::parse(Fmt::Type2, &buf).unwrap();
1587 assert_eq!(consumed, TYPE2_LEN);
1588 assert_eq!(parsed, mh);
1589 }
1590
1591 #[test]
1592 fn type2_extended_timestamp_parse_serialize_byte_identical() {
1593 let bytes: [u8; TYPE2_LEN + 4] = [
1594 0xFF, 0xFF, 0xFF, 0x11, 0x22, 0x33, 0x44, ];
1597 let (mh, consumed) = MessageHeader::parse(Fmt::Type2, &bytes).unwrap();
1598 assert_eq!(consumed, TYPE2_LEN + 4);
1599 assert_eq!(
1600 mh,
1601 MessageHeader::Type2 {
1602 timestamp_delta: 0x1122_3344,
1603 }
1604 );
1605 let mut buf = [0u8; TYPE2_LEN + 4];
1606 mh.serialize_into(&mut buf).unwrap();
1607 assert_eq!(buf, bytes);
1608 }
1609
1610 #[test]
1613 fn type3_round_trip_is_zero_bytes() {
1614 let mh = MessageHeader::Type3;
1615 assert_eq!(mh.serialized_len(), 0);
1616 let mut buf: [u8; 0] = [];
1617 let n = mh.serialize_into(&mut buf).unwrap();
1618 assert_eq!(n, 0);
1619 let (parsed, consumed) = MessageHeader::parse(Fmt::Type3, &[]).unwrap();
1620 assert_eq!(consumed, 0);
1621 assert_eq!(parsed, MessageHeader::Type3);
1622 }
1623
1624 #[test]
1627 fn type0_truncated_input_is_buffer_too_short() {
1628 let bytes = [0u8; TYPE0_LEN - 1];
1629 assert!(matches!(
1630 MessageHeader::parse(Fmt::Type0, &bytes),
1631 Err(RtmpError::BufferTooShort {
1632 need: TYPE0_LEN,
1633 ..
1634 })
1635 ));
1636 }
1637
1638 #[test]
1639 fn type0_extended_marker_but_truncated_extended_field_is_buffer_too_short() {
1640 let mut bytes = [0u8; TYPE0_LEN + 2]; bytes[0] = 0xFF;
1642 bytes[1] = 0xFF;
1643 bytes[2] = 0xFF;
1644 assert!(matches!(
1645 MessageHeader::parse(Fmt::Type0, &bytes),
1646 Err(RtmpError::BufferTooShort {
1647 need,
1648 ..
1649 }) if need == TYPE0_LEN + 4
1650 ));
1651 }
1652
1653 #[test]
1664 fn message_stream_id_le_differs_from_be_for_asymmetric_value() {
1665 let v: u32 = 0xAABB_CCDD;
1669 assert_ne!(v.to_le_bytes(), v.to_be_bytes());
1670 }
1671
1672 fn msg(csid: u32, timestamp: u32, type_id: u8, stream_id: u32, payload: Vec<u8>) -> Message {
1675 Message {
1676 chunk_stream_id: csid,
1677 timestamp,
1678 message_type_id: type_id,
1679 message_stream_id: stream_id,
1680 payload,
1681 }
1682 }
1683
1684 #[test]
1685 fn writer_assembler_round_trip_small_message_single_chunk() {
1686 let original = msg(4, 1000, 9, 1, vec![0xAB; 50]);
1687 let mut writer = ChunkWriter::new();
1688 let bytes = writer.write(&original);
1689
1690 let mut assembler = ChunkAssembler::new();
1691 let out = assembler.push(&bytes).unwrap();
1692 assert_eq!(out.len(), 1, "one message must come back out");
1693 assert_eq!(out[0], original);
1694 }
1695
1696 #[test]
1697 fn writer_assembler_round_trip_message_larger_than_chunk_size() {
1698 let original = msg(6, 5000, 9, 42, (0u8..=255).cycle().take(300).collect());
1701 let mut writer = ChunkWriter::new();
1702 let bytes = writer.write(&original);
1703
1704 let expected_len = 1 + TYPE0_LEN + 128 + (1 + 128) + (1 + 44);
1709 assert_eq!(bytes.len(), expected_len);
1710
1711 let mut assembler = ChunkAssembler::new();
1712 let out = assembler.push(&bytes).unwrap();
1713 assert_eq!(
1714 out.len(),
1715 1,
1716 "the 3 chunks must reassemble into ONE message"
1717 );
1718 assert_eq!(out[0], original);
1719 assert_eq!(out[0].payload.len(), 300);
1720 }
1721
1722 #[test]
1723 fn assembler_multi_chunk_payload_reassembled_in_order() {
1724 let mut assembler = ChunkAssembler::new();
1729 assembler.set_chunk_size(4);
1730
1731 let bh0 = BasicHeader {
1732 fmt: Fmt::Type0,
1733 chunk_stream_id: 3,
1734 };
1735 let mh0 = MessageHeader::Type0 {
1736 timestamp: 0,
1737 message_length: 10,
1738 message_type_id: 8,
1739 message_stream_id: 0,
1740 };
1741 let mut input = Vec::new();
1742 write_serialized(&mut input, &bh0);
1743 write_serialized(&mut input, &mh0);
1744 input.extend_from_slice(&[1, 2, 3, 4]);
1745
1746 let bh3 = BasicHeader {
1747 fmt: Fmt::Type3,
1748 chunk_stream_id: 3,
1749 };
1750 write_serialized(&mut input, &bh3);
1751 input.extend_from_slice(&[5, 6, 7, 8]);
1752 write_serialized(&mut input, &bh3);
1753 input.extend_from_slice(&[9, 10]);
1754
1755 let out = assembler.push(&input).unwrap();
1756 assert_eq!(out.len(), 1);
1757 assert_eq!(out[0].payload, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1758 }
1759
1760 #[test]
1761 fn assembler_header_inheritance_type0_type1_type2_type3() {
1762 let mut assembler = ChunkAssembler::new();
1767 assembler.set_chunk_size(5);
1768 let csid = 5;
1769
1770 let mut input = Vec::new();
1771 write_serialized(
1772 &mut input,
1773 &BasicHeader {
1774 fmt: Fmt::Type0,
1775 chunk_stream_id: csid,
1776 },
1777 );
1778 write_serialized(
1779 &mut input,
1780 &MessageHeader::Type0 {
1781 timestamp: 1000,
1782 message_length: 5,
1783 message_type_id: 8,
1784 message_stream_id: 7,
1785 },
1786 );
1787 input.extend_from_slice(&[0; 5]);
1788
1789 write_serialized(
1790 &mut input,
1791 &BasicHeader {
1792 fmt: Fmt::Type1,
1793 chunk_stream_id: csid,
1794 },
1795 );
1796 write_serialized(
1797 &mut input,
1798 &MessageHeader::Type1 {
1799 timestamp_delta: 20,
1800 message_length: 5,
1801 message_type_id: 8,
1802 },
1803 );
1804 input.extend_from_slice(&[1; 5]);
1805
1806 write_serialized(
1807 &mut input,
1808 &BasicHeader {
1809 fmt: Fmt::Type2,
1810 chunk_stream_id: csid,
1811 },
1812 );
1813 write_serialized(
1814 &mut input,
1815 &MessageHeader::Type2 {
1816 timestamp_delta: 30,
1817 },
1818 );
1819 input.extend_from_slice(&[2; 5]);
1820
1821 write_serialized(
1822 &mut input,
1823 &BasicHeader {
1824 fmt: Fmt::Type3,
1825 chunk_stream_id: csid,
1826 },
1827 );
1828 input.extend_from_slice(&[3; 5]);
1829
1830 let out = assembler.push(&input).unwrap();
1831 assert_eq!(out.len(), 4);
1832
1833 assert_eq!(out[0].timestamp, 1000);
1834 assert_eq!(out[0].message_stream_id, 7);
1835 assert_eq!(out[0].message_type_id, 8);
1836 assert_eq!(out[0].payload, vec![0; 5]);
1837
1838 assert_eq!(out[1].timestamp, 1020, "fmt1: 1000 + delta 20");
1839 assert_eq!(out[1].message_stream_id, 7, "fmt1 inherits stream id");
1840 assert_eq!(out[1].message_type_id, 8);
1841 assert_eq!(out[1].payload, vec![1; 5]);
1842
1843 assert_eq!(out[2].timestamp, 1050, "fmt2: 1020 + delta 30");
1844 assert_eq!(out[2].message_stream_id, 7, "fmt2 inherits stream id");
1845 assert_eq!(out[2].message_type_id, 8, "fmt2 inherits type id");
1846 assert_eq!(out[2].payload, vec![2; 5], "fmt2 inherits message length");
1847
1848 assert_eq!(
1849 out[3].timestamp, 1080,
1850 "fmt3 (new message) inherits fmt2's delta 30: 1050 + 30"
1851 );
1852 assert_eq!(out[3].message_stream_id, 7, "fmt3 inherits stream id");
1853 assert_eq!(out[3].message_type_id, 8, "fmt3 inherits type id");
1854 assert_eq!(out[3].payload, vec![3; 5], "fmt3 inherits message length");
1855 }
1856
1857 #[test]
1858 fn assembler_mid_stream_set_chunk_size_changes_split_boundary() {
1859 let mut assembler = ChunkAssembler::new();
1865 let csid = 7;
1866
1867 let first = msg(csid, 100, 8, 1, vec![0xAA; 10]);
1868 let mut writer = ChunkWriter::new();
1869 let first_bytes = writer.write(&first);
1870 let out = assembler.push(&first_bytes).unwrap();
1871 assert_eq!(out, vec![first]);
1872
1873 assembler.set_chunk_size(4);
1874 let bh0 = BasicHeader {
1875 fmt: Fmt::Type0,
1876 chunk_stream_id: csid,
1877 };
1878 let mh0 = MessageHeader::Type0 {
1879 timestamp: 200,
1880 message_length: 10,
1881 message_type_id: 8,
1882 message_stream_id: 1,
1883 };
1884 let mut chunk1 = Vec::new();
1885 write_serialized(&mut chunk1, &bh0);
1886 write_serialized(&mut chunk1, &mh0);
1887 chunk1.extend_from_slice(&[1, 2, 3, 4]);
1888 let out = assembler.push(&chunk1).unwrap();
1889 assert!(
1890 out.is_empty(),
1891 "only 4 of 10 payload bytes arrived, message must not complete yet"
1892 );
1893
1894 let bh3 = BasicHeader {
1895 fmt: Fmt::Type3,
1896 chunk_stream_id: csid,
1897 };
1898 let mut chunk2 = Vec::new();
1899 write_serialized(&mut chunk2, &bh3);
1900 chunk2.extend_from_slice(&[5, 6, 7, 8]);
1901 let out = assembler.push(&chunk2).unwrap();
1902 assert!(out.is_empty(), "8 of 10 payload bytes, still incomplete");
1903
1904 let mut chunk3 = Vec::new();
1905 write_serialized(&mut chunk3, &bh3);
1906 chunk3.extend_from_slice(&[9, 10]);
1907 let out = assembler.push(&chunk3).unwrap();
1908 assert_eq!(out.len(), 1);
1909 assert_eq!(out[0].payload, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1910 }
1911
1912 #[test]
1913 fn writer_assembler_round_trip_extended_timestamp_split_across_chunks() {
1914 let original = msg(8, EXTENDED_TIMESTAMP_MARKER + 12345, 9, 2, vec![0x7E; 300]);
1920 let mut writer = ChunkWriter::new();
1921 let bytes = writer.write(&original);
1922
1923 let bh_len = 1; let first_header_len = bh_len + TYPE0_LEN + EXTENDED_TIMESTAMP_LEN;
1929 assert_eq!(&bytes[bh_len..bh_len + 3], [0xFF, 0xFF, 0xFF]);
1930 let ext_offset = bh_len + TYPE0_LEN;
1931 assert_eq!(
1932 &bytes[ext_offset..ext_offset + 4],
1933 &original.timestamp.to_be_bytes()
1934 );
1935 let first_payload_take = 128usize;
1936 let second_chunk_start = first_header_len + first_payload_take;
1937 assert_eq!(bytes[second_chunk_start] >> 6, Fmt::Type3.to_bits());
1939 let second_ext_offset = second_chunk_start + 1;
1940 assert_eq!(
1941 &bytes[second_ext_offset..second_ext_offset + 4],
1942 &original.timestamp.to_be_bytes(),
1943 "fmt3 continuation must carry the same extended timestamp"
1944 );
1945
1946 let mut assembler = ChunkAssembler::new();
1947 let out = assembler.push(&bytes).unwrap();
1948 assert_eq!(out.len(), 1);
1949 assert_eq!(out[0], original);
1950 assert_eq!(out[0].timestamp, EXTENDED_TIMESTAMP_MARKER + 12345);
1951 }
1952
1953 #[test]
1954 fn assembler_partial_feed_split_mid_header_no_drop_or_duplicate() {
1955 let original = msg(9, 42, 8, 3, vec![0x11; 200]);
1956 let mut writer = ChunkWriter::new();
1957 let bytes = writer.write(&original);
1958
1959 let split_at = 4;
1962 assert!(split_at < 1 + TYPE0_LEN);
1963
1964 let mut assembler = ChunkAssembler::new();
1965 let out1 = assembler.push(&bytes[..split_at]).unwrap();
1966 assert!(out1.is_empty(), "partial header must not error or complete");
1967 let out2 = assembler.push(&bytes[split_at..]).unwrap();
1968 assert_eq!(out2.len(), 1, "message must complete exactly once");
1969 assert_eq!(out2[0], original);
1970 }
1971
1972 #[test]
1973 fn assembler_partial_feed_split_mid_payload_no_drop_or_duplicate() {
1974 let original = msg(10, 42, 8, 3, vec![0x22; 300]);
1975 let mut writer = ChunkWriter::new();
1976 let bytes = writer.write(&original);
1977
1978 let split_at = 1 + TYPE0_LEN + 60;
1980 let mut assembler = ChunkAssembler::new();
1981 let out1 = assembler.push(&bytes[..split_at]).unwrap();
1982 assert!(out1.is_empty());
1983 let out2 = assembler.push(&bytes[split_at..]).unwrap();
1984 assert_eq!(out2.len(), 1);
1985 assert_eq!(out2[0], original);
1986 }
1987
1988 #[test]
1989 fn assembler_partial_feed_byte_at_a_time_never_drops_or_duplicates() {
1990 let original = msg(11, 7, 9, 4, vec![0x33; 260]);
1991 let mut writer = ChunkWriter::new();
1992 let bytes = writer.write(&original);
1993
1994 let mut assembler = ChunkAssembler::new();
1995 let mut collected = Vec::new();
1996 for b in &bytes {
1997 collected.extend(assembler.push(std::slice::from_ref(b)).unwrap());
1998 }
1999 assert_eq!(collected.len(), 1);
2000 assert_eq!(collected[0], original);
2001 }
2002
2003 #[test]
2004 fn assembler_type1_on_unseen_csid_is_malformed() {
2005 let mut assembler = ChunkAssembler::new();
2006 let mut input = Vec::new();
2007 write_serialized(
2008 &mut input,
2009 &BasicHeader {
2010 fmt: Fmt::Type1,
2011 chunk_stream_id: 20,
2012 },
2013 );
2014 write_serialized(
2015 &mut input,
2016 &MessageHeader::Type1 {
2017 timestamp_delta: 5,
2018 message_length: 3,
2019 message_type_id: 1,
2020 },
2021 );
2022 input.extend_from_slice(&[0, 0, 0]);
2023 assert!(matches!(
2024 assembler.push(&input),
2025 Err(RtmpError::Malformed { .. })
2026 ));
2027 }
2028
2029 #[test]
2030 fn assembler_type3_on_unseen_csid_is_malformed() {
2031 let mut assembler = ChunkAssembler::new();
2032 let mut input = Vec::new();
2033 write_serialized(
2034 &mut input,
2035 &BasicHeader {
2036 fmt: Fmt::Type3,
2037 chunk_stream_id: 21,
2038 },
2039 );
2040 assert!(matches!(
2041 assembler.push(&input),
2042 Err(RtmpError::Malformed { .. })
2043 ));
2044 }
2045
2046 #[test]
2047 fn assembler_truncated_input_never_panics_across_many_split_points() {
2048 let original = msg(12, 99, 8, 5, vec![0x44; 400]);
2049 let mut writer = ChunkWriter::new();
2050 let bytes = writer.write(&original);
2051
2052 for split in 0..=bytes.len() {
2056 let mut assembler = ChunkAssembler::new();
2057 let first = assembler.push(&bytes[..split]);
2058 let Ok(first_msgs) = first else {
2059 continue;
2060 };
2061 let second = assembler.push(&bytes[split..]).unwrap();
2062 let mut all = first_msgs;
2063 all.extend(second);
2064 assert_eq!(all, vec![original.clone()]);
2065 }
2066 }
2067
2068 #[test]
2069 fn writer_default_chunk_size_matches_assembler_default() {
2070 assert_eq!(ChunkWriter::new().chunk_size, DEFAULT_CHUNK_SIZE);
2071 assert_eq!(ChunkAssembler::new().chunk_size, DEFAULT_CHUNK_SIZE);
2072 }
2073
2074 #[test]
2075 fn assembler_type3_immediately_after_type0_uses_type0_timestamp_as_implied_delta() {
2076 let mut assembler = ChunkAssembler::new();
2084 let csid = 40;
2085
2086 let mut input = Vec::new();
2087 write_serialized(
2088 &mut input,
2089 &BasicHeader {
2090 fmt: Fmt::Type0,
2091 chunk_stream_id: csid,
2092 },
2093 );
2094 write_serialized(
2095 &mut input,
2096 &MessageHeader::Type0 {
2097 timestamp: 1000,
2098 message_length: 5,
2099 message_type_id: 8,
2100 message_stream_id: 2,
2101 },
2102 );
2103 input.extend_from_slice(&[1, 2, 3, 4, 5]);
2104
2105 write_serialized(
2108 &mut input,
2109 &BasicHeader {
2110 fmt: Fmt::Type3,
2111 chunk_stream_id: csid,
2112 },
2113 );
2114 input.extend_from_slice(&[9, 9, 9, 9, 9]);
2115
2116 let out = assembler.push(&input).unwrap();
2117 assert_eq!(out.len(), 2);
2118 assert_eq!(out[0].timestamp, 1000);
2119 assert_eq!(
2120 out[1].timestamp, 2000,
2121 "fmt3 immediately after fmt0 implies delta == the fmt0's own timestamp (1000), not 0: 1000 + 1000"
2122 );
2123 }
2124
2125 #[test]
2126 fn assembler_type2_on_unseen_csid_is_malformed() {
2127 let mut assembler = ChunkAssembler::new();
2128 let mut input = Vec::new();
2129 write_serialized(
2130 &mut input,
2131 &BasicHeader {
2132 fmt: Fmt::Type2,
2133 chunk_stream_id: 22,
2134 },
2135 );
2136 write_serialized(&mut input, &MessageHeader::Type2 { timestamp_delta: 5 });
2137 assert!(matches!(
2138 assembler.push(&input),
2139 Err(RtmpError::Malformed { .. })
2140 ));
2141 }
2142
2143 #[test]
2144 fn assembler_set_chunk_size_zero_is_floored_to_one() {
2145 let mut assembler = ChunkAssembler::new();
2146 assembler.set_chunk_size(0);
2147 assert_eq!(assembler.chunk_size, 1, "floored at 1, not stuck at 0");
2148
2149 let csid = 41;
2153 let mut input = Vec::new();
2154 write_serialized(
2155 &mut input,
2156 &BasicHeader {
2157 fmt: Fmt::Type0,
2158 chunk_stream_id: csid,
2159 },
2160 );
2161 write_serialized(
2162 &mut input,
2163 &MessageHeader::Type0 {
2164 timestamp: 1,
2165 message_length: 3,
2166 message_type_id: 8,
2167 message_stream_id: 0,
2168 },
2169 );
2170 input.push(0xAA);
2171 write_serialized(
2172 &mut input,
2173 &BasicHeader {
2174 fmt: Fmt::Type3,
2175 chunk_stream_id: csid,
2176 },
2177 );
2178 input.push(0xBB);
2179 write_serialized(
2180 &mut input,
2181 &BasicHeader {
2182 fmt: Fmt::Type3,
2183 chunk_stream_id: csid,
2184 },
2185 );
2186 input.push(0xCC);
2187
2188 let out = assembler.push(&input).unwrap();
2189 assert_eq!(out.len(), 1);
2190 assert_eq!(out[0].payload, vec![0xAA, 0xBB, 0xCC]);
2191 }
2192
2193 fn single_chunk(csid: u32, payload: &[u8]) -> Vec<u8> {
2200 let mut input = Vec::new();
2201 write_serialized(
2202 &mut input,
2203 &BasicHeader {
2204 fmt: Fmt::Type0,
2205 chunk_stream_id: csid,
2206 },
2207 );
2208 write_serialized(
2209 &mut input,
2210 &MessageHeader::Type0 {
2211 timestamp: 0,
2212 message_length: payload.len() as u32,
2213 message_type_id: 9,
2214 message_stream_id: 1,
2215 },
2216 );
2217 input.extend_from_slice(payload);
2218 input
2219 }
2220
2221 #[test]
2222 fn oversized_message_length_header_is_rejected_without_allocating() {
2223 let mut assembler = ChunkAssembler::new();
2233 let mut input = Vec::new();
2234 write_serialized(
2235 &mut input,
2236 &BasicHeader {
2237 fmt: Fmt::Type0,
2238 chunk_stream_id: 4,
2239 },
2240 );
2241 write_serialized(
2242 &mut input,
2243 &MessageHeader::Type0 {
2244 timestamp: 0,
2245 message_length: 0x00FF_FFFF, message_type_id: 9,
2247 message_stream_id: 1,
2248 },
2249 );
2250 input.extend(std::iter::repeat_n(0u8, DEFAULT_CHUNK_SIZE as usize));
2251
2252 let err = assembler.push(&input).expect_err(
2253 "a message_length beyond MAX_MESSAGE_LEN must be rejected before any \
2254 message_length-sized buffer is allocated",
2255 );
2256 assert!(matches!(err, RtmpError::Malformed { .. }));
2257 }
2258
2259 #[test]
2260 fn message_length_at_the_cap_is_accepted() {
2261 let mut assembler = ChunkAssembler::new();
2264 let mut input = Vec::new();
2265 write_serialized(
2266 &mut input,
2267 &BasicHeader {
2268 fmt: Fmt::Type0,
2269 chunk_stream_id: 4,
2270 },
2271 );
2272 write_serialized(
2273 &mut input,
2274 &MessageHeader::Type0 {
2275 timestamp: 0,
2276 message_length: MAX_MESSAGE_LEN,
2277 message_type_id: 9,
2278 message_stream_id: 1,
2279 },
2280 );
2281 input.extend(std::iter::repeat_n(0u8, DEFAULT_CHUNK_SIZE as usize));
2282
2283 assert!(assembler.push(&input).is_ok());
2286 }
2287
2288 #[test]
2289 fn csid_flood_beyond_max_csids_is_rejected() {
2290 let mut assembler = ChunkAssembler::new();
2298 for i in 0..MAX_CSIDS {
2299 let csid = BASIC_HEADER_1BYTE_MIN_CSID + i as u32;
2300 let out = assembler
2301 .push(&single_chunk(csid, &[0xAB]))
2302 .unwrap_or_else(|e| panic!("csid {csid} (#{i}, within the bound) rejected: {e}"));
2303 assert_eq!(out.len(), 1);
2304 }
2305
2306 let flood_csid = BASIC_HEADER_1BYTE_MIN_CSID + MAX_CSIDS as u32;
2307 let err = assembler
2308 .push(&single_chunk(flood_csid, &[0xCD]))
2309 .expect_err("a new csid beyond MAX_CSIDS must be rejected, not silently accepted");
2310 assert!(matches!(err, RtmpError::Malformed { .. }));
2311 }
2312
2313 #[test]
2314 fn csid_flood_cap_does_not_count_repeats_of_the_same_csid() {
2315 let mut assembler = ChunkAssembler::new();
2319 for i in 0..(MAX_CSIDS * 4) {
2320 let out = assembler
2321 .push(&single_chunk(BASIC_HEADER_1BYTE_MIN_CSID, &[i as u8]))
2322 .expect("repeated use of a single already-known csid must never be rejected");
2323 assert_eq!(out.len(), 1);
2324 }
2325 }
2326
2327 #[test]
2328 fn set_chunk_size_is_capped_at_max_chunk_size() {
2329 let mut assembler = ChunkAssembler::new();
2330 assembler.set_chunk_size(u32::MAX);
2331 assert_eq!(assembler.chunk_size, MAX_CHUNK_SIZE);
2332
2333 let mut writer = ChunkWriter::new();
2334 writer.set_chunk_size(u32::MAX);
2335 assert_eq!(writer.chunk_size, MAX_CHUNK_SIZE);
2336 }
2337}