use crate::buffer::Buffer;
use crate::bytes::ntoh32;
use crate::types::ErrorCode;
use crate::types::Result;
const RTMP_VERSION: u8 = 0x03;
const HANDSHAKE_SIZE: usize = 1536;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum HandshakeState {
ServerWaitC0 = 0,
ServerWaitC1,
ServerWaitC2,
ClientWaitS0,
ClientWaitS1,
ClientWaitS2,
Done,
}
#[derive(Debug)]
pub struct Handshake {
pub state: HandshakeState,
pub version: u8,
pub peer_time: u32,
pub out: Buffer,
}
impl Default for Handshake {
fn default() -> Self {
Self {
state: HandshakeState::ServerWaitC0,
version: 0,
peer_time: 0,
out: Buffer::new(),
}
}
}
impl Handshake {
pub fn is_complete(&self) -> bool {
self.state == HandshakeState::Done
}
pub fn cleanup(&mut self) {
self.out.reset();
}
}
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E3779B97F4A7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
z ^ (z >> 31)
}
fn fill_random(buf: &mut [u8]) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let mut state = now ^ (buf.as_ptr() as u64);
let mut i = 0;
while i < buf.len() {
let r = splitmix64(&mut state);
for b in 0..8 {
if i >= buf.len() {
break;
}
buf[i] = (r >> (b * 8)) as u8;
i += 1;
}
}
}
fn get_time() -> u32 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32
}
pub fn server_init(hs: &mut Handshake) {
hs.state = HandshakeState::ServerWaitC0;
hs.version = 0;
hs.peer_time = 0;
}
pub fn server_read_c0(hs: &mut Handshake, buf: &mut Buffer) -> Result<()> {
let mut ver = [0u8; 1];
buf.read(&mut ver).map_err(|_| ErrorCode::Io)?;
if ver[0] != RTMP_VERSION {
return Err(ErrorCode::Handshake);
}
hs.version = ver[0];
hs.state = HandshakeState::ServerWaitC1;
Ok(())
}
pub fn server_read_c1(hs: &mut Handshake, buf: &mut Buffer) -> Result<()> {
if buf.available() < HANDSHAKE_SIZE {
return Err(ErrorCode::Io);
}
let mut c1 = vec![0u8; HANDSHAKE_SIZE];
buf.read(&mut c1).map_err(|_| ErrorCode::Io)?;
hs.peer_time = ntoh32(&c1[..4]);
let mut s1 = vec![0u8; HANDSHAKE_SIZE];
let server_time = get_time();
s1[..4].copy_from_slice(&server_time.to_be_bytes());
fill_random(&mut s1[8..]);
let mut s2 = c1.clone();
s2[4..8].copy_from_slice(&server_time.to_be_bytes());
hs.out.reset();
hs.out.write(&s1).map_err(|_| ErrorCode::Internal)?;
hs.out.write(&s2).map_err(|_| ErrorCode::Internal)?;
hs.state = HandshakeState::ServerWaitC2;
Ok(())
}
pub fn server_read_c2(hs: &mut Handshake, buf: &mut Buffer) -> Result<()> {
if buf.available() < HANDSHAKE_SIZE {
return Err(ErrorCode::Io);
}
let mut c2 = vec![0u8; HANDSHAKE_SIZE];
buf.read(&mut c2).map_err(|_| ErrorCode::Io)?;
hs.state = HandshakeState::Done;
Ok(())
}
pub fn client_init(hs: &mut Handshake) {
hs.state = HandshakeState::ClientWaitS0;
hs.version = 0;
hs.peer_time = 0;
}
pub fn client_generate_c0c1(hs: &mut Handshake) -> Result<()> {
let client_time = get_time();
hs.peer_time = client_time;
let mut c0c1 = vec![0u8; 1 + HANDSHAKE_SIZE];
c0c1[0] = RTMP_VERSION;
c0c1[1..5].copy_from_slice(&client_time.to_be_bytes());
fill_random(&mut c0c1[9..]);
hs.out.reset();
hs.out.write(&c0c1).map_err(|_| ErrorCode::Internal)?;
hs.state = HandshakeState::ClientWaitS1;
Ok(())
}
pub fn client_read_s0(hs: &mut Handshake, buf: &mut Buffer) -> Result<()> {
let mut ver = [0u8; 1];
buf.read(&mut ver).map_err(|_| ErrorCode::Io)?;
if ver[0] != RTMP_VERSION {
return Err(ErrorCode::Handshake);
}
hs.version = ver[0];
hs.state = HandshakeState::ClientWaitS1;
Ok(())
}
pub fn client_read_s1(hs: &mut Handshake, buf: &mut Buffer) -> Result<()> {
if buf.available() < HANDSHAKE_SIZE {
return Err(ErrorCode::Io);
}
let mut s1 = vec![0u8; HANDSHAKE_SIZE];
buf.read(&mut s1).map_err(|_| ErrorCode::Io)?;
hs.peer_time = ntoh32(&s1[..4]);
let mut c2 = s1.clone();
c2[4..8].copy_from_slice(&get_time().to_be_bytes());
hs.out.reset();
hs.out.write(&c2).map_err(|_| ErrorCode::Internal)?;
hs.state = HandshakeState::ClientWaitS2;
Ok(())
}
pub fn client_read_s2(hs: &mut Handshake, buf: &mut Buffer) -> Result<()> {
if buf.available() < HANDSHAKE_SIZE {
return Err(ErrorCode::Io);
}
let mut s2 = vec![0u8; HANDSHAKE_SIZE];
buf.read(&mut s2).map_err(|_| ErrorCode::Io)?;
hs.state = HandshakeState::Done;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn full_client_server_handshake_completes() {
let mut client = Handshake::default();
let mut server = Handshake::default();
client_init(&mut client);
server_init(&mut server);
client_generate_c0c1(&mut client).unwrap();
let c0c1 = client.out.peek().to_vec();
client.out.reset();
let mut server_in = Buffer::new();
server_in.write(&c0c1).unwrap();
server_read_c0(&mut server, &mut server_in).unwrap();
server_read_c1(&mut server, &mut server_in).unwrap();
let s1s2 = server.out.peek().to_vec();
assert_eq!(server.state, HandshakeState::ServerWaitC2);
assert_eq!(s1s2.len(), HANDSHAKE_SIZE * 2);
let mut s0s1s2 = vec![RTMP_VERSION];
s0s1s2.extend_from_slice(&s1s2);
let mut client_in = Buffer::new();
client_in.write(&s0s1s2).unwrap();
client_read_s0(&mut client, &mut client_in).unwrap();
client_read_s1(&mut client, &mut client_in).unwrap();
let c2 = client.out.peek().to_vec();
assert_eq!(client.state, HandshakeState::ClientWaitS2);
let mut server_in2 = Buffer::new();
server_in2.write(&c2).unwrap();
server_read_c2(&mut server, &mut server_in2).unwrap();
assert!(server.is_complete());
let mut client_in2 = Buffer::new();
client_in2.write(&vec![0u8; HANDSHAKE_SIZE]).unwrap();
client_read_s2(&mut client, &mut client_in2).unwrap();
assert!(client.is_complete());
}
#[test]
fn server_read_c0_rejects_wrong_version() {
let mut hs = Handshake::default();
let mut buf = Buffer::new();
buf.write(&[0x99]).unwrap();
assert!(server_read_c0(&mut hs, &mut buf).is_err());
}
#[test]
fn s1_time_field_matches_wall_clock_in_big_endian() {
let mut hs = Handshake::default();
server_init(&mut hs);
let c1 = vec![0u8; HANDSHAKE_SIZE];
let mut buf = Buffer::new();
buf.write(&c1).unwrap();
let before = get_time();
server_read_c1(&mut hs, &mut buf).unwrap();
let after = get_time();
let s1 = &hs.out.peek()[..HANDSHAKE_SIZE];
let time_from_s1 = u32::from_be_bytes([s1[0], s1[1], s1[2], s1[3]]);
assert!(time_from_s1 >= before && time_from_s1 <= after);
}
#[test]
fn s2_echoes_c1_time1_and_carries_server_time_in_time2() {
let mut hs = Handshake::default();
server_init(&mut hs);
let mut c1 = vec![0u8; HANDSHAKE_SIZE];
c1[..4].copy_from_slice(&0x1234_5678u32.to_be_bytes());
c1[8..].copy_from_slice(&[0xAB; HANDSHAKE_SIZE - 8]);
let mut buf = Buffer::new();
buf.write(&c1).unwrap();
let before = get_time();
server_read_c1(&mut hs, &mut buf).unwrap();
let after = get_time();
let out = hs.out.peek();
let s2 = &out[HANDSHAKE_SIZE..HANDSHAKE_SIZE * 2];
let time1 = u32::from_be_bytes([s2[0], s2[1], s2[2], s2[3]]);
let time2 = u32::from_be_bytes([s2[4], s2[5], s2[6], s2[7]]);
assert_eq!(time1, 0x1234_5678, "S2 time1 must echo C1's time1");
assert!(
time2 >= before && time2 <= after,
"S2 time2 must be the server's own time"
);
assert_eq!(&s2[8..], &c1[8..], "S2 random bytes must echo C1's random bytes");
}
#[test]
fn c2_echoes_s1_time1_and_carries_client_time_in_time2() {
let mut client = Handshake::default();
client_init(&mut client);
let mut s1 = vec![0u8; HANDSHAKE_SIZE];
s1[..4].copy_from_slice(&0x0BAD_F00Du32.to_be_bytes());
s1[8..].copy_from_slice(&[0xCD; HANDSHAKE_SIZE - 8]);
let mut buf = Buffer::new();
buf.write(&s1).unwrap();
let before = get_time();
client_read_s1(&mut client, &mut buf).unwrap();
let after = get_time();
let c2 = client.out.peek();
let time1 = u32::from_be_bytes([c2[0], c2[1], c2[2], c2[3]]);
let time2 = u32::from_be_bytes([c2[4], c2[5], c2[6], c2[7]]);
assert_eq!(time1, 0x0BAD_F00D, "C2 time1 must echo S1's time1");
assert!(
time2 >= before && time2 <= after,
"C2 time2 must be the client's own time"
);
assert_eq!(&c2[8..], &s1[8..], "C2 random bytes must echo S1's random bytes");
}
}