Skip to main content

native_ipc_core/
codec.rs

1//! Fixed-width little-endian envelopes and explicit payload codecs.
2
3use alloc::vec::Vec;
4use core::fmt;
5use core::ops::Range;
6
7/// Wire signature at the start of every encoded message.
8pub const MESSAGE_MAGIC: u32 = 0x4e49_5043;
9/// Current incompatible wire revision.
10pub const VERSION_MAJOR: u16 = 1;
11/// Current exact wire revision; decoders reject any different minor value.
12pub const VERSION_MINOR: u16 = 0;
13/// Bytes occupied by the common message envelope.
14pub const ENVELOPE_LEN: usize = 72;
15
16/// Bounded decoder policy applied before a protocol allocates or creates records.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct Limits {
19    /// Maximum complete encoded message size.
20    pub max_message_bytes: u32,
21    /// Maximum opaque payload size after the common envelope.
22    pub max_payload_bytes: u32,
23    /// Maximum variable records a protocol decoder may construct.
24    pub max_records: u32,
25    /// Maximum aggregate allocation a protocol decoder may perform.
26    pub max_allocation_bytes: u32,
27}
28
29/// Stateful resource budget shared by one complete protocol decode.
30pub struct DecodeContext<'a> {
31    limits: &'a Limits,
32    records_remaining: u32,
33    allocation_remaining: u32,
34}
35
36impl<'a> DecodeContext<'a> {
37    fn new(limits: &'a Limits) -> Self {
38        Self {
39            limits,
40            records_remaining: limits.max_records,
41            allocation_remaining: limits.max_allocation_bytes,
42        }
43    }
44
45    /// Returns the immutable outer decoder policy.
46    pub const fn limits(&self) -> &Limits {
47        self.limits
48    }
49
50    /// Charges records against the aggregate budget before constructing them.
51    pub fn claim_records(&mut self, count: u32) -> Result<(), DecodeError> {
52        self.records_remaining = self
53            .records_remaining
54            .checked_sub(count)
55            .ok_or(DecodeError::LimitExceeded(LimitKind::Records))?;
56        Ok(())
57    }
58
59    /// Charges bytes against the aggregate allocation budget before allocation.
60    pub fn claim_allocation(&mut self, bytes: u32) -> Result<(), DecodeError> {
61        self.allocation_remaining = self
62            .allocation_remaining
63            .checked_sub(bytes)
64            .ok_or(DecodeError::LimitExceeded(LimitKind::AllocationBytes))?;
65        Ok(())
66    }
67
68    /// Charges and copies hostile bytes into owned storage.
69    pub fn copy_bytes(&mut self, source: &[u8]) -> Result<Vec<u8>, DecodeError> {
70        let len = u32::try_from(source.len()).map_err(|_| DecodeError::LengthOverflow)?;
71        self.claim_allocation(len)?;
72        Ok(source.to_vec())
73    }
74}
75
76impl Limits {
77    /// Creates an explicit decoder policy.
78    pub const fn new(
79        max_message_bytes: u32,
80        max_payload_bytes: u32,
81        max_records: u32,
82        max_allocation_bytes: u32,
83    ) -> Self {
84        Self {
85            max_message_bytes,
86            max_payload_bytes,
87            max_records,
88            max_allocation_bytes,
89        }
90    }
91}
92
93/// Caller-provided metadata for a newly encoded message.
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
95pub struct Envelope {
96    /// Numeric message kind; interpretation belongs to the protocol.
97    pub kind: u32,
98    /// Numeric flags; unknown required bits must be rejected by the protocol.
99    pub flags: u32,
100    /// Nonzero connection generation.
101    pub generation: u64,
102    /// Nonzero per-direction sequence.
103    pub sequence: u64,
104    payload_len: u32,
105}
106
107impl Envelope {
108    /// Creates envelope metadata. The encoder fills the payload length.
109    pub const fn new(kind: u32, flags: u32, generation: u64, sequence: u64) -> Self {
110        Self {
111            kind,
112            flags,
113            generation,
114            sequence,
115            payload_len: 0,
116        }
117    }
118
119    /// Returns the encoded payload size.
120    pub const fn payload_len(self) -> u32 {
121        self.payload_len
122    }
123
124    /// Returns the total encoded size after checked conversion.
125    pub fn total_len(self) -> Result<usize, EncodeError> {
126        ENVELOPE_LEN
127            .checked_add(self.payload_len as usize)
128            .ok_or(EncodeError::LengthOverflow)
129    }
130
131    fn with_payload_len(mut self, payload_len: usize) -> Result<Self, EncodeError> {
132        self.payload_len = u32::try_from(payload_len).map_err(|_| EncodeError::LengthOverflow)?;
133        Ok(self)
134    }
135}
136
137/// An owned, fully validated decoded message and its common metadata.
138#[derive(Clone, Debug, Eq, PartialEq)]
139pub struct DecodedMessage<T> {
140    /// Validated common metadata.
141    pub envelope: Envelope,
142    /// Protocol-owned safe message value.
143    pub message: T,
144}
145
146/// Explicit protocol payload codec.
147///
148/// Implementations must manually encode fixed-width values and must apply
149/// the decode context before allocation or record construction. The common envelope is
150/// encoded and validated by [`encode_message`] and [`decode_message`].
151pub trait Protocol {
152    /// Safe, owned protocol message representation.
153    type Message;
154
155    /// Exact 256-bit schema identity for this protocol revision.
156    const SCHEMA_ID: [u8; 32];
157
158    /// Encodes only the protocol-specific payload.
159    fn encode_payload(
160        message: &Self::Message,
161        destination: &mut [u8],
162    ) -> Result<usize, EncodeError>;
163
164    /// Decodes hostile payload bytes into an owned safe value.
165    fn decode_payload(
166        source: &[u8],
167        context: &mut DecodeContext<'_>,
168    ) -> Result<Self::Message, DecodeError>;
169}
170
171/// Encodes a common envelope followed by an explicitly encoded payload.
172pub fn encode_message<P: Protocol>(
173    envelope: Envelope,
174    message: &P::Message,
175    destination: &mut [u8],
176) -> Result<usize, EncodeError> {
177    if envelope.generation == 0 {
178        return Err(EncodeError::ZeroGeneration);
179    }
180    if envelope.sequence == 0 {
181        return Err(EncodeError::ZeroSequence);
182    }
183    if destination.len() < ENVELOPE_LEN {
184        return Err(EncodeError::DestinationTooSmall {
185            required: ENVELOPE_LEN,
186            actual: destination.len(),
187        });
188    }
189
190    let payload_len = P::encode_payload(message, &mut destination[ENVELOPE_LEN..])?;
191    let envelope = envelope.with_payload_len(payload_len)?;
192    let total_len = envelope.total_len()?;
193    if total_len > destination.len() {
194        return Err(EncodeError::DestinationTooSmall {
195            required: total_len,
196            actual: destination.len(),
197        });
198    }
199    encode_envelope::<P>(envelope, &mut destination[..ENVELOPE_LEN]);
200    Ok(total_len)
201}
202
203/// Validates the common envelope, bounds, and schema before decoding a payload.
204pub fn decode_message<P: Protocol>(
205    source: &[u8],
206    limits: &Limits,
207) -> Result<DecodedMessage<P::Message>, DecodeError> {
208    if source.len() < ENVELOPE_LEN {
209        return Err(DecodeError::Truncated {
210            required: ENVELOPE_LEN,
211            actual: source.len(),
212        });
213    }
214    if source.len() > limits.max_message_bytes as usize {
215        return Err(DecodeError::LimitExceeded(LimitKind::MessageBytes));
216    }
217    let envelope = decode_envelope::<P>(&source[..ENVELOPE_LEN])?;
218    let payload_len = envelope.payload_len as usize;
219    if payload_len > limits.max_payload_bytes as usize {
220        return Err(DecodeError::LimitExceeded(LimitKind::PayloadBytes));
221    }
222    let total_len = ENVELOPE_LEN
223        .checked_add(payload_len)
224        .ok_or(DecodeError::LengthOverflow)?;
225    if total_len != source.len() {
226        return Err(DecodeError::NonCanonicalLength {
227            declared: total_len,
228            actual: source.len(),
229        });
230    }
231    let mut context = DecodeContext::new(limits);
232    let message = P::decode_payload(&source[ENVELOPE_LEN..], &mut context)?;
233    Ok(DecodedMessage { envelope, message })
234}
235
236/// A checked relative byte range within one containing record.
237#[derive(Clone, Debug, Eq, PartialEq)]
238pub struct RelativeRange(Range<usize>);
239
240impl RelativeRange {
241    /// Validates a `u32` offset and length relative to a containing record.
242    pub fn new(offset: u32, len: u32, containing_len: usize) -> Result<Self, DecodeError> {
243        let start = offset as usize;
244        let end = start
245            .checked_add(len as usize)
246            .ok_or(DecodeError::LengthOverflow)?;
247        if end > containing_len {
248            return Err(DecodeError::RelativeRangeOutOfBounds {
249                offset,
250                len,
251                containing_len,
252            });
253        }
254        Ok(Self(start..end))
255    }
256
257    /// Returns the validated range.
258    pub fn range(&self) -> Range<usize> {
259        self.0.clone()
260    }
261}
262
263/// Encoder failures; values remain bounded and contain no peer-provided text.
264#[allow(missing_docs)]
265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
266pub enum EncodeError {
267    /// Destination cannot contain the requested encoding.
268    DestinationTooSmall { required: usize, actual: usize },
269    /// A length cannot be represented by the wire format.
270    LengthOverflow,
271    /// Generation zero is reserved.
272    ZeroGeneration,
273    /// Sequence zero is reserved.
274    ZeroSequence,
275    /// Protocol-specific bounded failure code.
276    Protocol(u16),
277}
278
279/// Decoder resource limit that was exceeded.
280#[derive(Clone, Copy, Debug, Eq, PartialEq)]
281pub enum LimitKind {
282    /// Complete encoded message size.
283    MessageBytes,
284    /// Opaque payload size.
285    PayloadBytes,
286    /// Protocol record count.
287    Records,
288    /// Aggregate protocol allocation.
289    AllocationBytes,
290}
291
292/// Decoder failures; values remain bounded and contain no peer-provided text.
293#[allow(missing_docs)]
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
295pub enum DecodeError {
296    /// Input ends before a required byte.
297    Truncated { required: usize, actual: usize },
298    /// Message signature is not recognized.
299    BadMagic(u32),
300    /// Wire version is unsupported.
301    BadVersion { major: u16, minor: u16 },
302    /// Schema identity differs from the selected protocol.
303    SchemaMismatch,
304    /// Generation zero is reserved.
305    ZeroGeneration,
306    /// Sequence zero is reserved.
307    ZeroSequence,
308    /// Declared and actual sizes do not form one canonical record.
309    NonCanonicalLength { declared: usize, actual: usize },
310    /// Checked length arithmetic overflowed.
311    LengthOverflow,
312    /// A checked relative range escapes its containing record.
313    RelativeRangeOutOfBounds {
314        offset: u32,
315        len: u32,
316        containing_len: usize,
317    },
318    /// A configured resource limit was exceeded.
319    LimitExceeded(LimitKind),
320    /// Protocol-specific bounded failure code.
321    Protocol(u16),
322}
323
324impl fmt::Display for EncodeError {
325    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
326        write!(formatter, "message encoding failed: {self:?}")
327    }
328}
329
330impl fmt::Display for DecodeError {
331    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
332        write!(formatter, "message decoding failed: {self:?}")
333    }
334}
335
336#[cfg(feature = "std")]
337impl std::error::Error for EncodeError {}
338#[cfg(feature = "std")]
339impl std::error::Error for DecodeError {}
340
341fn encode_envelope<P: Protocol>(envelope: Envelope, bytes: &mut [u8]) {
342    put_u32(bytes, 0, MESSAGE_MAGIC);
343    put_u16(bytes, 4, VERSION_MAJOR);
344    put_u16(bytes, 6, VERSION_MINOR);
345    put_u32(bytes, 8, envelope.kind);
346    put_u32(bytes, 12, envelope.flags);
347    put_u32(bytes, 16, envelope.payload_len);
348    put_u32(bytes, 20, ENVELOPE_LEN as u32);
349    put_u64(bytes, 24, envelope.generation);
350    put_u64(bytes, 32, envelope.sequence);
351    bytes[40..72].copy_from_slice(&P::SCHEMA_ID);
352}
353
354fn decode_envelope<P: Protocol>(bytes: &[u8]) -> Result<Envelope, DecodeError> {
355    let magic = get_u32(bytes, 0);
356    if magic != MESSAGE_MAGIC {
357        return Err(DecodeError::BadMagic(magic));
358    }
359    let major = get_u16(bytes, 4);
360    let minor = get_u16(bytes, 6);
361    if major != VERSION_MAJOR || minor != VERSION_MINOR {
362        return Err(DecodeError::BadVersion { major, minor });
363    }
364    let header_len = get_u32(bytes, 20);
365    if header_len != ENVELOPE_LEN as u32 {
366        return Err(DecodeError::NonCanonicalLength {
367            declared: header_len as usize,
368            actual: ENVELOPE_LEN,
369        });
370    }
371    if bytes[40..72] != P::SCHEMA_ID {
372        return Err(DecodeError::SchemaMismatch);
373    }
374    let generation = get_u64(bytes, 24);
375    if generation == 0 {
376        return Err(DecodeError::ZeroGeneration);
377    }
378    let sequence = get_u64(bytes, 32);
379    if sequence == 0 {
380        return Err(DecodeError::ZeroSequence);
381    }
382    Ok(Envelope {
383        kind: get_u32(bytes, 8),
384        flags: get_u32(bytes, 12),
385        generation,
386        sequence,
387        payload_len: get_u32(bytes, 16),
388    })
389}
390
391fn put_u16(bytes: &mut [u8], offset: usize, value: u16) {
392    bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
393}
394
395fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
396    bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
397}
398
399fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
400    bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
401}
402
403fn get_u16(bytes: &[u8], offset: usize) -> u16 {
404    u16::from_le_bytes(
405        bytes[offset..offset + 2]
406            .try_into()
407            .expect("fixed checked range"),
408    )
409}
410
411fn get_u32(bytes: &[u8], offset: usize) -> u32 {
412    u32::from_le_bytes(
413        bytes[offset..offset + 4]
414            .try_into()
415            .expect("fixed checked range"),
416    )
417}
418
419fn get_u64(bytes: &[u8], offset: usize) -> u64 {
420    u64::from_le_bytes(
421        bytes[offset..offset + 8]
422            .try_into()
423            .expect("fixed checked range"),
424    )
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430
431    struct U32Protocol;
432
433    impl Protocol for U32Protocol {
434        type Message = u32;
435        const SCHEMA_ID: [u8; 32] = [0xa5; 32];
436
437        fn encode_payload(
438            message: &Self::Message,
439            destination: &mut [u8],
440        ) -> Result<usize, EncodeError> {
441            if destination.len() < 4 {
442                return Err(EncodeError::DestinationTooSmall {
443                    required: 4,
444                    actual: destination.len(),
445                });
446            }
447            destination[..4].copy_from_slice(&message.to_le_bytes());
448            Ok(4)
449        }
450
451        fn decode_payload(
452            source: &[u8],
453            _context: &mut DecodeContext<'_>,
454        ) -> Result<Self::Message, DecodeError> {
455            if source.len() != 4 {
456                return Err(DecodeError::Protocol(1));
457            }
458            Ok(u32::from_le_bytes(source.try_into().unwrap()))
459        }
460    }
461
462    const LIMITS: Limits = Limits::new(1024, 512, 8, 512);
463
464    #[test]
465    fn golden_envelope_is_manual_little_endian() {
466        let mut actual = [0; ENVELOPE_LEN + 4];
467        let len =
468            encode_message::<U32Protocol>(Envelope::new(7, 0x10, 2, 3), &0x1122_3344, &mut actual)
469                .unwrap();
470        assert_eq!(len, 76);
471        let mut expected = [0; ENVELOPE_LEN + 4];
472        expected[0..4].copy_from_slice(&MESSAGE_MAGIC.to_le_bytes());
473        expected[4..6].copy_from_slice(&VERSION_MAJOR.to_le_bytes());
474        expected[6..8].copy_from_slice(&VERSION_MINOR.to_le_bytes());
475        expected[8..12].copy_from_slice(&7_u32.to_le_bytes());
476        expected[12..16].copy_from_slice(&0x10_u32.to_le_bytes());
477        expected[16..20].copy_from_slice(&4_u32.to_le_bytes());
478        expected[20..24].copy_from_slice(&(ENVELOPE_LEN as u32).to_le_bytes());
479        expected[24..32].copy_from_slice(&2_u64.to_le_bytes());
480        expected[32..40].copy_from_slice(&3_u64.to_le_bytes());
481        expected[40..72].fill(0xa5);
482        expected[72..76].copy_from_slice(&0x1122_3344_u32.to_le_bytes());
483        assert_eq!(actual, expected);
484
485        let decoded = decode_message::<U32Protocol>(&actual, &LIMITS).unwrap();
486        assert_eq!(decoded.envelope.kind, 7);
487        assert_eq!(decoded.message, 0x1122_3344);
488    }
489
490    #[test]
491    fn hostile_common_fields_are_rejected_before_payload_decode() {
492        let mut bytes = [0; ENVELOPE_LEN + 4];
493        encode_message::<U32Protocol>(Envelope::new(1, 0, 5, 1), &7, &mut bytes).unwrap();
494
495        for len in 0..ENVELOPE_LEN {
496            assert!(matches!(
497                decode_message::<U32Protocol>(&bytes[..len], &LIMITS),
498                Err(DecodeError::Truncated { .. })
499            ));
500        }
501        let mut bad = bytes;
502        bad[40] ^= 1;
503        assert_eq!(
504            decode_message::<U32Protocol>(&bad, &LIMITS).unwrap_err(),
505            DecodeError::SchemaMismatch
506        );
507        let mut bad = bytes;
508        bad[16..20].copy_from_slice(&500_u32.to_le_bytes());
509        assert!(matches!(
510            decode_message::<U32Protocol>(&bad, &LIMITS),
511            Err(DecodeError::NonCanonicalLength { .. })
512        ));
513        let strict = Limits::new(75, 512, 8, 512);
514        assert_eq!(
515            decode_message::<U32Protocol>(&bytes, &strict).unwrap_err(),
516            DecodeError::LimitExceeded(LimitKind::MessageBytes)
517        );
518    }
519
520    #[test]
521    fn relative_ranges_and_allocation_limits_are_checked() {
522        assert_eq!(RelativeRange::new(2, 3, 5).unwrap().range(), 2..5);
523        assert!(matches!(
524            RelativeRange::new(u32::MAX, 2, 8),
525            Err(DecodeError::RelativeRangeOutOfBounds { .. })
526        ));
527        let limits = Limits::new(10, 10, 1, 8);
528        let mut context = DecodeContext::new(&limits);
529        assert_eq!(
530            context.copy_bytes(&[0; 9]).unwrap_err(),
531            DecodeError::LimitExceeded(LimitKind::AllocationBytes)
532        );
533        let aggregate_limits = Limits::new(10, 10, 1, 8);
534        let mut context = DecodeContext::new(&aggregate_limits);
535        assert_eq!(context.copy_bytes(&[0; 4]).unwrap(), &[0; 4]);
536        assert_eq!(context.copy_bytes(&[0; 4]).unwrap(), &[0; 4]);
537        assert_eq!(
538            context.copy_bytes(&[0; 1]).unwrap_err(),
539            DecodeError::LimitExceeded(LimitKind::AllocationBytes)
540        );
541    }
542}