Skip to main content

hopper_core/abi/
integers.rs

1//! Alignment-1 little-endian integer wire types.
2
3use core::fmt;
4
5/// Generate a little-endian wire integer type.
6///
7/// Each type is `#[repr(transparent)]` over `[u8; N]`, guaranteeing alignment 1.
8/// Arithmetic operations are not provided -- convert to native, compute, convert back.
9/// This keeps the wire layer honest: it's a storage format, not a compute type.
10macro_rules! wire_int {
11    (
12        $(#[$meta:meta])*
13        $name:ident, $native:ty, $size:literal, $canonical:literal
14    ) => {
15        $(#[$meta])*
16        #[derive(Clone, Copy, PartialEq, Eq, Default)]
17        #[repr(transparent)]
18        pub struct $name([u8; $size]);
19
20        // Compile-time guarantees
21        const _: () = assert!(core::mem::size_of::<$name>() == $size);
22        const _: () = assert!(core::mem::align_of::<$name>() == 1);
23
24        impl $name {
25            /// Zero value.
26            pub const ZERO: Self = Self([0u8; $size]);
27
28            /// Maximum value.
29            pub const MAX: Self = Self(<$native>::MAX.to_le_bytes());
30
31            /// Minimum value.
32            pub const MIN: Self = Self(<$native>::MIN.to_le_bytes());
33
34            /// Wrap a native value into wire format.
35            #[inline(always)]
36            pub const fn new(v: $native) -> Self {
37                Self(v.to_le_bytes())
38            }
39
40            /// Read the native value from wire format.
41            #[inline(always)]
42            pub const fn get(self) -> $native {
43                <$native>::from_le_bytes(self.0)
44            }
45
46            /// Write a native value into this wire slot.
47            #[inline(always)]
48            pub fn set(&mut self, v: $native) {
49                self.0 = v.to_le_bytes();
50            }
51
52            /// Raw byte access (immutable).
53            #[inline(always)]
54            pub const fn as_bytes(&self) -> &[u8; $size] {
55                &self.0
56            }
57
58            /// Raw byte access (mutable).
59            #[inline(always)]
60            pub fn as_bytes_mut(&mut self) -> &mut [u8; $size] {
61                &mut self.0
62            }
63        }
64
65        impl From<$native> for $name {
66            #[inline(always)]
67            fn from(v: $native) -> Self {
68                Self::new(v)
69            }
70        }
71
72        impl From<$name> for $native {
73            #[inline(always)]
74            fn from(w: $name) -> Self {
75                w.get()
76            }
77        }
78
79        impl PartialOrd for $name {
80            #[inline(always)]
81            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
82                Some(self.cmp(other))
83            }
84        }
85
86        impl Ord for $name {
87            #[inline(always)]
88            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
89                self.get().cmp(&other.get())
90            }
91        }
92
93        impl fmt::Debug for $name {
94            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95                write!(f, "{}({})", stringify!($name), self.get())
96            }
97        }
98
99        impl fmt::Display for $name {
100            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101                write!(f, "{}", self.get())
102            }
103        }
104
105        // SAFETY: align_of == 1, all bit patterns valid, Copy, no drop glue.
106        unsafe impl crate::abi::WireType for $name {
107            const WIRE_SIZE: usize = $size;
108            const CANONICAL_NAME: &'static str = $canonical;
109        }
110
111        // Bytemuck proof (Hopper Safety Audit Must-Fix #5): the Pod
112        // supertrait bound requires these impls. `#[repr(transparent)]`
113        // over `[u8; N]` satisfies every bytemuck obligation, all
114        // bit patterns valid, no padding, align-1 inherited from the
115        // inner array.
116        #[cfg(feature = "hopper-native-backend")]
117        unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Zeroable for $name {}
118        #[cfg(feature = "hopper-native-backend")]
119        unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Pod for $name {}
120
121        // SAFETY: #[repr(transparent)] over [u8; N], all bit patterns valid.
122        unsafe impl crate::account::Pod for $name {}
123
124        // Audit Step 5 seal: stamp the Hopper-authored marker so the
125        // blanket `ZeroCopy` impl picks this primitive up. A user
126        // bypassing the wire_int! path with their own bare
127        // `unsafe impl Pod` does not get the seal.
128        unsafe impl ::hopper_runtime::__sealed::HopperZeroCopySealed for $name {}
129
130        impl crate::account::FixedLayout for $name {
131            const SIZE: usize = $size;
132        }
133    };
134}
135
136wire_int!(
137    /// 16-bit unsigned little-endian wire integer.
138    WireU16, u16, 2, "u16"
139);
140
141wire_int!(
142    /// 32-bit unsigned little-endian wire integer.
143    WireU32, u32, 4, "u32"
144);
145
146wire_int!(
147    /// 64-bit unsigned little-endian wire integer.
148    WireU64, u64, 8, "u64"
149);
150
151wire_int!(
152    /// 128-bit unsigned little-endian wire integer.
153    WireU128, u128, 16, "u128"
154);
155
156wire_int!(
157    /// 16-bit signed little-endian wire integer.
158    WireI16, i16, 2, "i16"
159);
160
161wire_int!(
162    /// 32-bit signed little-endian wire integer.
163    WireI32, i32, 4, "i32"
164);
165
166wire_int!(
167    /// 64-bit signed little-endian wire integer.
168    WireI64, i64, 8, "i64"
169);
170
171wire_int!(
172    /// 128-bit signed little-endian wire integer.
173    WireI128, i128, 16, "i128"
174);
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn wire_u64_roundtrip() {
182        let w = WireU64::new(0xDEAD_BEEF_CAFE_BABE);
183        assert_eq!(w.get(), 0xDEAD_BEEF_CAFE_BABE);
184    }
185
186    #[test]
187    fn wire_i64_negative() {
188        let w = WireI64::new(-42);
189        assert_eq!(w.get(), -42);
190    }
191
192    #[test]
193    fn wire_ordering() {
194        let a = WireU32::new(10);
195        let b = WireU32::new(20);
196        assert!(a < b);
197    }
198
199    #[test]
200    fn wire_default_is_zero() {
201        let w = WireU64::default();
202        assert_eq!(w.get(), 0);
203    }
204}