Skip to main content

fluentbase_codec/
encoder.rs

1use crate::func::FunctionArgs;
2use crate::{alloc::string::ToString, error::CodecError};
3use byteorder::{ByteOrder, BE, LE};
4use bytes::{Buf, BytesMut};
5use core::marker::PhantomData;
6// TODO: @d1r1 Investigate whether decoding the result into an uninitialized memory (e.g., using
7// `MaybeUninit`) would be more efficient than initializing with `Default`.
8// This could potentially reduce unnecessary memory initialization overhead in cases where
9// the default value is not required before the actual decoding takes place.
10// Consider benchmarking both approaches to measure performance differences.
11
12/// Trait for encoding and decoding values with specific byte order, alignment, and mode.
13///
14/// # Type Parameters
15/// - `B`: The byte order used for encoding/decoding.
16/// - `ALIGN`: The alignment requirement for the encoded data.
17/// - `SOL_MODE`: A boolean flag indicating whether Solidity-compatible mode is enabled.
18/// - `IS_STATIC`: A boolean flag indicating whether the encoded data is static (used for
19///   SolidityPackedABI).
20pub trait Encoder<B: ByteOrder, const ALIGN: usize, const SOL_MODE: bool, const IS_STATIC: bool>:
21    Sized
22{
23    /// Returns the header size for this encoder.
24    const HEADER_SIZE: usize;
25    const IS_DYNAMIC: bool;
26
27    /// Encodes the value into the given buffer at the specified offset.
28    ///
29    /// # Arguments
30    /// * `buf` - The buffer to encode into.
31    /// * `offset` - The starting offset in the buffer for encoding.
32    ///
33    /// # Returns
34    /// `Ok(())` if encoding was successful, or an error if encoding failed.
35    fn encode(&self, buf: &mut BytesMut, offset: usize) -> Result<(), CodecError>;
36
37    /// Decodes a value from the given buffer starting at the specified offset.
38    ///
39    /// # Arguments
40    /// * `buf` - The buffer to decode from.
41    /// * `offset` - The starting offset in the buffer for decoding.
42    ///
43    /// # Returns
44    /// The decoded value if successful, or an error if decoding failed.
45    fn decode(buf: &impl Buf, offset: usize) -> Result<Self, CodecError>;
46
47    /// Partially decodes the header to determine the length and offset of the encoded data.
48    ///
49    /// # Arguments
50    /// * `buf` - The buffer to decode from.
51    /// * `offset` - The starting offset in the buffer for decoding.
52    ///
53    /// # Returns
54    /// A tuple `(data_offset, data_length)` if successful, or an error if decoding failed.
55    fn partial_decode(buf: &impl Buf, offset: usize) -> Result<(usize, usize), CodecError>;
56
57    /// Calculates the number of bytes needed to encode the value.
58    ///
59    /// This includes the header size and any additional space needed for alignment.
60    /// The default implementation aligns the header size to the specified alignment.
61    fn size_hint(&self) -> usize {
62        align_up::<ALIGN>(Self::HEADER_SIZE)
63    }
64}
65
66macro_rules! define_encoder_mode {
67    ($name:ident, $byte_order:ty, $align:expr, $sol_mode:expr) => {
68        pub struct $name<T>(PhantomData<T>);
69
70        impl<T> $name<T>
71        where
72            T: Encoder<$byte_order, $align, $sol_mode, false>,
73        {
74            pub fn is_dynamic() -> bool {
75                <T as Encoder<$byte_order, $align, $sol_mode, false>>::IS_DYNAMIC
76            }
77
78            pub fn encode(value: &T, buf: &mut BytesMut, offset: usize) -> Result<(), CodecError> {
79                value.encode(buf, offset)
80            }
81
82            pub fn decode(buf: &impl Buf, offset: usize) -> Result<T, CodecError> {
83                T::decode(buf, offset)
84            }
85
86            pub fn partial_decode(
87                buf: &impl Buf,
88                offset: usize,
89            ) -> Result<(usize, usize), CodecError> {
90                T::partial_decode(buf, offset)
91            }
92
93            pub fn size_hint(value: &T) -> usize {
94                value.size_hint()
95            }
96        }
97    };
98    ($name:ident, $byte_order:ty, $align:expr, $sol_mode:expr, static_only) => {
99        pub struct $name<T>(PhantomData<T>);
100
101        impl<T> $name<T>
102        where
103            T: Encoder<$byte_order, $align, $sol_mode, true>,
104        {
105            pub fn is_dynamic() -> bool {
106                T::IS_DYNAMIC
107            }
108
109            pub fn encode(value: &T, buf: &mut BytesMut, offset: usize) -> Result<(), CodecError> {
110                value.encode(buf, offset)
111            }
112
113            pub fn decode(buf: &impl Buf, offset: usize) -> Result<T, CodecError> {
114                T::decode(buf, offset)
115            }
116
117            pub fn partial_decode(
118                buf: &impl Buf,
119                offset: usize,
120            ) -> Result<(usize, usize), CodecError> {
121                T::partial_decode(buf, offset)
122            }
123
124            pub fn size_hint(value: &T) -> usize {
125                value.size_hint()
126            }
127        }
128    };
129}
130
131define_encoder_mode!(SolidityABI, BE, 32, true);
132define_encoder_mode!(CompactABI, LE, 4, false);
133
134// SolidityPackedABI works only for static types
135define_encoder_mode!(SolidityPackedABI, BE, 1, true, static_only);
136
137impl<T> SolidityABI<T> {
138    pub fn encode_function_args(value: &T, buf: &mut BytesMut) -> Result<(), CodecError>
139    where
140        T: FunctionArgs<BE, 32, true, false>,
141    {
142        value.encode_as_args(buf)
143    }
144
145    pub fn decode_function_args(buf: &impl Buf) -> Result<T, CodecError>
146    where
147        T: FunctionArgs<BE, 32, true, false>,
148    {
149        T::decode_as_args(buf)
150    }
151}
152
153impl<T> CompactABI<T> {
154    pub fn encode_function_args(value: &T, buf: &mut BytesMut) -> Result<(), CodecError>
155    where
156        T: FunctionArgs<LE, 4, false, false>,
157    {
158        value.encode_as_args(buf)
159    }
160
161    pub fn decode_function_args(buf: &impl Buf) -> Result<T, CodecError>
162    where
163        T: FunctionArgs<LE, 4, false, false>,
164    {
165        T::decode_as_args(buf)
166    }
167}
168pub trait SolidityEncoder: Encoder<BE, 32, true, false> {
169    const SOLIDITY_HEADER_SIZE: usize = <Self as Encoder<BE, 32, true, false>>::HEADER_SIZE;
170}
171
172impl<T> SolidityEncoder for T where T: Encoder<BE, 32, true, false> {}
173
174pub trait SolidityPackedEncoder: Encoder<BE, 1, true, true> {
175    const SOLIDITY_PACKED_HEADER_SIZE: usize = <Self as Encoder<BE, 1, true, true>>::HEADER_SIZE;
176}
177
178impl<T> SolidityPackedEncoder for T where T: Encoder<BE, 1, true, true> {}
179
180pub trait FluentEncoder: Encoder<LE, 4, false, false> {
181    const FLUENT_HEADER_SIZE: usize = <Self as Encoder<LE, 4, false, false>>::HEADER_SIZE;
182}
183
184impl<T> FluentEncoder for T where T: Encoder<LE, 4, false, false> {}
185
186/// Checks if the given byte order is big-endian.
187pub fn is_big_endian<B: ByteOrder>() -> bool {
188    B::read_u16(&[0x12, 0x34]) == 0x1234
189}
190
191/// Rounds up the given offset to the nearest multiple of ALIGN.
192/// ALIGN must be a power of two.
193#[inline]
194pub const fn align_up<const ALIGN: usize>(offset: usize) -> usize {
195    (offset + ALIGN - 1) & !(ALIGN - 1)
196}
197
198/// Checks if the given type is dynamic.
199pub fn is_dynamic<
200    T: Encoder<B, ALIGN, SOL_MODE, IS_STATIC>,
201    B: ByteOrder,
202    const ALIGN: usize,
203    const SOL_MODE: bool,
204    const IS_STATIC: bool,
205>() -> bool {
206    T::IS_DYNAMIC
207}
208
209pub fn write_u32_aligned<B: ByteOrder, const ALIGN: usize>(
210    buf: &mut BytesMut,
211    offset: usize,
212    value: u32,
213) {
214    let aligned_value_size = align_up::<ALIGN>(4);
215
216    ensure_buf_size(buf, offset + aligned_value_size);
217
218    if is_big_endian::<B>() {
219        // For big-endian, copy to the end of the aligned array
220        let start = offset + aligned_value_size - 4;
221        B::write_u32(&mut buf[start..], value);
222    } else {
223        // For little-endian, copy to the start of the aligned array
224        B::write_u32(&mut buf[offset..offset + 4], value);
225    }
226}
227
228pub fn read_u32_aligned<B: ByteOrder, const ALIGN: usize>(
229    buf: &impl Buf,
230    offset: usize,
231) -> Result<u32, CodecError> {
232    let aligned_value_size = align_up::<ALIGN>(4);
233
234    // Check for overflow
235    let end_offset = offset.checked_add(aligned_value_size).ok_or_else(|| {
236        CodecError::Decoding(crate::error::DecodingError::BufferOverflow {
237            msg: "Overflow occurred when calculating end offset while reading aligned u32"
238                .to_string(),
239        })
240    })?;
241
242    if buf.remaining() < end_offset {
243        return Err(CodecError::Decoding(
244            crate::error::DecodingError::BufferTooSmall {
245                expected: end_offset,
246                found: buf.remaining(),
247                msg: "Buffer underflow occurred while reading aligned u32".to_string(),
248            },
249        ));
250    }
251
252    if is_big_endian::<B>() {
253        Ok(B::read_u32(&buf.chunk()[end_offset - 4..end_offset]))
254    } else {
255        Ok(B::read_u32(&buf.chunk()[offset..offset + 4]))
256    }
257}
258
259/// Returns a mutable slice of the buffer at the specified offset, aligned to the specified
260/// alignment. This slice is guaranteed to be large enough to hold the value of value_size.
261pub(crate) fn get_aligned_slice<B: ByteOrder, const ALIGN: usize>(
262    buf: &mut BytesMut,
263    offset: usize,
264    value_size: usize,
265) -> &mut [u8] {
266    let aligned_offset = align_up::<ALIGN>(offset);
267    let word_size = align_up::<ALIGN>(ALIGN.max(value_size));
268
269    // Ensure the buffer is large enough
270    ensure_buf_size(buf, aligned_offset + word_size);
271
272    let write_offset = if is_big_endian::<B>() {
273        // For big-endian, return slice at the end of the aligned space
274        aligned_offset + word_size - value_size
275    } else {
276        // For little-endian, return a slice at the beginning of the aligned space
277        aligned_offset
278    };
279
280    &mut buf[write_offset..write_offset + value_size]
281}
282
283pub(crate) fn get_aligned_indices<B: ByteOrder, const ALIGN: usize>(
284    offset: usize,
285    value_size: usize,
286) -> (usize, usize) {
287    let aligned_offset = align_up::<ALIGN>(offset);
288    let word_size = align_up::<ALIGN>(ALIGN.max(value_size));
289
290    let write_offset = if is_big_endian::<B>() {
291        // For big-endian, return indices at the end of the aligned space
292        aligned_offset + word_size - value_size
293    } else {
294        // For little-endian, return indices at the beginning of the aligned space
295        aligned_offset
296    };
297
298    (write_offset, write_offset + value_size)
299}
300
301/// Ensure the buffer is large enough to hold the data
302pub fn ensure_buf_size(buf: &mut BytesMut, required_size: usize) {
303    if buf.len() < required_size {
304        buf.resize(required_size, 0);
305    }
306}