Skip to main content

ic_wire/bounded/packed_i16/
mod.rs

1use crate::Error;
2use alloc::vec::Vec;
3use core::fmt;
4use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
5
6/// Bounded signed 16-bit words encoded as little-endian bytes.
7///
8/// Unlike `List<i16, N>` or `Vec<i16>`, this uses a byte count and fixed-width
9/// words instead of an element count and ZigZag varints. Switching a field
10/// requires coordinated consumers. Small-magnitude values take more space.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct PackedI16<const N: usize>(Vec<i16>);
13impl<const N: usize> PackedI16<N> {
14    /// Admit an owned sequence without copying. `N` counts elements, not bytes.
15    pub fn new(values: Vec<i16>) -> Result<Self, Error> {
16        if values.len() > N {
17            Err(Error::Limit)
18        } else {
19            Ok(Self(values))
20        }
21    }
22    /// Borrow the admitted words.
23    pub fn as_slice(&self) -> &[i16] {
24        &self.0
25    }
26    /// Recover the admitted sequence without copying.
27    pub fn into_vec(self) -> Vec<i16> {
28        self.0
29    }
30}
31impl<const N: usize> Serialize for PackedI16<N> {
32    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
33        // Opt-in field layout: [32767] changes from 01 fe ff 03 (Vec<i16>)
34        // to 02 ff 7f (byte count + LE words). Ordinary lists are unchanged;
35        // consumers switching this field must coordinate schemas and retry bytes.
36        #[cfg(target_endian = "little")]
37        {
38            serializer.serialize_bytes(bytemuck::must_cast_slice(&self.0))
39        }
40        #[cfg(target_endian = "big")]
41        {
42            let mut bytes = Vec::new();
43            bytes
44                .try_reserve_exact(self.0.len() * 2)
45                .map_err(|_| serde::ser::Error::custom("allocation"))?;
46            bytes.extend(self.0.iter().flat_map(|value| value.to_le_bytes()));
47            serializer.serialize_bytes(&bytes)
48        }
49    }
50}
51impl<'de, const N: usize> Deserialize<'de> for PackedI16<N> {
52    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
53        deserializer
54            .deserialize_bytes(PackedVisitor { limit: N })
55            .map(Self)
56    }
57}
58
59// Share decoding across bounds instead of specializing the allocation/copy loop
60// for each N. The field's exact element limit is still checked before allocation;
61// byte layout, canonical verification and budget charges remain unchanged.
62struct PackedVisitor {
63    limit: usize,
64}
65impl de::Visitor<'_> for PackedVisitor {
66    type Value = Vec<i16>;
67    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.write_str("bounded packed little-endian i16 words")
69    }
70    #[inline(never)]
71    fn visit_bytes<E: de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
72        if !bytes.len().is_multiple_of(2) || bytes.len() / 2 > self.limit {
73            return Err(E::custom("invalid packed length"));
74        }
75        let mut values = Vec::new();
76        values
77            .try_reserve_exact(bytes.len() / 2)
78            .map_err(|_| E::custom("allocation"))?;
79        values.extend(
80            bytes
81                .as_chunks::<2>()
82                .0
83                .iter()
84                .map(|word| i16::from_le_bytes(*word)),
85        );
86        Ok(values)
87    }
88}