Skip to main content

rmt_flute/
alc.rs

1//! ALC — Asynchronous Layered Coding packet (RFC 5775 §2, §4).
2//!
3//! An ALC packet (the UDP payload) = **LCT header + FEC Payload ID + Encoding
4//! Symbol(s)**. ALC v1 uses LCT v1 ([`crate::lct`]).
5//!
6//! ⚠ The concrete FEC Payload ID bit layout is **not defined by RFC 5775** — it
7//! depends on the FEC Scheme / FEC Encoding ID in use (RFC 5052 and the FEC
8//! Scheme document). This crate therefore treats the FEC Payload ID as opaque
9//! bytes in [`AlcPacket::fec_payload_id`]; the caller, knowing the FEC scheme,
10//! slices it. One concrete layout (Small Block Systematic, `fec_id` = 129) is
11//! provided as [`FecPayloadId128`] for convenience.
12//!
13//! A *data-less* ALC packet (RFC 5775 §4.1) carries the LCT header only — no FEC
14//! Payload ID and no payload; that maps to an [`AlcPacket`] with an empty
15//! `fec_payload_id` and empty `payload`.
16
17use crate::error::{Error, Result};
18use crate::lct::LctHeader;
19
20/// HET for ALC's EXT_FTI (FEC Object Transmission Information) — RFC 5775 §4.2.
21/// Variable-length form (HET 0..=127). The HEC body is FEC-scheme dependent.
22pub const HET_EXT_FTI: u8 = 64;
23
24/// ALC PSI bit: SPI (Source Packet Indicator) — RFC 5775 §2.1, the high PSI bit.
25/// SPI = 1 ⇒ source-data FEC Payload ID format; 0 ⇒ repair-data format.
26pub const PSI_SPI: u8 = 0b10;
27
28/// A parsed ALC packet (RFC 5775 §4.1): an [`LctHeader`] followed by an opaque
29/// FEC Payload ID and the encoding-symbol payload.
30///
31/// Nothing is stored raw beyond the application-opaque FEC Payload ID and
32/// payload regions; the LCT header is fully typed and re-serialized from its
33/// fields. `fec_payload_id_len` must be supplied to [`AlcPacket::parse`]
34/// because RFC 5775 does not define the FEC Payload ID size (it is FEC-scheme
35/// dependent).
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize))]
38pub struct AlcPacket<'a> {
39    /// The LCT header (RFC 5651).
40    pub lct: LctHeader<'a>,
41    /// The opaque FEC Payload ID bytes (FEC-scheme dependent; may be empty for
42    /// a data-less control packet).
43    pub fec_payload_id: &'a [u8],
44    /// The encoding-symbol payload bytes (may be empty for a data-less packet).
45    pub payload: &'a [u8],
46}
47
48impl<'a> AlcPacket<'a> {
49    /// Construct an ALC packet from its parts.
50    pub fn new(lct: LctHeader<'a>, fec_payload_id: &'a [u8], payload: &'a [u8]) -> Self {
51        AlcPacket {
52            lct,
53            fec_payload_id,
54            payload,
55        }
56    }
57
58    /// `true` if the high PSI bit (SPI, source-packet indicator) is set.
59    pub fn spi(&self) -> bool {
60        self.lct.psi & PSI_SPI != 0
61    }
62
63    /// Total serialized length in bytes.
64    pub fn serialized_len(&self) -> usize {
65        self.lct.serialized_len() + self.fec_payload_id.len() + self.payload.len()
66    }
67
68    /// Parse an ALC packet. `fec_payload_id_len` is the FEC-scheme-defined size
69    /// of the FEC Payload ID in bytes (use `0` for a data-less packet that
70    /// carries no FEC Payload ID and no payload).
71    pub fn parse(data: &'a [u8], fec_payload_id_len: usize) -> Result<Self> {
72        let (lct, used) = LctHeader::parse(data)?;
73        let rest = &data[used..];
74        if rest.len() < fec_payload_id_len {
75            return Err(Error::BufferTooShort {
76                need: fec_payload_id_len,
77                have: rest.len(),
78                what: "ALC FEC Payload ID",
79            });
80        }
81        let fec_payload_id = &rest[..fec_payload_id_len];
82        let payload = &rest[fec_payload_id_len..];
83        Ok(AlcPacket {
84            lct,
85            fec_payload_id,
86            payload,
87        })
88    }
89
90    /// Serialize the ALC packet into `out`. Returns bytes written.
91    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
92        let total = self.serialized_len();
93        if out.len() < total {
94            return Err(Error::OutputBufferTooSmall {
95                need: total,
96                have: out.len(),
97            });
98        }
99        let mut off = self.lct.serialize_into(out)?;
100        out[off..off + self.fec_payload_id.len()].copy_from_slice(self.fec_payload_id);
101        off += self.fec_payload_id.len();
102        out[off..off + self.payload.len()].copy_from_slice(self.payload);
103        off += self.payload.len();
104        Ok(off)
105    }
106}
107
108/// FEC Payload ID for Small Block Systematic codes (`fec_id` = 128/129),
109/// reproduced from RFC 5445 as an *illustrative* layout (RFC 5775 itself
110/// defines no FEC Payload ID format). 8 bytes: a 32-bit `source_block_number`,
111/// a 16-bit `source_block_length`, and a 16-bit `encoding_symbol_id`.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114pub struct FecPayloadId128 {
115    /// Coding-block position within the object.
116    pub source_block_number: u32,
117    /// Number of source symbols (user-data segments) in the block.
118    pub source_block_length: u16,
119    /// Symbol index; `< source_block_length` ⇒ source symbol, else parity.
120    pub encoding_symbol_id: u16,
121}
122
123/// Wire size in bytes of a [`FecPayloadId128`].
124pub const FEC_PAYLOAD_ID_128_LEN: usize = 8;
125
126impl FecPayloadId128 {
127    /// Serialized length (always [`FEC_PAYLOAD_ID_128_LEN`]).
128    pub fn serialized_len(&self) -> usize {
129        FEC_PAYLOAD_ID_128_LEN
130    }
131
132    /// Parse from exactly the first 8 bytes of `data`.
133    pub fn parse(data: &[u8]) -> Result<Self> {
134        if data.len() < FEC_PAYLOAD_ID_128_LEN {
135            return Err(Error::BufferTooShort {
136                need: FEC_PAYLOAD_ID_128_LEN,
137                have: data.len(),
138                what: "FEC Payload ID (fec_id 128/129)",
139            });
140        }
141        Ok(FecPayloadId128 {
142            source_block_number: u32::from_be_bytes([data[0], data[1], data[2], data[3]]),
143            source_block_length: u16::from_be_bytes([data[4], data[5]]),
144            encoding_symbol_id: u16::from_be_bytes([data[6], data[7]]),
145        })
146    }
147
148    /// Serialize into `out` (8 bytes). Returns bytes written.
149    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
150        if out.len() < FEC_PAYLOAD_ID_128_LEN {
151            return Err(Error::OutputBufferTooSmall {
152                need: FEC_PAYLOAD_ID_128_LEN,
153                have: out.len(),
154            });
155        }
156        out[0..4].copy_from_slice(&self.source_block_number.to_be_bytes());
157        out[4..6].copy_from_slice(&self.source_block_length.to_be_bytes());
158        out[6..8].copy_from_slice(&self.encoding_symbol_id.to_be_bytes());
159        Ok(FEC_PAYLOAD_ID_128_LEN)
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::lct::{LCT_VERSION, LctHeader};
167    use alloc::vec;
168
169    fn lct_with_tsi() -> ([u8; 4], [u8; 4]) {
170        // CCI (4) + TSI (4, S=1) so the ALC TSI-non-zero rule holds.
171        ([0u8; 4], [0x00, 0x00, 0x00, 0x07])
172    }
173
174    #[test]
175    fn alc_packet_round_trip() {
176        let (cci, tsi) = lct_with_tsi();
177        let lct = LctHeader {
178            version: LCT_VERSION,
179            psi: PSI_SPI,
180            close_session: false,
181            close_object: false,
182            codepoint: 0x80,
183            cci: &cci,
184            tsi: &tsi,
185            toi: &[],
186            extensions: vec![],
187        };
188        let fpid = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x02];
189        let payload = [0xDEu8, 0xAD, 0xBE, 0xEF];
190        let pkt = AlcPacket::new(lct, &fpid, &payload);
191        assert!(pkt.spi());
192
193        let mut out = vec![0u8; pkt.serialized_len()];
194        let n = pkt.serialize_into(&mut out).unwrap();
195        assert_eq!(n, pkt.serialized_len());
196
197        let re = AlcPacket::parse(&out, FEC_PAYLOAD_ID_128_LEN).unwrap();
198        assert_eq!(re, pkt);
199        assert_eq!(re.fec_payload_id, &fpid);
200        assert_eq!(re.payload, &payload);
201    }
202
203    #[test]
204    fn data_less_packet_has_no_fpid_or_payload() {
205        let (cci, tsi) = lct_with_tsi();
206        let lct = LctHeader {
207            version: LCT_VERSION,
208            psi: 0,
209            close_session: true,
210            close_object: false,
211            codepoint: 0,
212            cci: &cci,
213            tsi: &tsi,
214            toi: &[],
215            extensions: vec![],
216        };
217        let pkt = AlcPacket::new(lct, &[], &[]);
218        let mut out = vec![0u8; pkt.serialized_len()];
219        pkt.serialize_into(&mut out).unwrap();
220        let re = AlcPacket::parse(&out, 0).unwrap();
221        assert_eq!(re, pkt);
222        assert!(re.fec_payload_id.is_empty());
223        assert!(re.payload.is_empty());
224    }
225
226    #[test]
227    fn fec_payload_id_128_exact_bytes() {
228        let f = FecPayloadId128 {
229            source_block_number: 0x0102_0304,
230            source_block_length: 0x0506,
231            encoding_symbol_id: 0x0708,
232        };
233        let mut out = [0u8; FEC_PAYLOAD_ID_128_LEN];
234        f.serialize_into(&mut out).unwrap();
235        assert_eq!(out, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
236        assert_eq!(FecPayloadId128::parse(&out).unwrap(), f);
237    }
238
239    #[test]
240    fn mutating_payload_changes_wire() {
241        let (cci, tsi) = lct_with_tsi();
242        let mk = |p: &[u8]| {
243            let lct = LctHeader {
244                version: LCT_VERSION,
245                psi: 0,
246                close_session: false,
247                close_object: false,
248                codepoint: 0,
249                cci: &cci,
250                tsi: &tsi,
251                toi: &[],
252                extensions: vec![],
253            };
254            let pkt = AlcPacket::new(lct, &[], p);
255            let mut out = vec![0u8; pkt.serialized_len()];
256            pkt.serialize_into(&mut out).unwrap();
257            out
258        };
259        assert_ne!(mk(&[1, 2, 3, 4]), mk(&[1, 2, 3, 5]));
260    }
261}