Skip to main content

sapflow_srt/
caller.rs

1//! Live caller, written from draft-sharabayko-srt-01 sections
2//! 3.2.1 and 4.3.1 and our own packet/listener modules.
3//!
4//! The adapter owns its UDP socket and supplies monotonic time. Drain
5//! `poll_transmit(now)` immediately to that socket, then wait for input or
6//! `poll_timeout()`. A returned datagram counts as sent: the adapter must
7//! stop the session if it cannot submit it. Do not buffer paced datagrams
8//! in another unbounded queue. Qualification is still pending for
9//! seamless reconnection, late-drop negotiation or adaptive bitrate.
10
11use 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
25/// CRYPT is the specification's mandatory legacy KK-understanding flag,
26/// NOT a claim of encryption. Configured sessions require verified KM.
27/// We are send-only: do not advertise receiving or periodic NAK capability.
28const FLAGS: u32 = srt_flags::TSBPDSND | srt_flags::CRYPT | srt_flags::REXMITFLG;
29
30/// Settings for a [`Caller`]; [`Caller::new`] checks them and fails with a
31/// [`ConfigError`] if any is invalid.
32#[derive(Debug, Clone)]
33pub struct CallerConfig {
34    /// UTF-8, at most 512 bytes, no NUL. Sent in clear in this lab milestone.
35    pub stream_id: String,
36    /// Receiver latency (TSBPD delay) we ask the listener to use, rounded up
37    /// to whole milliseconds; it may use more. 1 ms to 65535 ms; default 120
38    /// ms.
39    pub latency: Duration,
40    /// How long to wait for the handshake to complete, counted from creation;
41    /// at least `handshake_retry`, at most 60 s; default 5 s.
42    pub connect_timeout: Duration,
43    /// Interval between handshake packets while connecting; 10 ms to 5 s;
44    /// default 250 ms.
45    pub handshake_retry: Duration,
46    /// Most handshake packets sent before giving up with
47    /// [`CallerCloseReason::HandshakeLimit`]; 1 to 1024; default 32.
48    pub max_handshake_packets: u32,
49    /// A connected session ends after this long without a valid packet from
50    /// the listener; 10 ms to 60 s; default 5 s.
51    pub peer_idle: Duration,
52    /// Limits and pacing for the sending window.
53    pub send: SendConfig,
54    /// None explicitly selects plaintext; Some requires encryption.
55    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/// Why a [`Caller`] could not be created.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum ConfigError {
76    /// The stream ID is longer than 512 bytes or contains a NUL.
77    StreamId,
78    /// A time, packet, payload, bandwidth or byte limit is out of range.
79    Limits,
80    /// The caller socket ID is zero.
81    SocketId,
82    /// The peer address has port 0, or is unspecified or multicast.
83    Peer,
84    /// The crypto configuration is invalid, or secure randomness was
85    /// unavailable.
86    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/// Why a [`Caller`] session ended.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum CallerCloseReason {
104    /// [`Caller::stop`] was called.
105    Cancelled,
106    /// The handshake did not complete within `connect_timeout`.
107    ConnectTimeout,
108    /// `max_handshake_packets` were sent without completing the handshake.
109    HandshakeLimit,
110    /// No valid packet from the listener for `peer_idle`.
111    PeerIdle,
112    /// The listener refused the handshake; the code is its rejection reason
113    /// (see [`RejectCode`](crate::listener::RejectCode)).
114    Rejected(u32),
115    /// The listener's handshake is not one this live caller can use: wrong
116    /// version, missing or unsupported extensions, or incompatible flags or
117    /// sizes.
118    UnsupportedPeer,
119    /// Encryption failed: the listener's key material response did not match,
120    /// or a key could not be used or rotated safely.
121    Crypto,
122    /// Queued media reached `send.max_age_us` before it was acknowledged.
123    MediaExpired,
124    /// A packet was retransmitted `send.max_retransmits` times.
125    RetryLimit,
126    /// The listener sent SHUTDOWN.
127    PeerClosed,
128    /// The listener sent a peer error; the code is the one it reported.
129    PeerError(u32),
130}
131
132/// What happened to a [`Caller`] session; see [`Caller::poll_event`].
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum CallerEvent {
135    /// Handshake only; does not prove application acceptance or playback.
136    Connected {
137        /// The socket ID the listener gave the connection.
138        peer_socket: u32,
139        /// The negotiated TSBPD latency: the larger of ours and the
140        /// listener's receiver delay, in whole milliseconds.
141        latency: Duration,
142        /// Largest message [`Caller::enqueue`] accepts, in bytes
143        /// (`send.max_payload`).
144        max_payload: usize,
145    },
146    /// The session ended.
147    Closed {
148        /// Why it ended.
149        reason: CallerCloseReason,
150    },
151}
152
153/// Counters for a [`Caller`].
154#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
155pub struct CallerStats {
156    /// Handshake packets sent, repeats included.
157    pub handshake_packets: u64,
158    /// Datagrams dropped for being over 1500 bytes or not a well-formed
159    /// control packet.
160    pub malformed: u64,
161    /// Datagrams ignored as not meant for this session: wrong source address,
162    /// wrong destination socket, or unexpected in the current state.
163    pub stray: u64,
164    /// The sender's counters; all zero until connected, and kept after
165    /// closing.
166    pub send: SendStats,
167}
168
169enum State {
170    Induction,
171    Conclusion { cookie: u32, socket_id: u32 },
172    Open(Box<Sender>),
173    Closed,
174}
175
176/// A live SRT caller: connects to a listener, then sends queued messages
177/// paced and bounded. Sans-IO; see the module docs.
178pub 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    /// A caller for `peer` with a random socket ID and initial sequence
195    /// number; `now` starts its clock. Fails if `config` or `peer` is not
196    /// valid.
197    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    /// Deterministic construction for tests. Production adapters should use
205    /// `new`, or provide fresh independently random socket/sequence IDs.
206    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    /// Whether the handshake has completed and the session is still open.
265    pub fn is_connected(&self) -> bool {
266        matches!(self.state, State::Open(_))
267    }
268    /// Whether the session has ended, for any reason.
269    pub fn is_closed(&self) -> bool {
270        matches!(self.state, State::Closed)
271    }
272    /// The counters so far, with the sender's once connected.
273    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    /// The next [`CallerEvent`], if any.
281    pub fn poll_event(&mut self) -> Option<CallerEvent> {
282        self.events.pop_front()
283    }
284
285    /// Transfers the payload's allocation into the bounded send window.
286    /// QueueFull is backpressure; the rejected payload is discarded.
287    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    /// Transfers ownership on success; returns the identical Vec allocation
292    /// on every rejection, including QueueFull. No payload clone or copy.
293    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    /// Takes a datagram that arrived from `peer` at `now`. One that is not a
308    /// well-formed control packet for this session from the listener is
309    /// counted in [`CallerStats`] and ignored.
310    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    /// Does what is due at `now`: connect and idle timeouts, the media age
346    /// limit and retransmission timers.
347    pub fn handle_timeout(&mut self, now: Instant) {
348        let t = self.micros(now);
349        self.tick(t);
350    }
351
352    /// One datagram at most. Repeated calls at the same time cannot burst
353    /// data; control responses are coalesced to one ACKACK slot.
354    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    /// When to call [`handle_timeout`](Self::handle_timeout) next; `None`
380    /// once the session has closed.
381    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    /// Idempotent cancellation releases payloads/timers immediately. Poll
398    /// once afterwards to send the optional best-effort shutdown datagram.
399    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                    // libsrt 1.5.4 echoes the caller ID in induction (observed
492                    // on the wire), not an addressable listener connection.
493                    // Its demultiplexer accepts the conclusion at socket 0.
494                    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
685/// Reject truncated control bodies before the shared permissive packet
686/// parser. Unknown control types cannot allocate beyond the datagram cap.
687fn 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
721// A range endpoint is an ordinary 31-bit sequence, never another range
722// marker. Check before Packet::parse masks the sequence-number flag bit.
723fn 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}