Skip to main content

pack_io/
codec.rs

1//! The codec primitives: the [`Encode`] / [`Decode`] behaviour traits, the
2//! concrete in-memory [`Encoder`] / [`Decoder`] types, the [`Config`] struct,
3//! and the Tier-1 [`encode`] / [`decode`] free functions.
4//!
5//! ## Layering
6//!
7//! - **Tier 1** — the [`encode`] / [`decode`] free functions. One line each
8//!   direction, no setup, no type parameters beyond the target type.
9//! - **Tier 2** — concrete encoder / decoder types. The in-memory pair
10//!   ([`Encoder`] + [`Decoder`]) lives in this module; the streaming pair
11//!   ([`crate::IoEncoder`] + [`crate::IoDecoder`]) lives in
12//!   [`crate::io`] and is `std`-gated. All four implement the [`Encode`] /
13//!   [`Decode`] behaviour traits, so [`Serialize`] / [`Deserialize`] impls
14//!   work through any of them.
15//! - **Tier 3** — implementing the [`Serialize`] / [`Deserialize`] traits
16//!   directly on your own types. Generic over `E: Encode` / `D: Decode`, so
17//!   one impl works for both in-memory and streaming codecs.
18//!
19//! ## Safety contract for decoders
20//!
21//! Every method on [`Decode`] is total: it either returns the requested
22//! value (advancing the read cursor) or returns a [`SerialError`]. It never
23//! panics, never reads past the input, and never allocates more memory than
24//! the [`Config::max_alloc`] cap permits.
25
26use alloc::vec;
27use alloc::vec::Vec;
28
29use crate::error::{Result, SerialError};
30use crate::traits::{Deserialize, Serialize};
31use crate::varint;
32
33/// Configuration for a decode session.
34///
35/// At construction time the codec validates the configuration; an invalid
36/// config (currently: `max_alloc == 0`) is rejected before any bytes are read.
37/// Validation happens once, in [`Decoder::with_config`] /
38/// [`crate::IoDecoder::with_config`], not on every operation.
39///
40/// `Config` is `#[non_exhaustive]` so the project can add knobs in a MINOR
41/// release without breaking downstream code. Build instances with
42/// [`Config::new`] / [`Config::with_max_alloc`] or via [`Default`].
43///
44/// # Examples
45///
46/// ```
47/// use pack_io::{Config, Decoder};
48///
49/// // Refuse to allocate more than 16 KiB for any single length-prefixed
50/// // value (a `String`, a `Vec<u8>`, a collection element count, …).
51/// // Hostile producers that send multi-gigabyte length prefixes fail fast.
52/// let cfg = Config::new().with_max_alloc(16 * 1024);
53/// let dec = Decoder::with_config(&[], cfg).expect("non-zero cap");
54/// drop(dec);
55/// ```
56#[non_exhaustive]
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct Config {
59    /// Maximum number of bytes the decoder may allocate for any single
60    /// length-prefixed value (a `String`, a `Vec<u8>`, a collection element
61    /// count, …).
62    ///
63    /// The default is 1 GiB, which is enough that well-formed inputs are
64    /// never rejected on size, while still defending against the obvious
65    /// hostile-length-prefix DoS. Tighten this in any context that accepts
66    /// untrusted input from a low-budget producer.
67    pub max_alloc: usize,
68}
69
70impl Default for Config {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl Config {
77    /// Default configuration: `max_alloc = 1 GiB`.
78    ///
79    /// 1 GiB is large enough to be irrelevant for well-formed inputs and
80    /// small enough to refuse the obvious `length = u64::MAX` attack before
81    /// allocating a single byte.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// let cfg = pack_io::Config::new();
87    /// assert_eq!(cfg.max_alloc, 1 << 30);
88    /// ```
89    #[must_use]
90    pub const fn new() -> Self {
91        Self { max_alloc: 1 << 30 }
92    }
93
94    /// Replace `max_alloc` and return the updated config.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// let cfg = pack_io::Config::new().with_max_alloc(4096);
100    /// assert_eq!(cfg.max_alloc, 4096);
101    /// ```
102    #[must_use]
103    pub const fn with_max_alloc(mut self, max_alloc: usize) -> Self {
104        self.max_alloc = max_alloc;
105        self
106    }
107
108    /// Validate the configuration. Returns an error if any field is
109    /// nonsensical.
110    pub(crate) fn validate(self) -> Result<Self> {
111        if self.max_alloc == 0 {
112            return Err(SerialError::InvalidLength {
113                declared: 0,
114                remaining: 0,
115            });
116        }
117        Ok(self)
118    }
119}
120
121// ---------------------------------------------------------------------------
122// Encode / Decode behaviour traits
123// ---------------------------------------------------------------------------
124
125/// Sink that a [`Serialize`] implementation writes its wire-format bytes
126/// into.
127///
128/// Implemented by every concrete encoder in the crate ([`Encoder`] for the
129/// in-memory case, [`crate::IoEncoder`] for `std::io::Write` streams). User
130/// code rarely implements `Encode` directly — `Serialize` impls are written
131/// generically over `E: Encode` so a single impl works for every encoder
132/// flavour.
133///
134/// # Examples
135///
136/// ```
137/// use pack_io::{Encode, Encoder, Result};
138///
139/// // A helper that writes a length-prefixed list of `u32`s into any encoder.
140/// fn write_u32_list<E: Encode>(enc: &mut E, items: &[u32]) -> Result<()> {
141///     enc.write_varint_u64(items.len() as u64)?;
142///     for item in items {
143///         enc.write_varint_u64(u64::from(*item))?;
144///     }
145///     Ok(())
146/// }
147///
148/// let mut enc = Encoder::new();
149/// write_u32_list(&mut enc, &[1, 2, 3]).unwrap();
150/// ```
151pub trait Encode {
152    /// Append a single byte.
153    ///
154    /// # Errors
155    ///
156    /// Returns the encoder's underlying error variant (I/O failure for
157    /// streaming encoders; never errors for the in-memory [`Encoder`]).
158    fn write_byte(&mut self, byte: u8) -> Result<()>;
159
160    /// Append a slice of bytes.
161    ///
162    /// # Errors
163    ///
164    /// Same as [`Encode::write_byte`].
165    fn write_bytes(&mut self, bytes: &[u8]) -> Result<()>;
166
167    /// Hint that the caller is about to write `additional` more bytes.
168    ///
169    /// In-memory encoders MAY pre-allocate the requested capacity to avoid
170    /// intermediate `Vec` growth. Streaming encoders typically ignore the
171    /// hint. The default implementation is a no-op.
172    #[inline]
173    fn reserve(&mut self, additional: usize) {
174        let _ = additional;
175    }
176
177    /// Append a `u64` as an unsigned LEB128 varint (1–10 bytes).
178    ///
179    /// # Errors
180    ///
181    /// Same as [`Encode::write_bytes`].
182    #[inline]
183    fn write_varint_u64(&mut self, value: u64) -> Result<()> {
184        let mut buf = [0u8; varint::MAX_VARINT_LEN_U64];
185        let n = varint::write_u64(value, &mut buf);
186        self.write_bytes(&buf[..n])
187    }
188
189    /// Append a `u128` as an unsigned LEB128 varint (1–19 bytes).
190    ///
191    /// # Errors
192    ///
193    /// Same as [`Encode::write_bytes`].
194    #[inline]
195    fn write_varint_u128(&mut self, value: u128) -> Result<()> {
196        let mut buf = [0u8; varint::MAX_VARINT_LEN_U128];
197        let n = varint::write_u128(value, &mut buf);
198        self.write_bytes(&buf[..n])
199    }
200}
201
202/// Source that a [`Deserialize`] implementation reads its wire-format bytes
203/// from.
204///
205/// Implemented by every concrete decoder in the crate ([`Decoder`] for the
206/// in-memory case, [`crate::IoDecoder`] for `std::io::Read` streams). User
207/// code rarely implements `Decode` directly — `Deserialize` impls are
208/// written generically over `D: Decode`.
209///
210/// All methods are **total**: on any byte sequence they either succeed
211/// (advancing the cursor) or return a [`SerialError`]. They never panic,
212/// never read past the input, and never allocate more memory than
213/// [`Decode::max_alloc`] permits.
214pub trait Decode {
215    /// Read the next byte, advancing the cursor.
216    ///
217    /// # Errors
218    ///
219    /// Returns [`SerialError::UnexpectedEof`] if the input is exhausted.
220    /// Streaming decoders MAY return an I/O-flavoured error variant.
221    fn read_byte(&mut self) -> Result<u8>;
222
223    /// Fill `out` with exactly `out.len()` bytes, advancing the cursor.
224    ///
225    /// # Errors
226    ///
227    /// Returns [`SerialError::UnexpectedEof`] on short read.
228    fn read_into(&mut self, out: &mut [u8]) -> Result<()>;
229
230    /// Maximum number of bytes the decoder will allocate for a single
231    /// length-prefixed value. Mirrors [`Config::max_alloc`].
232    fn max_alloc(&self) -> usize;
233
234    /// Read a LEB128 varint as a `u64`.
235    ///
236    /// # Errors
237    ///
238    /// Returns [`SerialError::VarintOverflow`] for an overlong encoding,
239    /// or [`SerialError::UnexpectedEof`] for a truncated one.
240    #[inline]
241    fn read_varint_u64(&mut self) -> Result<u64> {
242        let mut result: u64 = 0;
243        let mut shift: u32 = 0;
244        for consumed in 1..=varint::MAX_VARINT_LEN_U64 {
245            let byte = self.read_byte()?;
246            // The 10th byte may only set bit 0 — anything else overflows u64.
247            if consumed == varint::MAX_VARINT_LEN_U64 && (byte & 0xfe) != 0 {
248                return Err(SerialError::VarintOverflow);
249            }
250            result |= u64::from(byte & 0x7f) << shift;
251            if byte & 0x80 == 0 {
252                return Ok(result);
253            }
254            shift += 7;
255        }
256        Err(SerialError::VarintOverflow)
257    }
258
259    /// Read a LEB128 varint as a `u128`.
260    ///
261    /// # Errors
262    ///
263    /// See [`Decode::read_varint_u64`].
264    #[inline]
265    fn read_varint_u128(&mut self) -> Result<u128> {
266        let mut result: u128 = 0;
267        let mut shift: u32 = 0;
268        for consumed in 1..=varint::MAX_VARINT_LEN_U128 {
269            let byte = self.read_byte()?;
270            // The 19th byte may only set the low two bits.
271            if consumed == varint::MAX_VARINT_LEN_U128 && (byte & 0xfc) != 0 {
272                return Err(SerialError::VarintOverflow);
273            }
274            result |= u128::from(byte & 0x7f) << shift;
275            if byte & 0x80 == 0 {
276                return Ok(result);
277            }
278            shift += 7;
279        }
280        Err(SerialError::VarintOverflow)
281    }
282
283    /// Read a length-prefixed byte run, allocating a fresh `Vec<u8>`.
284    ///
285    /// The length is read as a varint, validated against
286    /// [`Decode::max_alloc`], then the corresponding number of bytes is
287    /// read from the underlying source.
288    ///
289    /// # Errors
290    ///
291    /// - [`SerialError::InvalidLength`] if the prefix exceeds `max_alloc`.
292    /// - [`SerialError::UnexpectedEof`] if the source runs out before the
293    ///   declared length is satisfied.
294    #[inline]
295    fn read_length_prefixed(&mut self) -> Result<Vec<u8>> {
296        let declared = self.read_varint_u64()?;
297        let max = self.max_alloc() as u64;
298        if declared > max {
299            return Err(SerialError::InvalidLength {
300                declared,
301                remaining: 0,
302            });
303        }
304        let len = declared as usize;
305        let mut buf = vec![0u8; len];
306        self.read_into(&mut buf)?;
307        Ok(buf)
308    }
309}
310
311// ---------------------------------------------------------------------------
312// In-memory Encoder
313// ---------------------------------------------------------------------------
314
315/// In-memory encoder. Writes into an owned `Vec<u8>`; the buffer can be
316/// reused across encodes by calling [`Encoder::take`] to swap it out.
317///
318/// Implements [`Encode`], so [`Serialize`] impls written generically over
319/// `E: Encode` work directly through it.
320///
321/// # Examples
322///
323/// ```
324/// use pack_io::Encoder;
325///
326/// let mut enc = Encoder::new();
327/// enc.write(&7_u64).unwrap();
328/// enc.write(&"hello").unwrap();
329/// let bytes = enc.into_inner();
330/// assert!(bytes.len() > 0);
331/// ```
332#[derive(Debug, Default)]
333pub struct Encoder {
334    out: Vec<u8>,
335}
336
337impl Encoder {
338    /// Construct an encoder with an empty output buffer.
339    ///
340    /// # Examples
341    ///
342    /// ```
343    /// let enc = pack_io::Encoder::new();
344    /// assert!(enc.as_bytes().is_empty());
345    /// ```
346    #[must_use]
347    pub fn new() -> Self {
348        Self { out: Vec::new() }
349    }
350
351    /// Construct an encoder backed by `buffer`. The encoder appends to the
352    /// buffer rather than allocating its own — callers that re-use a single
353    /// `Vec<u8>` across many encodes avoid the per-call allocation.
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use pack_io::Encoder;
359    ///
360    /// let buf = Vec::with_capacity(64);
361    /// let mut enc = Encoder::into_buffer(buf);
362    /// enc.write(&42_u64).unwrap();
363    /// let buf = enc.into_inner();
364    /// assert!(!buf.is_empty());
365    /// ```
366    #[must_use]
367    pub fn into_buffer(buffer: Vec<u8>) -> Self {
368        Self { out: buffer }
369    }
370
371    /// Borrow the encoded bytes accumulated so far.
372    #[inline]
373    #[must_use]
374    pub fn as_bytes(&self) -> &[u8] {
375        &self.out
376    }
377
378    /// Consume the encoder and return its underlying buffer.
379    #[inline]
380    #[must_use]
381    pub fn into_inner(self) -> Vec<u8> {
382        self.out
383    }
384
385    /// Swap the encoder's buffer with a fresh empty one, returning the bytes
386    /// written so far. Useful for "encode then send" loops that want to
387    /// re-use the encoder.
388    #[must_use]
389    pub fn take(&mut self) -> Vec<u8> {
390        core::mem::take(&mut self.out)
391    }
392
393    /// Encode `value`, appending its bytes to the internal buffer.
394    ///
395    /// # Errors
396    ///
397    /// Propagates any error returned by the type's [`Serialize`]
398    /// implementation. Primitive impls in this crate never error on an
399    /// in-memory encoder.
400    #[inline]
401    pub fn write<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<()> {
402        value.serialize(self)
403    }
404}
405
406impl Encode for Encoder {
407    #[inline]
408    fn write_byte(&mut self, byte: u8) -> Result<()> {
409        self.out.push(byte);
410        Ok(())
411    }
412
413    #[inline]
414    fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
415        self.out.extend_from_slice(bytes);
416        Ok(())
417    }
418
419    #[inline]
420    fn reserve(&mut self, additional: usize) {
421        self.out.reserve(additional);
422    }
423}
424
425// ---------------------------------------------------------------------------
426// In-memory Decoder
427// ---------------------------------------------------------------------------
428
429/// In-memory decoder. Borrows from an input slice and advances a position
430/// pointer as values are read. Bounds-checked on every operation.
431///
432/// Implements [`Decode`], so [`Deserialize`] impls written generically over
433/// `D: Decode` work directly through it.
434///
435/// # Examples
436///
437/// ```
438/// use pack_io::{Encoder, Decoder};
439///
440/// let mut enc = Encoder::new();
441/// enc.write(&7_u64).unwrap();
442/// enc.write(&true).unwrap();
443/// let bytes = enc.into_inner();
444///
445/// let mut dec = Decoder::new(&bytes);
446/// let n: u64 = dec.read().unwrap();
447/// let b: bool = dec.read().unwrap();
448/// assert_eq!(n, 7);
449/// assert!(b);
450/// assert!(dec.is_empty());
451/// ```
452#[derive(Debug)]
453pub struct Decoder<'a> {
454    input: &'a [u8],
455    pos: usize,
456    config: Config,
457}
458
459impl<'a> Decoder<'a> {
460    /// Construct a decoder over `bytes`.
461    #[inline]
462    #[must_use]
463    pub fn new(bytes: &'a [u8]) -> Self {
464        Self {
465            input: bytes,
466            pos: 0,
467            config: Config::default(),
468        }
469    }
470
471    /// Construct a decoder with the supplied configuration.
472    ///
473    /// # Errors
474    ///
475    /// Returns [`SerialError::InvalidLength`] if `config.max_alloc == 0`.
476    pub fn with_config(bytes: &'a [u8], config: Config) -> Result<Self> {
477        Ok(Self {
478            input: bytes,
479            pos: 0,
480            config: config.validate()?,
481        })
482    }
483
484    /// Bytes consumed so far from the start of the input.
485    #[inline]
486    #[must_use]
487    pub fn position(&self) -> usize {
488        self.pos
489    }
490
491    /// Number of bytes remaining in the input.
492    #[inline]
493    #[must_use]
494    pub fn remaining(&self) -> usize {
495        self.input.len().saturating_sub(self.pos)
496    }
497
498    /// True when there are no more bytes to read.
499    #[inline]
500    #[must_use]
501    pub fn is_empty(&self) -> bool {
502        self.remaining() == 0
503    }
504
505    /// Decode a value of type `T` from the current position.
506    ///
507    /// # Errors
508    ///
509    /// Returns any [`SerialError`] surfaced by `T::deserialize`.
510    #[inline]
511    pub fn read<T: Deserialize>(&mut self) -> Result<T> {
512        T::deserialize(self)
513    }
514}
515
516impl Decode for Decoder<'_> {
517    #[inline]
518    fn read_byte(&mut self) -> Result<u8> {
519        match self.input.get(self.pos) {
520            Some(&b) => {
521                self.pos += 1;
522                Ok(b)
523            }
524            None => Err(SerialError::UnexpectedEof {
525                needed: 1,
526                remaining: 0,
527            }),
528        }
529    }
530
531    #[inline]
532    fn read_into(&mut self, out: &mut [u8]) -> Result<()> {
533        let n = out.len();
534        let remaining = self.remaining();
535        if n > remaining {
536            return Err(SerialError::UnexpectedEof {
537                needed: n,
538                remaining,
539            });
540        }
541        let start = self.pos;
542        let end = start + n;
543        out.copy_from_slice(&self.input[start..end]);
544        self.pos = end;
545        Ok(())
546    }
547
548    #[inline]
549    fn max_alloc(&self) -> usize {
550        self.config.max_alloc
551    }
552
553    /// In-memory specialisation: validates length against the actual buffer
554    /// length too, not just `max_alloc`. Catches truncated inputs without
555    /// allocating.
556    #[inline]
557    fn read_length_prefixed(&mut self) -> Result<Vec<u8>> {
558        let declared = self.read_varint_u64()?;
559        let max = self.config.max_alloc as u64;
560        if declared > max {
561            return Err(SerialError::InvalidLength {
562                declared,
563                remaining: self.remaining(),
564            });
565        }
566        let len = declared as usize;
567        let remaining = self.remaining();
568        if len > remaining {
569            return Err(SerialError::InvalidLength {
570                declared,
571                remaining,
572            });
573        }
574        let start = self.pos;
575        let end = start + len;
576        let slice = &self.input[start..end];
577        self.pos = end;
578        Ok(slice.to_vec())
579    }
580}
581
582// ---------------------------------------------------------------------------
583// Tier-1 free functions
584// ---------------------------------------------------------------------------
585
586/// Encode `value` into a freshly allocated `Vec<u8>`.
587///
588/// This is the **Tier-1** entry point — the one-line surface for the common
589/// case. Allocates one buffer sized to fit the encoded value.
590///
591/// # Examples
592///
593/// ```
594/// let bytes = pack_io::encode(&42_u64).unwrap();
595/// let back: u64 = pack_io::decode(&bytes).unwrap();
596/// assert_eq!(back, 42);
597/// ```
598///
599/// # Errors
600///
601/// Propagates any error returned by the type's [`Serialize`] implementation.
602/// The built-in primitive and collection impls never error on an in-memory
603/// encoder.
604#[inline]
605pub fn encode<T: Serialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
606    let mut enc = Encoder::new();
607    value.serialize(&mut enc)?;
608    Ok(enc.into_inner())
609}
610
611/// Decode a value of type `T` from `bytes`, requiring the input to be fully
612/// consumed.
613///
614/// This is the **Tier-1** entry point — the one-line surface for the common
615/// case. After the value has been read, the decoder checks that no bytes
616/// remain; trailing input is reported as [`SerialError::TrailingBytes`].
617/// Callers that want to read several values from a single buffer should use
618/// [`Decoder`] directly.
619///
620/// # Examples
621///
622/// ```
623/// let bytes = pack_io::encode(&"hello").unwrap();
624/// let back: String = pack_io::decode(&bytes).unwrap();
625/// assert_eq!(back, "hello");
626/// ```
627///
628/// # Errors
629///
630/// - Returns [`SerialError::TrailingBytes`] when extra bytes follow the value.
631/// - Propagates any [`SerialError`] from the type's [`Deserialize`] impl.
632#[inline]
633pub fn decode<T: Deserialize>(bytes: &[u8]) -> Result<T> {
634    let mut dec = Decoder::new(bytes);
635    let value = T::deserialize(&mut dec)?;
636    let remaining = dec.remaining();
637    if remaining != 0 {
638        return Err(SerialError::TrailingBytes { remaining });
639    }
640    Ok(value)
641}
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646
647    #[test]
648    fn config_default_has_one_gib_cap() {
649        let cfg = Config::default();
650        assert_eq!(cfg.max_alloc, 1 << 30);
651    }
652
653    #[test]
654    fn decoder_with_zero_cap_is_rejected() {
655        let cfg = Config::new().with_max_alloc(0);
656        let err = Decoder::with_config(&[], cfg).expect_err("zero cap is invalid");
657        assert!(matches!(err, SerialError::InvalidLength { .. }));
658    }
659
660    #[test]
661    fn encoder_into_buffer_reuses_caller_vec() {
662        let mut buf = Vec::with_capacity(64);
663        buf.push(0xff);
664        let mut enc = Encoder::into_buffer(buf);
665        enc.write(&7_u64).unwrap();
666        let out = enc.into_inner();
667        assert_eq!(out[0], 0xff);
668        assert!(out.len() > 1);
669    }
670
671    #[test]
672    fn encoder_take_returns_buffer_and_resets() {
673        let mut enc = Encoder::new();
674        enc.write(&1_u64).unwrap();
675        let first = enc.take();
676        assert!(!first.is_empty());
677        assert!(enc.as_bytes().is_empty());
678
679        enc.write(&2_u64).unwrap();
680        let second = enc.take();
681        assert_eq!(second, [0x02]);
682    }
683
684    #[test]
685    fn decode_rejects_trailing_bytes() {
686        let mut bytes = encode(&7_u8).unwrap();
687        bytes.push(0xff);
688        let err = decode::<u8>(&bytes).expect_err("trailing bytes should fail");
689        assert!(matches!(err, SerialError::TrailingBytes { remaining: 1 }));
690    }
691
692    #[test]
693    fn decoder_read_past_end_returns_unexpected_eof() {
694        let mut dec = Decoder::new(&[0x01]);
695        let _: u8 = dec.read().unwrap();
696        let err = dec.read::<u8>().expect_err("past end should fail");
697        assert!(matches!(err, SerialError::UnexpectedEof { .. }));
698    }
699
700    #[test]
701    fn decoder_length_prefix_above_cap_is_rejected() {
702        let cfg = Config::new().with_max_alloc(4);
703        let bytes = [0x05, b'h', b'e', b'l', b'l', b'o'];
704        let mut dec = Decoder::with_config(&bytes, cfg).expect("non-zero cap");
705        let err = dec
706            .read_length_prefixed()
707            .expect_err("length > cap should fail");
708        assert!(matches!(
709            err,
710            SerialError::InvalidLength { declared: 5, .. }
711        ));
712    }
713
714    #[test]
715    fn decoder_length_prefix_overflowing_remaining_is_rejected() {
716        let bytes = [0x10, b'a', b'b'];
717        let mut dec = Decoder::new(&bytes);
718        let err = dec
719            .read_length_prefixed()
720            .expect_err("length > remaining should fail");
721        assert!(matches!(err, SerialError::InvalidLength { .. }));
722    }
723
724    #[test]
725    fn decoder_position_advances_with_reads() {
726        let bytes = [0x01, 0x02, 0x03];
727        let mut dec = Decoder::new(&bytes);
728        assert_eq!(dec.position(), 0);
729        let _ = dec.read_byte().unwrap();
730        assert_eq!(dec.position(), 1);
731        let mut buf = [0u8; 2];
732        dec.read_into(&mut buf).unwrap();
733        assert_eq!(dec.position(), 3);
734        assert!(dec.is_empty());
735    }
736
737    #[test]
738    fn read_into_short_read_is_rejected() {
739        let mut dec = Decoder::new(&[0x01, 0x02]);
740        let mut buf = [0u8; 4];
741        let err = dec.read_into(&mut buf).expect_err("short read");
742        assert!(matches!(err, SerialError::UnexpectedEof { .. }));
743    }
744}