arcly_stream/protocol/srt/
handshake.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum HandshakeType {
12 Induction,
14 Conclusion,
16 WaveAHand,
18 Agreement,
20 Other(u32),
22}
23
24impl HandshakeType {
25 fn from_u32(v: u32) -> HandshakeType {
26 match v {
27 1 => HandshakeType::Induction,
28 0xFFFF_FFFF => HandshakeType::Conclusion,
29 0 => HandshakeType::WaveAHand,
30 0xFFFF_FFFE => HandshakeType::Agreement,
31 other => HandshakeType::Other(other),
32 }
33 }
34
35 fn to_u32(self) -> u32 {
36 match self {
37 HandshakeType::Induction => 1,
38 HandshakeType::Conclusion => 0xFFFF_FFFF,
39 HandshakeType::WaveAHand => 0,
40 HandshakeType::Agreement => 0xFFFF_FFFE,
41 HandshakeType::Other(v) => v,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct SrtHandshake {
49 pub version: u32,
51 pub encryption: u16,
54 pub initial_sequence: u32,
56 pub handshake_type: HandshakeType,
58 pub socket_id: u32,
60 pub cookie: u32,
62}
63
64impl SrtHandshake {
65 const PAYLOAD: usize = 16;
68
69 pub fn parse(datagram: &[u8]) -> Option<SrtHandshake> {
72 let b = datagram.get(Self::PAYLOAD..)?;
73 if b.len() < 32 {
74 return None;
75 }
76 let w = |i: usize| u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
77 Some(SrtHandshake {
78 version: w(0),
79 encryption: u16::from_be_bytes([b[4], b[5]]),
80 initial_sequence: w(8),
81 handshake_type: HandshakeType::from_u32(w(20)),
82 socket_id: w(24),
83 cookie: w(28),
84 })
85 }
86
87 pub fn wants_encryption(&self) -> bool {
89 self.encryption != 0
90 }
91}
92
93fn syn_cookie(socket_id: u32) -> u32 {
98 socket_id
99 .rotate_left(13)
100 .wrapping_mul(0x9E37_79B1)
101 .wrapping_add(0x5247_5421)
102}
103
104pub fn respond(datagram: &[u8]) -> Option<Vec<u8>> {
111 let hs = SrtHandshake::parse(datagram)?;
112 if hs.wants_encryption() {
113 return None; }
115 let mut reply = datagram.to_vec();
116 let cookie = match hs.handshake_type {
117 HandshakeType::Induction => syn_cookie(hs.socket_id),
118 HandshakeType::Conclusion => hs.cookie,
119 _ => return None,
120 };
121 let at = SrtHandshake::PAYLOAD + 28;
123 reply
124 .get_mut(at..at + 4)?
125 .copy_from_slice(&cookie.to_be_bytes());
126 Some(reply)
127}
128
129pub fn caller_handshake(
137 req_type: HandshakeType,
138 socket_id: u32,
139 initial_sequence: u32,
140 cookie: u32,
141) -> Vec<u8> {
142 let mut d = vec![0u8; 16]; d[0] = 0x80;
144 let mut body = vec![0u8; 32];
145 body[0..4].copy_from_slice(&5u32.to_be_bytes()); body[8..12].copy_from_slice(&initial_sequence.to_be_bytes());
148 body[12..16].copy_from_slice(&1500u32.to_be_bytes()); body[16..20].copy_from_slice(&8192u32.to_be_bytes()); body[20..24].copy_from_slice(&req_type.to_u32().to_be_bytes());
151 body[24..28].copy_from_slice(&socket_id.to_be_bytes());
152 body[28..32].copy_from_slice(&cookie.to_be_bytes());
153 d.extend_from_slice(&body);
154 d
155}
156
157pub fn caller_induction(socket_id: u32, initial_sequence: u32) -> Vec<u8> {
159 caller_handshake(HandshakeType::Induction, socket_id, initial_sequence, 0)
160}
161
162pub fn caller_conclusion(socket_id: u32, initial_sequence: u32, cookie: u32) -> Vec<u8> {
164 caller_handshake(
165 HandshakeType::Conclusion,
166 socket_id,
167 initial_sequence,
168 cookie,
169 )
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn handshake_datagram(req_type: u32, encryption: u16) -> Vec<u8> {
178 let mut d = vec![0u8; 16]; d[0] = 0x80; let mut body = vec![0u8; 32];
181 body[0..4].copy_from_slice(&5u32.to_be_bytes()); body[4..6].copy_from_slice(&encryption.to_be_bytes());
183 body[20..24].copy_from_slice(&req_type.to_be_bytes());
184 body[24..28].copy_from_slice(&0xABCD_1234u32.to_be_bytes()); d.extend_from_slice(&body);
186 d
187 }
188
189 #[test]
190 fn parses_induction_handshake() {
191 let d = handshake_datagram(1, 0);
192 let hs = SrtHandshake::parse(&d).unwrap();
193 assert_eq!(hs.version, 5);
194 assert_eq!(hs.handshake_type, HandshakeType::Induction);
195 assert_eq!(hs.socket_id, 0xABCD_1234);
196 assert!(!hs.wants_encryption());
197 }
198
199 #[test]
200 fn induction_response_installs_nonzero_cookie() {
201 let d = handshake_datagram(1, 0);
202 let reply = respond(&d).unwrap();
203 let parsed = SrtHandshake::parse(&reply).unwrap();
204 assert_ne!(parsed.cookie, 0, "cookie installed in induction response");
205 }
206
207 #[test]
208 fn encrypted_handshake_is_rejected() {
209 let d = handshake_datagram(1, 0x0002);
210 assert!(respond(&d).is_none());
211 }
212
213 #[test]
214 fn non_handshake_request_type_has_no_response() {
215 let d = handshake_datagram(0, 0); assert!(respond(&d).is_none());
217 }
218
219 #[test]
220 fn caller_handshake_loops_through_listener() {
221 let induction = caller_induction(0x0BAD_F00D, 42);
224 let hs = SrtHandshake::parse(&induction).unwrap();
225 assert_eq!(hs.handshake_type, HandshakeType::Induction);
226 assert_eq!(hs.version, 5);
227 assert_eq!(hs.socket_id, 0x0BAD_F00D);
228
229 let resp = respond(&induction).expect("listener induction reply");
230 let cookie = SrtHandshake::parse(&resp).unwrap().cookie;
231 assert_ne!(cookie, 0, "listener installed a cookie");
232
233 let conclusion = caller_conclusion(0x0BAD_F00D, 42, cookie);
234 let chs = SrtHandshake::parse(&conclusion).unwrap();
235 assert_eq!(chs.handshake_type, HandshakeType::Conclusion);
236 assert_eq!(chs.cookie, cookie, "caller echoes the cookie");
237
238 let agree = respond(&conclusion).expect("listener conclusion reply");
239 assert_eq!(SrtHandshake::parse(&agree).unwrap().cookie, cookie);
240 }
241}