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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
265pub enum EncodeError {
266    /// Destination cannot contain the requested encoding.
267    DestinationTooSmall {
268        /// Minimum destination size.
269        required: usize,
270        /// Supplied destination size.
271        actual: usize,
272    },
273    /// A length cannot be represented by the wire format.
274    LengthOverflow,
275    /// Generation zero is reserved.
276    ZeroGeneration,
277    /// Sequence zero is reserved.
278    ZeroSequence,
279    /// Protocol-specific bounded failure code.
280    Protocol(u16),
281}
282
283/// Decoder resource limit that was exceeded.
284#[derive(Clone, Copy, Debug, Eq, PartialEq)]
285pub enum LimitKind {
286    /// Complete encoded message size.
287    MessageBytes,
288    /// Opaque payload size.
289    PayloadBytes,
290    /// Protocol record count.
291    Records,
292    /// Aggregate protocol allocation.
293    AllocationBytes,
294}
295
296/// Decoder failures; values remain bounded and contain no peer-provided text.
297#[derive(Clone, Copy, Debug, Eq, PartialEq)]
298pub enum DecodeError {
299    /// Input ends before a required byte.
300    Truncated {
301        /// Minimum input size for the attempted decode.
302        required: usize,
303        /// Supplied input size.
304        actual: usize,
305    },
306    /// Message signature is not recognized.
307    BadMagic(u32),
308    /// Wire version is unsupported.
309    BadVersion {
310        /// Received major version.
311        major: u16,
312        /// Received minor version.
313        minor: u16,
314    },
315    /// Schema identity differs from the selected protocol.
316    SchemaMismatch,
317    /// Generation zero is reserved.
318    ZeroGeneration,
319    /// Sequence zero is reserved.
320    ZeroSequence,
321    /// Declared and actual sizes do not form one canonical record.
322    NonCanonicalLength {
323        /// Length declared by the envelope.
324        declared: usize,
325        /// Length of the supplied record.
326        actual: usize,
327    },
328    /// Checked length arithmetic overflowed.
329    LengthOverflow,
330    /// A checked relative range escapes its containing record.
331    RelativeRangeOutOfBounds {
332        /// Peer-declared byte offset.
333        offset: u32,
334        /// Peer-declared byte length.
335        len: u32,
336        /// Size of the containing validated record.
337        containing_len: usize,
338    },
339    /// A configured resource limit was exceeded.
340    LimitExceeded(LimitKind),
341    /// Protocol-specific bounded failure code.
342    Protocol(u16),
343}
344
345impl fmt::Display for EncodeError {
346    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
347        write!(formatter, "message encoding failed: {self:?}")
348    }
349}
350
351impl fmt::Display for DecodeError {
352    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
353        write!(formatter, "message decoding failed: {self:?}")
354    }
355}
356
357#[cfg(feature = "std")]
358impl std::error::Error for EncodeError {}
359#[cfg(feature = "std")]
360impl std::error::Error for DecodeError {}
361
362fn encode_envelope<P: Protocol>(envelope: Envelope, bytes: &mut [u8]) {
363    put_u32(bytes, 0, MESSAGE_MAGIC);
364    put_u16(bytes, 4, VERSION_MAJOR);
365    put_u16(bytes, 6, VERSION_MINOR);
366    put_u32(bytes, 8, envelope.kind);
367    put_u32(bytes, 12, envelope.flags);
368    put_u32(bytes, 16, envelope.payload_len);
369    put_u32(bytes, 20, ENVELOPE_LEN as u32);
370    put_u64(bytes, 24, envelope.generation);
371    put_u64(bytes, 32, envelope.sequence);
372    bytes[40..72].copy_from_slice(&P::SCHEMA_ID);
373}
374
375fn decode_envelope<P: Protocol>(bytes: &[u8]) -> Result<Envelope, DecodeError> {
376    let magic = get_u32(bytes, 0);
377    if magic != MESSAGE_MAGIC {
378        return Err(DecodeError::BadMagic(magic));
379    }
380    let major = get_u16(bytes, 4);
381    let minor = get_u16(bytes, 6);
382    if major != VERSION_MAJOR || minor != VERSION_MINOR {
383        return Err(DecodeError::BadVersion { major, minor });
384    }
385    let header_len = get_u32(bytes, 20);
386    if header_len != ENVELOPE_LEN as u32 {
387        return Err(DecodeError::NonCanonicalLength {
388            declared: header_len as usize,
389            actual: ENVELOPE_LEN,
390        });
391    }
392    if bytes[40..72] != P::SCHEMA_ID {
393        return Err(DecodeError::SchemaMismatch);
394    }
395    let generation = get_u64(bytes, 24);
396    if generation == 0 {
397        return Err(DecodeError::ZeroGeneration);
398    }
399    let sequence = get_u64(bytes, 32);
400    if sequence == 0 {
401        return Err(DecodeError::ZeroSequence);
402    }
403    Ok(Envelope {
404        kind: get_u32(bytes, 8),
405        flags: get_u32(bytes, 12),
406        generation,
407        sequence,
408        payload_len: get_u32(bytes, 16),
409    })
410}
411
412fn put_u16(bytes: &mut [u8], offset: usize, value: u16) {
413    bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
414}
415
416fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
417    bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
418}
419
420fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
421    bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
422}
423
424fn get_u16(bytes: &[u8], offset: usize) -> u16 {
425    u16::from_le_bytes(
426        bytes[offset..offset + 2]
427            .try_into()
428            .expect("fixed checked range"),
429    )
430}
431
432fn get_u32(bytes: &[u8], offset: usize) -> u32 {
433    u32::from_le_bytes(
434        bytes[offset..offset + 4]
435            .try_into()
436            .expect("fixed checked range"),
437    )
438}
439
440fn get_u64(bytes: &[u8], offset: usize) -> u64 {
441    u64::from_le_bytes(
442        bytes[offset..offset + 8]
443            .try_into()
444            .expect("fixed checked range"),
445    )
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451
452    struct U32Protocol;
453
454    impl Protocol for U32Protocol {
455        type Message = u32;
456        const SCHEMA_ID: [u8; 32] = [0xa5; 32];
457
458        fn encode_payload(
459            message: &Self::Message,
460            destination: &mut [u8],
461        ) -> Result<usize, EncodeError> {
462            if destination.len() < 4 {
463                return Err(EncodeError::DestinationTooSmall {
464                    required: 4,
465                    actual: destination.len(),
466                });
467            }
468            destination[..4].copy_from_slice(&message.to_le_bytes());
469            Ok(4)
470        }
471
472        fn decode_payload(
473            source: &[u8],
474            _context: &mut DecodeContext<'_>,
475        ) -> Result<Self::Message, DecodeError> {
476            if source.len() != 4 {
477                return Err(DecodeError::Protocol(1));
478            }
479            Ok(u32::from_le_bytes(source.try_into().unwrap()))
480        }
481    }
482
483    const LIMITS: Limits = Limits::new(1024, 512, 8, 512);
484
485    #[test]
486    fn golden_envelope_is_manual_little_endian() {
487        let mut actual = [0; ENVELOPE_LEN + 4];
488        let len =
489            encode_message::<U32Protocol>(Envelope::new(7, 0x10, 2, 3), &0x1122_3344, &mut actual)
490                .unwrap();
491        assert_eq!(len, 76);
492        let mut expected = [0; ENVELOPE_LEN + 4];
493        expected[0..4].copy_from_slice(&MESSAGE_MAGIC.to_le_bytes());
494        expected[4..6].copy_from_slice(&VERSION_MAJOR.to_le_bytes());
495        expected[6..8].copy_from_slice(&VERSION_MINOR.to_le_bytes());
496        expected[8..12].copy_from_slice(&7_u32.to_le_bytes());
497        expected[12..16].copy_from_slice(&0x10_u32.to_le_bytes());
498        expected[16..20].copy_from_slice(&4_u32.to_le_bytes());
499        expected[20..24].copy_from_slice(&(ENVELOPE_LEN as u32).to_le_bytes());
500        expected[24..32].copy_from_slice(&2_u64.to_le_bytes());
501        expected[32..40].copy_from_slice(&3_u64.to_le_bytes());
502        expected[40..72].fill(0xa5);
503        expected[72..76].copy_from_slice(&0x1122_3344_u32.to_le_bytes());
504        assert_eq!(actual, expected);
505
506        let decoded = decode_message::<U32Protocol>(&actual, &LIMITS).unwrap();
507        assert_eq!(decoded.envelope.kind, 7);
508        assert_eq!(decoded.message, 0x1122_3344);
509    }
510
511    #[test]
512    fn hostile_common_fields_are_rejected_before_payload_decode() {
513        let mut bytes = [0; ENVELOPE_LEN + 4];
514        encode_message::<U32Protocol>(Envelope::new(1, 0, 5, 1), &7, &mut bytes).unwrap();
515
516        for len in 0..ENVELOPE_LEN {
517            assert!(matches!(
518                decode_message::<U32Protocol>(&bytes[..len], &LIMITS),
519                Err(DecodeError::Truncated { .. })
520            ));
521        }
522        let mut bad = bytes;
523        bad[40] ^= 1;
524        assert_eq!(
525            decode_message::<U32Protocol>(&bad, &LIMITS).unwrap_err(),
526            DecodeError::SchemaMismatch
527        );
528        let mut bad = bytes;
529        bad[16..20].copy_from_slice(&500_u32.to_le_bytes());
530        assert!(matches!(
531            decode_message::<U32Protocol>(&bad, &LIMITS),
532            Err(DecodeError::NonCanonicalLength { .. })
533        ));
534        let strict = Limits::new(75, 512, 8, 512);
535        assert_eq!(
536            decode_message::<U32Protocol>(&bytes, &strict).unwrap_err(),
537            DecodeError::LimitExceeded(LimitKind::MessageBytes)
538        );
539    }
540
541    #[test]
542    fn relative_ranges_and_allocation_limits_are_checked() {
543        assert_eq!(RelativeRange::new(2, 3, 5).unwrap().range(), 2..5);
544        assert!(matches!(
545            RelativeRange::new(u32::MAX, 2, 8),
546            Err(DecodeError::RelativeRangeOutOfBounds { .. })
547        ));
548        let limits = Limits::new(10, 10, 1, 8);
549        let mut context = DecodeContext::new(&limits);
550        assert_eq!(
551            context.copy_bytes(&[0; 9]).unwrap_err(),
552            DecodeError::LimitExceeded(LimitKind::AllocationBytes)
553        );
554        let aggregate_limits = Limits::new(10, 10, 1, 8);
555        let mut context = DecodeContext::new(&aggregate_limits);
556        assert_eq!(context.copy_bytes(&[0; 4]).unwrap(), &[0; 4]);
557        assert_eq!(context.copy_bytes(&[0; 4]).unwrap(), &[0; 4]);
558        assert_eq!(
559            context.copy_bytes(&[0; 1]).unwrap_err(),
560            DecodeError::LimitExceeded(LimitKind::AllocationBytes)
561        );
562    }
563}