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