#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum HandshakeType {
Induction,
Conclusion,
WaveAHand,
Agreement,
Other(u32),
}
impl HandshakeType {
fn from_u32(v: u32) -> HandshakeType {
match v {
1 => HandshakeType::Induction,
0xFFFF_FFFF => HandshakeType::Conclusion,
0 => HandshakeType::WaveAHand,
0xFFFF_FFFE => HandshakeType::Agreement,
other => HandshakeType::Other(other),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SrtHandshake {
pub version: u32,
pub encryption: u16,
pub initial_sequence: u32,
pub handshake_type: HandshakeType,
pub socket_id: u32,
pub cookie: u32,
}
impl SrtHandshake {
const PAYLOAD: usize = 16;
pub fn parse(datagram: &[u8]) -> Option<SrtHandshake> {
let b = datagram.get(Self::PAYLOAD..)?;
if b.len() < 32 {
return None;
}
let w = |i: usize| u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
Some(SrtHandshake {
version: w(0),
encryption: u16::from_be_bytes([b[4], b[5]]),
initial_sequence: w(8),
handshake_type: HandshakeType::from_u32(w(20)),
socket_id: w(24),
cookie: w(28),
})
}
pub fn wants_encryption(&self) -> bool {
self.encryption != 0
}
}
fn syn_cookie(socket_id: u32) -> u32 {
socket_id
.rotate_left(13)
.wrapping_mul(0x9E37_79B1)
.wrapping_add(0x5247_5421)
}
pub fn respond(datagram: &[u8]) -> Option<Vec<u8>> {
let hs = SrtHandshake::parse(datagram)?;
if hs.wants_encryption() {
return None; }
let mut reply = datagram.to_vec();
let cookie = match hs.handshake_type {
HandshakeType::Induction => syn_cookie(hs.socket_id),
HandshakeType::Conclusion => hs.cookie,
_ => return None,
};
let at = SrtHandshake::PAYLOAD + 28;
reply
.get_mut(at..at + 4)?
.copy_from_slice(&cookie.to_be_bytes());
Some(reply)
}
#[cfg(test)]
mod tests {
use super::*;
fn handshake_datagram(req_type: u32, encryption: u16) -> Vec<u8> {
let mut d = vec![0u8; 16]; d[0] = 0x80; let mut body = vec![0u8; 32];
body[0..4].copy_from_slice(&5u32.to_be_bytes()); body[4..6].copy_from_slice(&encryption.to_be_bytes());
body[20..24].copy_from_slice(&req_type.to_be_bytes());
body[24..28].copy_from_slice(&0xABCD_1234u32.to_be_bytes()); d.extend_from_slice(&body);
d
}
#[test]
fn parses_induction_handshake() {
let d = handshake_datagram(1, 0);
let hs = SrtHandshake::parse(&d).unwrap();
assert_eq!(hs.version, 5);
assert_eq!(hs.handshake_type, HandshakeType::Induction);
assert_eq!(hs.socket_id, 0xABCD_1234);
assert!(!hs.wants_encryption());
}
#[test]
fn induction_response_installs_nonzero_cookie() {
let d = handshake_datagram(1, 0);
let reply = respond(&d).unwrap();
let parsed = SrtHandshake::parse(&reply).unwrap();
assert_ne!(parsed.cookie, 0, "cookie installed in induction response");
}
#[test]
fn encrypted_handshake_is_rejected() {
let d = handshake_datagram(1, 0x0002);
assert!(respond(&d).is_none());
}
#[test]
fn non_handshake_request_type_has_no_response() {
let d = handshake_datagram(0, 0); assert!(respond(&d).is_none());
}
}