1use std::collections::{VecDeque, hash_map::RandomState};
12use std::hash::BuildHasher;
13use std::net::SocketAddr;
14use std::time::{Duration, Instant};
15
16use crate::crypto::{CryptoConfig, Encryptor};
17use crate::listener::SRT_VERSION;
18use crate::packet::{
19 Control, ControlPacket, Extension, Handshake, HandshakeKind, MAX_STREAM_ID, Packet, SRT_MAGIC,
20 SrtHandshake, ext_flags, srt_flags,
21};
22use crate::sender::{Failure, SendConfig, SendError, SendStats, Sender};
23use crate::seq::Seq;
24
25const FLAGS: u32 = srt_flags::TSBPDSND | srt_flags::CRYPT | srt_flags::REXMITFLG;
29
30#[derive(Debug, Clone)]
33pub struct CallerConfig {
34 pub stream_id: String,
36 pub latency: Duration,
40 pub connect_timeout: Duration,
43 pub handshake_retry: Duration,
46 pub max_handshake_packets: u32,
49 pub peer_idle: Duration,
52 pub send: SendConfig,
54 pub crypto: Option<CryptoConfig>,
56}
57
58impl Default for CallerConfig {
59 fn default() -> Self {
60 Self {
61 stream_id: String::new(),
62 latency: Duration::from_millis(120),
63 connect_timeout: Duration::from_secs(5),
64 handshake_retry: Duration::from_millis(250),
65 max_handshake_packets: 32,
66 peer_idle: Duration::from_secs(5),
67 send: SendConfig::default(),
68 crypto: None,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum ConfigError {
76 StreamId,
78 Limits,
80 SocketId,
82 Peer,
84 Crypto,
87}
88impl std::fmt::Display for ConfigError {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.write_str(match self {
91 Self::StreamId => "stream ID must contain at most 512 UTF-8 bytes and no NUL",
92 Self::Limits => "invalid caller time, packet, payload, bandwidth or byte limit",
93 Self::SocketId => "caller socket ID must be nonzero",
94 Self::Crypto => "invalid crypto configuration or unavailable secure randomness",
95 Self::Peer => "peer must have a nonzero port and a specified unicast address",
96 })
97 }
98}
99impl std::error::Error for ConfigError {}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum CallerCloseReason {
104 Cancelled,
106 ConnectTimeout,
108 HandshakeLimit,
110 PeerIdle,
112 Rejected(u32),
115 UnsupportedPeer,
119 Crypto,
122 MediaExpired,
124 RetryLimit,
126 PeerClosed,
128 PeerError(u32),
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum CallerEvent {
135 Connected {
137 peer_socket: u32,
139 latency: Duration,
142 max_payload: usize,
145 },
146 Closed {
148 reason: CallerCloseReason,
150 },
151}
152
153#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
155pub struct CallerStats {
156 pub handshake_packets: u64,
158 pub malformed: u64,
161 pub stray: u64,
164 pub send: SendStats,
167}
168
169enum State {
170 Induction,
171 Conclusion { cookie: u32, socket_id: u32 },
172 Open(Box<Sender>),
173 Closed,
174}
175
176pub struct Caller {
179 config: CallerConfig,
180 peer: SocketAddr,
181 socket_id: u32,
182 initial: Seq,
183 epoch: Instant,
184 latest: i64,
185 state: State,
186 next_handshake: i64,
187 events: VecDeque<CallerEvent>,
188 stats: CallerStats,
189 shutdown: Option<u32>,
190 crypto: Option<Encryptor>,
191}
192
193impl Caller {
194 pub fn new(config: CallerConfig, peer: SocketAddr, now: Instant) -> Result<Self, ConfigError> {
198 let random = RandomState::new();
199 let socket_id = (random.hash_one(("sapflow caller", peer)) as u32 & 0x3fff_ffff).max(1);
200 let initial = Seq::new(random.hash_one(("sapflow sequence", peer)) as u32);
201 Self::with_ids(config, peer, socket_id, initial, now)
202 }
203
204 pub fn with_ids(
207 config: CallerConfig,
208 peer: SocketAddr,
209 socket_id: u32,
210 initial: Seq,
211 now: Instant,
212 ) -> Result<Self, ConfigError> {
213 if config.stream_id.len() > MAX_STREAM_ID || config.stream_id.contains('\0') {
214 return Err(ConfigError::StreamId);
215 }
216 if socket_id == 0 {
217 return Err(ConfigError::SocketId);
218 }
219 if peer.port() == 0 || peer.ip().is_unspecified() || peer.ip().is_multicast() {
220 return Err(ConfigError::Peer);
221 }
222 if !config.send.valid()
223 || config.latency > Duration::from_millis(u64::from(u16::MAX))
224 || config.latency < Duration::from_millis(1)
225 || !(Duration::from_millis(10)..=Duration::from_secs(5))
226 .contains(&config.handshake_retry)
227 || config.connect_timeout < config.handshake_retry
228 || config.connect_timeout > Duration::from_secs(60)
229 || !(1..=1024).contains(&config.max_handshake_packets)
230 || !(Duration::from_millis(10)..=Duration::from_secs(60)).contains(&config.peer_idle)
231 {
232 return Err(ConfigError::Limits);
233 }
234 let crypto = config
235 .crypto
236 .as_ref()
237 .map(Encryptor::new)
238 .transpose()
239 .map_err(|_| ConfigError::Crypto)?;
240 Ok(Self {
241 crypto,
242 config,
243 peer,
244 socket_id,
245 initial,
246 epoch: now,
247 latest: 0,
248 state: State::Induction,
249 next_handshake: 0,
250 events: VecDeque::new(),
251 stats: CallerStats::default(),
252 shutdown: None,
253 })
254 }
255
256 fn micros(&mut self, now: Instant) -> i64 {
257 self.latest = self.latest.max(
258 i64::try_from(now.saturating_duration_since(self.epoch).as_micros())
259 .unwrap_or(i64::MAX),
260 );
261 self.latest
262 }
263
264 pub fn is_connected(&self) -> bool {
266 matches!(self.state, State::Open(_))
267 }
268 pub fn is_closed(&self) -> bool {
270 matches!(self.state, State::Closed)
271 }
272 pub fn stats(&self) -> CallerStats {
274 let mut stats = self.stats;
275 if let State::Open(sender) = &self.state {
276 stats.send = sender.stats();
277 }
278 stats
279 }
280 pub fn poll_event(&mut self) -> Option<CallerEvent> {
282 self.events.pop_front()
283 }
284
285 pub fn enqueue(&mut self, now: Instant, payload: Vec<u8>) -> Result<(), SendError> {
288 self.try_enqueue(now, payload).map_err(|(error, _)| error)
289 }
290
291 pub fn try_enqueue(
294 &mut self,
295 now: Instant,
296 payload: Vec<u8>,
297 ) -> Result<(), (SendError, Vec<u8>)> {
298 let t = self.micros(now);
299 self.tick(t);
300 if let State::Open(sender) = &mut self.state {
301 sender.try_enqueue(t, payload)
302 } else {
303 Err((SendError::NotConnected, payload))
304 }
305 }
306
307 pub fn handle_datagram(&mut self, now: Instant, peer: SocketAddr, bytes: &[u8]) {
311 let t = self.micros(now);
312 self.tick(t);
313 if self.is_closed() {
314 return;
315 }
316 if peer != self.peer {
317 self.stats.stray += 1;
318 return;
319 }
320 if bytes.len() > 1500 || !control_shape(bytes) {
321 self.stats.malformed += 1;
322 return;
323 }
324 let Ok(Packet::Control(packet)) = Packet::parse(bytes) else {
325 self.stats.malformed += 1;
326 return;
327 };
328 if packet.dest != self.socket_id {
329 self.stats.stray += 1;
330 return;
331 }
332 match packet.body {
333 Control::Handshake(h) => self.handshake(t, h),
334 control => {
335 if let State::Open(sender) = &mut self.state {
336 sender.on_control(t, &control);
337 } else {
338 self.stats.stray += 1;
339 }
340 self.tick(t);
341 }
342 }
343 }
344
345 pub fn handle_timeout(&mut self, now: Instant) {
348 let t = self.micros(now);
349 self.tick(t);
350 }
351
352 pub fn poll_transmit(&mut self, now: Instant) -> Option<(SocketAddr, Vec<u8>)> {
355 let t = self.micros(now);
356 self.tick(t);
357 if let Some(dest) = self.shutdown.take() {
358 let mut bytes = Vec::with_capacity(16);
359 ControlPacket::new(t as u32, dest, Control::Shutdown).encode(&mut bytes);
360 return Some((self.peer, bytes));
361 }
362 let bytes = match &mut self.state {
363 State::Open(sender) => sender.poll_transmit(t),
364 State::Induction | State::Conclusion { .. } if t >= self.next_handshake => {
365 if self.stats.handshake_packets >= u64::from(self.config.max_handshake_packets) {
366 self.close(CallerCloseReason::HandshakeLimit, false);
367 return None;
368 }
369 self.stats.handshake_packets += 1;
370 self.next_handshake = t.saturating_add(duration_us(self.config.handshake_retry));
371 Some(self.handshake_packet(t))
372 }
373 _ => None,
374 };
375 self.tick(t);
376 bytes.map(|bytes| (self.peer, bytes))
377 }
378
379 pub fn poll_timeout(&self) -> Option<Instant> {
382 let t = match &self.state {
383 State::Induction | State::Conclusion { .. } => self
384 .next_handshake
385 .min(duration_us(self.config.connect_timeout)),
386 State::Open(sender) => sender.next_timer().min(
387 sender
388 .last_receive()
389 .saturating_add(duration_us(self.config.peer_idle)),
390 ),
391 State::Closed => return None,
392 };
393 self.epoch
394 .checked_add(Duration::from_micros(t.max(0) as u64))
395 }
396
397 pub fn stop(&mut self, now: Instant) {
400 self.micros(now);
401 self.close(CallerCloseReason::Cancelled, true);
402 }
403
404 fn tick(&mut self, t: i64) {
405 let reason = match &mut self.state {
406 State::Induction | State::Conclusion { .. }
407 if t >= duration_us(self.config.connect_timeout) =>
408 {
409 Some(CallerCloseReason::ConnectTimeout)
410 }
411 State::Open(sender) => {
412 sender.on_timer(t);
413 sender
414 .failure()
415 .map(|failure| match failure {
416 Failure::Crypto => CallerCloseReason::Crypto,
417 Failure::MediaExpired => CallerCloseReason::MediaExpired,
418 Failure::RetryLimit => CallerCloseReason::RetryLimit,
419 Failure::PeerClosed => CallerCloseReason::PeerClosed,
420 Failure::PeerError(code) => CallerCloseReason::PeerError(code),
421 })
422 .or_else(|| {
423 (t.saturating_sub(sender.last_receive())
424 >= duration_us(self.config.peer_idle))
425 .then_some(CallerCloseReason::PeerIdle)
426 })
427 }
428 _ => None,
429 };
430 if let Some(reason) = reason {
431 self.close(
432 reason,
433 !matches!(
434 reason,
435 CallerCloseReason::PeerClosed | CallerCloseReason::PeerError(_)
436 ),
437 );
438 }
439 }
440
441 fn close(&mut self, reason: CallerCloseReason, notify: bool) {
442 if self.is_closed() {
443 return;
444 }
445 if let State::Open(sender) = &mut self.state {
446 if notify {
447 self.shutdown = Some(sender.peer_socket());
448 }
449 sender.clear();
450 self.stats.send = sender.stats();
451 }
452 self.state = State::Closed;
453 self.crypto = None;
454 self.config.crypto = None;
455 self.events.push_back(CallerEvent::Closed { reason });
456 }
457
458 fn handshake(&mut self, t: i64, h: Handshake) {
459 if matches!(self.state, State::Open(_)) {
460 self.stats.stray += 1;
461 return;
462 }
463 if h.initial_seq != self.initial.get() {
464 self.stats.stray += 1;
465 return;
466 }
467 if let HandshakeKind::Rejection(code) = h.kind {
468 self.close(CallerCloseReason::Rejected(code), false);
469 return;
470 }
471 if h.version != 5 {
472 self.close(CallerCloseReason::UnsupportedPeer, false);
473 return;
474 }
475 match self.state {
476 State::Induction if h.kind == HandshakeKind::Induction => {
477 if h.extension != SRT_MAGIC
478 || (h.encryption != 0
479 && self
480 .config
481 .crypto
482 .as_ref()
483 .is_none_or(|c| c.key_size.field() != h.encryption))
484 || !h.extensions.is_empty()
485 {
486 self.close(CallerCloseReason::UnsupportedPeer, false);
487 return;
488 }
489 self.state = State::Conclusion {
490 cookie: h.cookie,
491 socket_id: if h.socket_id == self.socket_id {
495 0
496 } else {
497 h.socket_id
498 },
499 };
500 self.next_handshake = t;
501 }
502 State::Conclusion { .. } if h.kind == HandshakeKind::Conclusion => {
503 let responses: Vec<_> = h
504 .extensions
505 .iter()
506 .filter_map(|e| match e {
507 Extension::HsRsp(s) => Some(*s),
508 _ => None,
509 })
510 .collect();
511 let Some(response) = responses.first().copied() else {
512 self.close(CallerCloseReason::UnsupportedPeer, false);
513 return;
514 };
515 let unsupported = h.extensions.iter().any(|e| match e {
516 Extension::KmReq(_)
517 | Extension::Filter(_)
518 | Extension::Group(_)
519 | Extension::HsReq(_) => true,
520 Extension::Congestion(c) => c.as_slice() != b"live",
521 _ => false,
522 });
523 if responses.len() != 1
524 || unsupported
525 || h.extension & ext_flags::HSREQ == 0
526 || h.socket_id == 0
527 || h.mtu < 44 + self.config.send.max_payload as u32
528 || h.mtu > 1500
529 || h.flow_window == 0
530 || h.flow_window >= 0x4000_0000
531 || response.version < 0x01_03_00
532 || response.flags & srt_flags::STREAM != 0
533 || response.flags & srt_flags::TSBPDRCV == 0
534 || response.flags & srt_flags::REXMITFLG == 0
535 {
536 self.close(CallerCloseReason::UnsupportedPeer, false);
537 return;
538 }
539 let kms: Vec<_> = h
540 .extensions
541 .iter()
542 .filter_map(|e| match e {
543 Extension::KmRsp(b) => Some(b.as_slice()),
544 _ => None,
545 })
546 .collect();
547 let crypto_ok = match &self.crypto {
548 Some(crypto) => {
549 kms.len() == 1
550 && h.extension & ext_flags::KMREQ != 0
551 && self.config.crypto.as_ref().is_some_and(|c| {
552 h.encryption == 0 || c.key_size.field() == h.encryption
553 })
554 && crypto.confirm_initial(kms.first().copied().unwrap_or_default())
555 }
556 None => {
557 kms.is_empty() && h.encryption == 0 && h.extension & ext_flags::KMREQ == 0
558 }
559 };
560 if !crypto_ok {
561 self.close(
562 if self.crypto.is_some() {
563 CallerCloseReason::Crypto
564 } else {
565 CallerCloseReason::UnsupportedPeer
566 },
567 false,
568 );
569 return;
570 }
571 let requested_ms = self.config.latency.as_micros().div_ceil(1000) as u64;
572 let latency =
573 Duration::from_millis(requested_ms.max(u64::from(response.receiver_delay)));
574 let mut sender = Sender::new(
575 self.config.send.clone(),
576 t,
577 h.socket_id,
578 self.initial,
579 (h.flow_window as usize).min(self.config.send.max_packets),
580 );
581 sender.set_crypto(self.crypto.take());
582 let max_payload = sender.max_payload();
583 self.state = State::Open(Box::new(sender));
584 self.events.push_back(CallerEvent::Connected {
585 peer_socket: h.socket_id,
586 latency,
587 max_payload,
588 });
589 }
590 _ => self.stats.stray += 1,
591 }
592 }
593
594 fn handshake_packet(&self, t: i64) -> Vec<u8> {
595 let (cookie, dest, conclusion) = match self.state {
596 State::Conclusion { cookie, socket_id } => (cookie, socket_id, true),
597 _ => (0, 0, false),
598 };
599 let mut extensions = Vec::new();
600 if conclusion {
601 extensions.push(Extension::HsReq(SrtHandshake {
602 version: SRT_VERSION,
603 flags: FLAGS,
604 receiver_delay: 0,
605 sender_delay: self.config.latency.as_micros().div_ceil(1000) as u16,
606 }));
607 if let Some(crypto) = &self.crypto
608 && let Ok(km) = crypto.initial()
609 {
610 extensions.push(Extension::KmReq(km));
611 }
612 if !self.config.stream_id.is_empty() {
613 extensions.push(Extension::StreamId(
614 self.config.stream_id.as_bytes().to_vec(),
615 ));
616 }
617 }
618 let h = Handshake {
619 version: if conclusion { 5 } else { 4 },
620 encryption: if conclusion {
621 self.config
622 .crypto
623 .as_ref()
624 .map_or(0, |c| c.key_size.field())
625 } else {
626 0
627 },
628 extension: if conclusion {
629 ext_flags::HSREQ
630 | if self.crypto.is_some() {
631 ext_flags::KMREQ
632 } else {
633 0
634 }
635 | if self.config.stream_id.is_empty() {
636 0
637 } else {
638 ext_flags::CONFIG
639 }
640 } else {
641 2
642 },
643 initial_seq: self.initial.get(),
644 mtu: 1500,
645 flow_window: self.config.send.max_packets as u32,
646 kind: if conclusion {
647 HandshakeKind::Conclusion
648 } else {
649 HandshakeKind::Induction
650 },
651 socket_id: self.socket_id,
652 cookie,
653 peer_ip: encode_ip(self.peer),
654 extensions,
655 };
656 let mut bytes = Vec::with_capacity(84 + self.config.stream_id.len());
657 ControlPacket::new(t as u32, dest, Control::Handshake(h)).encode(&mut bytes);
658 bytes
659 }
660}
661
662fn duration_us(d: Duration) -> i64 {
663 i64::try_from(d.as_micros()).unwrap_or(i64::MAX)
664}
665
666fn encode_ip(peer: SocketAddr) -> [u8; 16] {
667 let mut bytes = [0u8; 16];
668 match peer.ip() {
669 std::net::IpAddr::V4(ip) => {
670 for (dst, src) in bytes.iter_mut().zip(ip.octets().iter().rev()) {
671 *dst = *src;
672 }
673 }
674 std::net::IpAddr::V6(ip) => {
675 for (dst, src) in bytes.chunks_mut(4).zip(ip.octets().chunks(4)) {
676 for (d, s) in dst.iter_mut().zip(src.iter().rev()) {
677 *d = *s;
678 }
679 }
680 }
681 }
682 bytes
683}
684
685fn control_shape(bytes: &[u8]) -> bool {
688 let Some(header) = bytes.get(..16) else {
689 return false;
690 };
691 let mut words = header.chunks_exact(4);
692 let Some(first) = words.next() else {
693 return false;
694 };
695 let Ok(first) = <[u8; 4]>::try_from(first) else {
696 return false;
697 };
698 let first = u32::from_be_bytes(first);
699 if first & 0x8000_0000 == 0 {
700 return false;
701 }
702 let kind = (first >> 16) & 0x7fff;
703 if kind <= 8 && first & 0xffff != 0 {
704 return false;
705 }
706 let size = bytes.len() - 16;
707 match (first >> 16) & 0x7fff {
708 0 => size >= 48 && size.is_multiple_of(4),
709 2 => matches!(size, 4 | 16 | 24 | 28) && bytes.get(16).is_some_and(|b| b & 0x80 == 0),
710 3 => {
711 size > 0
712 && size.is_multiple_of(4)
713 && valid_loss_words(bytes.get(16..).unwrap_or_default())
714 }
715 1 | 4 | 5 | 6 | 8 => matches!(size, 0 | 4),
716 7 => size == 8,
717 _ => true,
718 }
719}
720
721fn valid_loss_words(body: &[u8]) -> bool {
724 let mut endpoint = false;
725 for word in body.chunks_exact(4) {
726 let Some(first) = word.first() else {
727 return false;
728 };
729 let marker = first & 0x80 != 0;
730 if endpoint && marker {
731 return false;
732 }
733 endpoint = marker && !endpoint;
734 }
735 !endpoint
736}