pub mod msg_type {
pub const SET_CHUNK_SIZE: u8 = 1;
pub const ABORT: u8 = 2;
pub const ACK: u8 = 3;
pub const USER_CONTROL: u8 = 4;
pub const WINDOW_ACK_SIZE: u8 = 5;
pub const SET_PEER_BANDWIDTH: u8 = 6;
pub const AUDIO: u8 = 8;
pub const VIDEO: u8 = 9;
pub const DATA_AMF0: u8 = 18;
pub const COMMAND_AMF0: u8 = 20;
}
pub struct Message {
pub chunk_stream_id: u32,
pub timestamp: u32,
pub type_id: u8,
pub msg_stream_id: u32,
pub payload: Vec<u8>,
}
pub fn write_message(out: &mut Vec<u8>, msg: &Message, chunk_size: usize) {
let ext_ts = msg.timestamp >= 0xFF_FFFF;
let ts_field = if ext_ts { 0xFF_FFFF } else { msg.timestamp };
write_basic_header(out, 0, msg.chunk_stream_id);
out.extend_from_slice(&ts_field.to_be_bytes()[1..4]); let len = msg.payload.len() as u32;
out.extend_from_slice(&len.to_be_bytes()[1..4]);
out.push(msg.type_id);
out.extend_from_slice(&msg.msg_stream_id.to_le_bytes()); if ext_ts {
out.extend_from_slice(&msg.timestamp.to_be_bytes());
}
let mut written = 0;
let first = msg.payload.len().min(chunk_size);
out.extend_from_slice(&msg.payload[..first]);
written += first;
while written < msg.payload.len() {
write_basic_header(out, 3, msg.chunk_stream_id);
if ext_ts {
out.extend_from_slice(&msg.timestamp.to_be_bytes());
}
let take = (msg.payload.len() - written).min(chunk_size);
out.extend_from_slice(&msg.payload[written..written + take]);
written += take;
}
}
fn write_basic_header(out: &mut Vec<u8>, fmt: u8, csid: u32) {
let fmt_bits = (fmt & 0x03) << 6;
if csid < 64 {
out.push(fmt_bits | (csid as u8));
} else if csid < 320 {
out.push(fmt_bits); out.push((csid - 64) as u8);
} else {
out.push(fmt_bits | 1); let v = (csid - 64) as u16;
out.push((v & 0xff) as u8);
out.push((v >> 8) as u8);
}
}
pub fn set_chunk_size_payload(size: u32) -> Vec<u8> {
(size & 0x7fff_ffff).to_be_bytes().to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_message_single_chunk() {
let msg = Message {
chunk_stream_id: 3,
timestamp: 0,
type_id: msg_type::COMMAND_AMF0,
msg_stream_id: 0,
payload: vec![0xAA, 0xBB, 0xCC],
};
let mut out = Vec::new();
write_message(&mut out, &msg, 128);
assert_eq!(out.len(), 1 + 11 + 3);
assert_eq!(out[0], (0 << 6) | 3); assert_eq!(out[7], msg_type::COMMAND_AMF0); assert_eq!(&out[12..15], &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn large_message_splits_with_type3_continuation() {
let payload = vec![0x11u8; 200];
let msg = Message {
chunk_stream_id: 4,
timestamp: 0,
type_id: msg_type::VIDEO,
msg_stream_id: 1,
payload,
};
let mut out = Vec::new();
write_message(&mut out, &msg, 128);
assert_eq!(out.len(), (1 + 11 + 128) + (1 + 72));
let cont = out[1 + 11 + 128];
assert_eq!(cont, (3 << 6) | 4);
}
#[test]
fn stream_id_is_little_endian() {
let msg = Message {
chunk_stream_id: 3,
timestamp: 0,
type_id: msg_type::VIDEO,
msg_stream_id: 1,
payload: vec![0x00],
};
let mut out = Vec::new();
write_message(&mut out, &msg, 128);
assert_eq!(&out[8..12], &1u32.to_le_bytes());
}
}