arcly_stream/protocol/srt/
egress.rs1use std::net::SocketAddr;
18use std::time::{Duration, Instant};
19
20use tokio::net::UdpSocket;
21use tokio::sync::mpsc;
22use tokio::time::timeout;
23use tokio_util::sync::CancellationToken;
24use tracing::{debug, info};
25
26use super::arq::{self, SendBuffer};
27use super::handshake::{caller_conclusion, caller_induction, SrtHandshake};
28use super::packet::{build_data_packet, ControlType};
29use crate::{Result, StreamError};
30
31const TS_BYTES_PER_DATAGRAM: usize = 7 * 188;
33const SETUP_TIMEOUT: Duration = Duration::from_secs(5);
35const SEND_WINDOW: usize = 2048;
37
38pub struct SrtCaller {
40 addr: SocketAddr,
41 socket_id: u32,
42 #[cfg(feature = "srt-encrypt")]
44 passphrase: Option<String>,
45 #[cfg(feature = "srt-encrypt")]
47 key_len: usize,
48}
49
50impl SrtCaller {
51 pub fn new(addr: SocketAddr) -> Self {
53 let socket_id = (0x4152_434C ^ (addr.port() as u32).rotate_left(16)) | 1;
55 Self {
56 addr,
57 socket_id,
58 #[cfg(feature = "srt-encrypt")]
59 passphrase: None,
60 #[cfg(feature = "srt-encrypt")]
61 key_len: 16,
62 }
63 }
64
65 #[cfg(feature = "srt-encrypt")]
68 pub fn with_passphrase(mut self, passphrase: impl Into<String>) -> Self {
69 self.passphrase = Some(passphrase.into());
70 self
71 }
72
73 #[cfg(feature = "srt-encrypt")]
75 pub fn with_key_len(mut self, key_len: usize) -> Self {
76 self.key_len = key_len;
77 self
78 }
79
80 pub async fn run(
84 self,
85 mut ts: mpsc::Receiver<bytes::Bytes>,
86 shutdown: CancellationToken,
87 ) -> Result<()> {
88 let sock = UdpSocket::bind(("0.0.0.0", 0)).await?;
89 sock.connect(self.addr).await?;
90 let mut buf = [0u8; 1500];
91
92 sock.send(&caller_induction(self.socket_id, 0)).await?;
94 let n = timeout(SETUP_TIMEOUT, sock.recv(&mut buf))
95 .await
96 .map_err(|_| StreamError::protocol("srt induction timed out"))??;
97 let induction = SrtHandshake::parse(&buf[..n])
98 .ok_or_else(|| StreamError::protocol("malformed srt induction response"))?;
99
100 #[cfg(feature = "srt-encrypt")]
104 let key_material = self
105 .passphrase
106 .as_ref()
107 .map(|_| super::keymaterial::KeyMaterial::generate(self.key_len));
108
109 #[cfg(feature = "srt-encrypt")]
111 let conclusion = match (&self.passphrase, &key_material) {
112 (Some(pass), Some(km)) => super::handshake::caller_conclusion_encrypted(
113 self.socket_id,
114 0,
115 induction.cookie,
116 self.key_len,
117 &km.to_message(pass.as_bytes()),
118 ),
119 _ => caller_conclusion(self.socket_id, 0, induction.cookie),
120 };
121 #[cfg(not(feature = "srt-encrypt"))]
122 let conclusion = caller_conclusion(self.socket_id, 0, induction.cookie);
123
124 sock.send(&conclusion).await?;
125 let n = timeout(SETUP_TIMEOUT, sock.recv(&mut buf))
126 .await
127 .map_err(|_| StreamError::protocol("srt conclusion timed out"))??;
128 let agreement = SrtHandshake::parse(&buf[..n])
129 .ok_or_else(|| StreamError::protocol("malformed srt conclusion response"))?;
130 let dest = agreement.socket_id;
132 info!(addr = %self.addr, dest, "srt caller connected");
133
134 #[cfg(feature = "srt-encrypt")]
137 let key_flag: u8 = if key_material.is_some() { 1 } else { 0 };
138 #[cfg(not(feature = "srt-encrypt"))]
139 let key_flag: u8 = 0;
140
141 let start = Instant::now();
142 let mut seq = 0u32;
143 let mut msg = 0u32;
144 let mut send_buf = SendBuffer::new(SEND_WINDOW);
145 loop {
146 tokio::select! {
147 _ = shutdown.cancelled() => break,
148
149 r = sock.recv(&mut buf) => {
152 let Ok(n) = r else { continue };
153 let dg = &buf[..n];
154 match arq::control_type(dg) {
155 Some(ControlType::Nak) => {
156 for pkt in send_buf.retransmit(&arq::parse_nak(dg)) {
157 let _ = sock.send(&pkt).await;
158 }
159 }
160 Some(ControlType::Ack) => {
161 if let Some(ack) = arq::parse_ack(dg) {
162 send_buf.acknowledge(ack);
163 }
164 if let Some(ack_no) = arq::ack_seqno(dg) {
166 let ts_us = start.elapsed().as_micros() as u32;
167 let _ = sock.send(&arq::build_ackack(ack_no, ts_us, dest)).await;
168 }
169 }
170 _ => {}
171 }
172 }
173
174 chunk = ts.recv() => match chunk {
175 Some(bytes) => {
176 for piece in bytes.chunks(TS_BYTES_PER_DATAGRAM) {
177 let ts_us = start.elapsed().as_micros() as u32;
178 let payload = piece.to_vec();
179 #[cfg(feature = "srt-encrypt")]
180 let mut payload = payload;
181 #[cfg(feature = "srt-encrypt")]
182 if let Some(km) = &key_material {
183 km.transform(seq, &mut payload);
184 }
185 let pkt =
186 build_data_packet(seq, msg, ts_us, dest, key_flag, false, &payload);
187 if sock.send(&pkt).await.is_err() {
188 return Ok(()); }
190 send_buf.record(seq, pkt);
191 seq = seq.wrapping_add(1) & 0x7FFF_FFFF;
192 msg = msg.wrapping_add(1) & 0x03FF_FFFF;
193 }
194 }
195 None => break, }
197 }
198 }
199 debug!(addr = %self.addr, "srt caller finished");
200 Ok(())
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::protocol::srt::{handshake::respond, SrtPacket};
208
209 #[tokio::test]
210 async fn caller_handshakes_and_sends_data_over_loopback() {
211 let listener = UdpSocket::bind("127.0.0.1:0").await.unwrap();
215 let addr = listener.local_addr().unwrap();
216
217 let (tx, rx) = mpsc::channel(4);
218 let shutdown = CancellationToken::new();
219 let sh = shutdown.clone();
220 let caller = SrtCaller::new(addr);
221 let caller_task = tokio::spawn(async move { caller.run(rx, sh).await });
222
223 let mut buf = [0u8; 1500];
224 let (n, peer) = listener.recv_from(&mut buf).await.unwrap();
226 let reply = respond(&buf[..n]).unwrap();
227 listener.send_to(&reply, peer).await.unwrap();
228 let (n, peer) = listener.recv_from(&mut buf).await.unwrap();
230 let reply = respond(&buf[..n]).unwrap();
231 listener.send_to(&reply, peer).await.unwrap();
232
233 tx.send(bytes::Bytes::from(vec![0x47u8; TS_BYTES_PER_DATAGRAM]))
235 .await
236 .unwrap();
237 let (n, _) = timeout(Duration::from_secs(5), listener.recv_from(&mut buf))
238 .await
239 .expect("data packet arrived")
240 .unwrap();
241 assert!(
242 matches!(SrtPacket::parse(&buf[..n]), Some(SrtPacket::Data { .. })),
243 "caller sent an SRT data packet after the handshake"
244 );
245
246 shutdown.cancel();
247 let _ = caller_task.await;
248 }
249}