Skip to main content

fluentbase_codec/
bytes_codec.rs

1use crate::{
2    alloc::string::ToString,
3    encoder::{align_up, read_u32_aligned, write_u32_aligned},
4    error::{CodecError, DecodingError},
5};
6use byteorder::ByteOrder;
7use bytes::{Buf, Bytes, BytesMut};
8use core::mem;
9
10/// Universal function to write bytes in Solidity or WASM compatible format
11///
12/// # Parameters
13///
14/// - `buf`: A mutable reference to a `BytesMut` buffer where the bytes will be written.
15/// - `header_offset`: The offset in the buffer where the header should be written.
16/// - `data`: A slice of bytes representing the data to be written.
17/// - `elements`: The number of elements in the dynamic array.
18///
19/// # Type Parameters
20///
21/// - `B`: The byte order to be used (e.g., `BigEndian` or `LittleEndian`).
22/// - `ALIGN`: The alignment size.
23/// - `SOL_MODE`: A boolean indicating whether to use Solidity mode (`true`) or WASM mode (`false`).
24///
25/// # Returns
26///
27/// The number of bytes written, including alignment.
28///
29/// # Example
30///
31/// ```
32/// use bytes::BytesMut;
33/// use byteorder::BigEndian;
34/// use fluentbase_codec::bytes_codec::write_bytes;
35/// let mut buf = BytesMut::new();
36/// let data = &[1, 2, 3, 4, 5];
37/// let elements = data.len() as u32;
38/// let written = write_bytes::<BigEndian, 32, true>(&mut buf, 0, data, elements);
39/// assert_eq!(written, 37);
40/// ```
41pub fn write_bytes<B, const ALIGN: usize, const SOL_MODE: bool>(
42    buf: &mut BytesMut,
43    offset: usize,
44    data: &[u8],
45    elements: u32, // number of elements in a dynamic array
46) -> usize
47where
48    B: ByteOrder,
49{
50    if SOL_MODE {
51        write_bytes_solidity::<B, ALIGN>(buf, offset, data, elements)
52    } else {
53        write_bytes_wasm::<B, ALIGN>(buf, offset, data)
54    }
55}
56
57/// Write bytes in Solidity compatible format
58pub fn write_bytes_solidity<B: ByteOrder, const ALIGN: usize>(
59    buf: &mut BytesMut,
60    offset: usize,
61    data: &[u8],
62    elements: u32, // Number of elements
63) -> usize {
64    // Ensure we have enough space to write the offset
65
66    if buf.len() < offset {
67        buf.resize(offset, 0);
68    }
69    let data_offset = buf.len();
70
71    // Write length of the data (number of elements)
72    write_u32_aligned::<B, ALIGN>(buf, data_offset, elements);
73
74    // Append the actual data
75    buf.extend_from_slice(data);
76
77    // Return the number of bytes written (including alignment)
78    buf.len() - data_offset
79}
80
81/// Write bytes in WASM compatible format
82pub fn write_bytes_wasm<B: ByteOrder, const ALIGN: usize>(
83    buf: &mut BytesMut,
84    offset: usize,
85    data: &[u8],
86) -> usize {
87    let aligned_elem_size = align_up::<ALIGN>(mem::size_of::<u32>());
88    let aligned_header_size = aligned_elem_size * 2;
89
90    // Ensure we have enough space to write the header
91    if buf.len() < offset + aligned_header_size {
92        buf.resize(offset + aligned_header_size, 0);
93    }
94
95    // We append the data to the end of buffer
96    let data_offset = buf.len();
97
98    // Write offset and data size
99    write_u32_aligned::<B, ALIGN>(buf, offset, data_offset as u32);
100    write_u32_aligned::<B, ALIGN>(buf, offset + aligned_elem_size, data.len() as u32);
101
102    // Append the actual data
103    buf.extend_from_slice(data);
104
105    buf.len() - data_offset
106}
107
108pub fn read_bytes<B: ByteOrder, const ALIGN: usize, const SOL_MODE: bool>(
109    buf: &impl Buf,
110    offset: usize,
111) -> Result<Bytes, CodecError> {
112    let (data_offset, data_len) = read_bytes_header::<B, ALIGN, SOL_MODE>(buf, offset)?;
113
114    let data = if SOL_MODE {
115        buf.chunk()[data_offset + 32..data_offset + 32 + data_len].to_vec()
116    } else {
117        buf.chunk()[data_offset..data_offset + data_len].to_vec()
118    };
119
120    Ok(Bytes::from(data))
121}
122
123/// Reads the header of the bytes data in Solidity or WASM compatible format
124/// Returns the offset and size of the data
125pub fn read_bytes_header<B: ByteOrder, const ALIGN: usize, const SOL_MODE: bool>(
126    buf: &impl Buf,
127    offset: usize,
128) -> Result<(usize, usize), CodecError> {
129    match SOL_MODE {
130        true => read_bytes_header_solidity::<B, ALIGN>(buf, offset),
131        false => read_bytes_header_wasm::<B, ALIGN>(buf, offset),
132    }
133}
134
135pub fn read_bytes_header_wasm<B: ByteOrder, const ALIGN: usize>(
136    buffer: &impl Buf,
137    offset: usize,
138) -> Result<(usize, usize), CodecError> {
139    let aligned_elem_size = align_up::<ALIGN>(mem::size_of::<u32>());
140
141    if buffer.remaining() < offset + aligned_elem_size * 2 {
142        return Err(CodecError::Decoding(DecodingError::BufferTooSmall {
143            expected: offset + aligned_elem_size * 2,
144            found: buffer.remaining(),
145            msg: "buffer too small to read bytes header".to_string(),
146        }));
147    }
148
149    let data_offset = read_u32_aligned::<B, ALIGN>(buffer, offset)? as usize;
150
151    let data_len = read_u32_aligned::<B, ALIGN>(buffer, offset + aligned_elem_size)? as usize;
152
153    Ok((data_offset, data_len))
154}
155
156/// Reads the header of a Solidiof the data
157///
158/// Given the original data:
159/// ```
160/// let original: Vec<Vec<u32>> = vec![vec![1, 2, 3], vec![4, 5]];
161/// ```
162/// The Solidity encoding would look like this:
163///
164///
165/// 000 000  : 00 00 00 20   ||  032 |
166/// 032 000  : 00 00 00 02   ||  002 |
167///
168/// 064 000  : 00 00 00 40   ||  064 | <---- buf should start here
169/// 096 032  : 00 00 00 c0   ||  192 |
170/// 128 064  : 00 00 00 03   ||  003 |
171/// 160 096  : 00 00 00 01   ||  001 |
172/// 192 128  : 00 00 00 02   ||  002 |
173/// 224 160  : 00 00 00 03   ||  003 |
174/// 256 192  : 00 00 00 02   ||  002 |
175/// 288 224  : 00 00 00 04   ||  004 |
176/// 320 256  : 00 00 00 05   ||  005 |
177///
178///
179/// # Parameters
180///
181/// - `buf`: The buffer containing the encoded data.
182/// - `offset`: The offset from which to start reading.
183///
184/// # Returns
185///
186/// A tuple containing the data offset and the data length.
187///
188/// # Errors
189///
190/// Returns a `CodecError` if reading from the buffer fails.
191pub fn read_bytes_header_solidity<B: ByteOrder, const ALIGN: usize>(
192    buf: &impl Buf,
193    offset: usize,
194) -> Result<(usize, usize), CodecError> {
195    let data_offset = read_u32_aligned::<B, ALIGN>(buf, offset)? as usize;
196
197    let data_len = read_u32_aligned::<B, ALIGN>(buf, data_offset)? as usize;
198
199    Ok((data_offset, data_len))
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::encoder::{CompactABI, SolidityABI};
206    use alloy_sol_types::{sol_data, SolType};
207    use byteorder::{BigEndian, LE};
208
209    #[test]
210    fn test_write_bytes_sol() {
211        let mut buf = BytesMut::new();
212
213        // For byte slice
214        let bytes: &[u8] = &[1, 2, 3, 4, 5];
215        let written = write_bytes_solidity::<BigEndian, 32>(&mut buf, 0, bytes, bytes.len() as u32);
216        assert_eq!(written, 37); // length (32) + (data + padding) (32)
217        let expected = [
218            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
219            0, 0, 5, 1, 2, 3, 4, 5,
220        ];
221
222        assert_eq!(buf.to_vec(), expected);
223        let mut buf = BytesMut::new();
224
225        let offset = buf.len();
226
227        // For Vec<u32>
228
229        let vec_u32 = [0u8, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30];
230
231        let written = write_bytes_solidity::<BigEndian, 32>(&mut buf, offset, &vec_u32, 3);
232        assert_eq!(written, 44); // length (32) + data
233
234        let expected = [
235            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
236            0, 0, 3, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30,
237        ];
238        assert_eq!(buf.to_vec(), expected);
239    }
240
241    #[test]
242    fn test_read_bytes_header_solidity_simple() {
243        let original = alloy_primitives::Bytes::from(vec![1, 2, 3, 4, 5]);
244
245        let mut buf = BytesMut::new();
246        SolidityABI::encode(&original, &mut buf, 0).unwrap();
247
248        let encoded = buf.freeze();
249
250        let encoded_alloy = &sol_data::Bytes::abi_encode(&original)[..];
251
252        println!("alloy encoded: {:?}", encoded_alloy);
253        println!("encoded: {:?}", hex::encode(&encoded));
254        let (offset, size) = read_bytes_header::<BigEndian, 32, true>(&encoded, 0).unwrap();
255
256        println!("Offset: {}, Size: {}", offset, size);
257
258        assert_eq!(offset, 32);
259        assert_eq!(size, 5);
260    }
261
262    #[test]
263    fn test_read_bytes_header_solidity_complex() {
264        let original: Vec<Vec<u32>> = vec![vec![1, 2, 3], vec![4, 5]];
265
266        let mut buf = BytesMut::new();
267        SolidityABI::encode(&original, &mut buf, 0).unwrap();
268
269        let encoded = buf.freeze();
270        println!("encoded: {:?}", hex::encode(&encoded));
271
272        let chunk = &encoded.chunk()[64..];
273
274        // 1st vec
275        let (offset, size) = read_bytes_header_solidity::<BigEndian, 32>(&chunk, 0).unwrap();
276        assert_eq!(offset, 64);
277        assert_eq!(size, 3);
278
279        // 2nd vec
280        let (offset, size) = read_bytes_header_solidity::<BigEndian, 32>(&chunk, 32).unwrap();
281        assert_eq!(offset, 192);
282        assert_eq!(size, 2);
283    }
284
285    #[test]
286    fn test_read_bytes_header_wasm() {
287        let original = alloy_primitives::Bytes::from(vec![1, 2, 3, 4, 5]);
288
289        let mut buf = BytesMut::new();
290        CompactABI::encode(&original, &mut buf, 0).unwrap();
291
292        let encoded = buf.freeze();
293        println!("encoded: {:?}", hex::encode(&encoded));
294
295        let (offset, size) = read_bytes_header::<LE, 4, false>(&encoded, 0).unwrap();
296
297        println!("Offset: {}, Size: {}", offset, size);
298
299        assert_eq!(offset, 8);
300        assert_eq!(size, 5);
301    }
302}