Skip to main content

xrpl_wasm_stdlib/host/
field_helpers.rs

1use crate::host::Result;
2use crate::host::error_codes::{
3    match_result_code, match_result_code_optional, match_result_code_with_expected_bytes,
4    match_result_code_with_expected_bytes_optional,
5};
6
7/// Helper function for retrieving fixed-size fields with exact byte validation.
8///
9/// This function encapsulates the common pattern of:
10/// 1. Allocating a buffer of fixed size
11/// 2. Calling a host function to retrieve the field
12/// 3. Validating that exactly the expected number of bytes were returned
13/// 4. Returning the initialized buffer
14///
15/// # Type Parameters
16///
17/// * `N` - The size of the buffer (compile-time constant)
18/// * `F` - The type of the host function closure
19///
20/// # Arguments
21///
22/// * `field_code` - The field code identifying which field to retrieve
23/// * `host_fn` - A closure that calls the appropriate host function
24///   - Takes: (field_code: i32, buffer_ptr: *mut u8, buffer_size: usize) -> i32
25///   - Returns: result code (number of bytes written or error code)
26///
27/// # Returns
28///
29/// Returns `Result<[u8; N]>` containing the initialized buffer if successful
30///
31/// # Example
32///
33/// ```ignore
34/// let buffer = get_fixed_size_field_with_expected_bytes::<20>(
35///     field_code,
36///     |fc, buf, size| unsafe { get_current_ledger_obj_field(fc, buf, size) },
37/// )?;
38/// ```
39#[inline]
40pub fn get_fixed_size_field_with_expected_bytes<const N: usize, F>(
41    field_code: impl Into<i32>,
42    host_fn: F,
43) -> Result<[u8; N]>
44where
45    F: FnOnce(i32, *mut u8, usize) -> i32,
46{
47    // uninit is safe: assume_init is only called when result_code == N, meaning the host wrote exactly N bytes.
48    let mut buffer = core::mem::MaybeUninit::<[u8; N]>::uninit();
49    let result_code = host_fn(field_code.into(), buffer.as_mut_ptr().cast(), N);
50    match_result_code_with_expected_bytes(result_code, N, || unsafe { buffer.assume_init() })
51}
52
53/// Optional variant of `get_fixed_size_field_with_expected_bytes`.
54///
55/// Returns `None` if the field is not found, otherwise behaves identically to the required variant.
56///
57/// # Type Parameters
58///
59/// * `N` - The size of the buffer (compile-time constant)
60/// * `F` - The type of the host function closure
61///
62/// # Arguments
63///
64/// * `field_code` - The field code identifying which field to retrieve
65/// * `host_fn` - A closure that calls the appropriate host function
66///
67/// # Returns
68///
69/// Returns `Result<Option<[u8; N]>>` where:
70/// * `Ok(Some(buffer))` - If the field is present and has the expected size
71/// * `Ok(None)` - If the field is not found
72/// * `Err(Error)` - If there's an error retrieving the field
73///
74/// # Example
75///
76/// ```ignore
77/// let buffer = get_fixed_size_field_with_expected_bytes_optional::<20>(
78///     field_code,
79///     |fc, buf, size| unsafe { get_current_ledger_obj_field(fc, buf, size) },
80/// )?;
81/// ```
82#[inline]
83pub fn get_fixed_size_field_with_expected_bytes_optional<const N: usize, F>(
84    field_code: impl Into<i32>,
85    host_fn: F,
86) -> Result<Option<[u8; N]>>
87where
88    F: FnOnce(i32, *mut u8, usize) -> i32,
89{
90    // uninit is safe: assume_init is only called when result_code == N,
91    // meaning the host wrote exactly N bytes.
92    let mut buffer = core::mem::MaybeUninit::<[u8; N]>::uninit();
93    let result_code = host_fn(field_code.into(), buffer.as_mut_ptr().cast(), N);
94    match_result_code_with_expected_bytes_optional(result_code, N, || {
95        Some(unsafe { buffer.assume_init() })
96    })
97}
98
99/// Helper function for retrieving variable-size fields.
100///
101/// This function encapsulates the common pattern for variable-size fields where:
102/// 1. A buffer of maximum size is allocated
103/// 2. A host function is called to retrieve the field
104/// 3. The actual number of bytes written is returned (not validated for exact match)
105/// 4. Both the buffer and the actual length are returned
106///
107/// This is used for fields like Amount and Blob where the actual size can vary.
108///
109/// # Type Parameters
110///
111/// * `N` - The maximum size of the buffer (compile-time constant)
112/// * `F` - The type of the host function closure
113///
114/// # Arguments
115///
116/// * `field_code` - The field code identifying which field to retrieve
117/// * `host_fn` - A closure that calls the appropriate host function
118///   - Takes: (field_code: i32, buffer_ptr: *mut u8, buffer_size: usize) -> i32
119///   - Returns: result code (number of bytes written or error code)
120///
121/// # Returns
122///
123/// Returns `Result<([u8; N], usize)>` containing the buffer and actual length if successful
124///
125/// # Example
126///
127/// ```ignore
128/// let (buffer, len) = get_variable_size_field::<48>(
129///     field_code,
130///     |fc, buf, size| unsafe { get_current_ledger_obj_field(fc, buf, size) },
131/// )?;
132/// ```
133#[inline]
134pub fn get_variable_size_field<const N: usize, F>(
135    field_code: impl Into<i32>,
136    host_fn: F,
137) -> Result<([u8; N], usize)>
138where
139    F: FnOnce(i32, *mut u8, usize) -> i32,
140{
141    // zeroed (not uninit): result_code == 0 is a valid success for variable-size fields
142    // (e.g. an empty SigningPubKey signals a multi-signature transaction), so the buffer
143    // must be valid before the host writes anything.
144    let mut buffer = core::mem::MaybeUninit::<[u8; N]>::zeroed();
145    let result_code = host_fn(field_code.into(), buffer.as_mut_ptr().cast(), N);
146    match_result_code(result_code, || {
147        (unsafe { buffer.assume_init() }, result_code as usize)
148    })
149}
150
151/// Optional variant of `get_variable_size_field`.
152///
153/// Returns `None` if the field is not found, otherwise behaves identically to the required variant.
154///
155/// # Type Parameters
156///
157/// * `N` - The maximum size of the buffer (compile-time constant)
158/// * `F` - The type of the host function closure
159///
160/// # Arguments
161///
162/// * `field_code` - The field code identifying which field to retrieve
163/// * `host_fn` - A closure that calls the appropriate host function
164///
165/// # Returns
166///
167/// Returns `Result<Option<([u8; N], usize)>>` where:
168/// * `Ok(Some((buffer, len)))` - If the field is present
169/// * `Ok(None)` - If the field is not found
170/// * `Err(Error)` - If there's an error retrieving the field
171///
172/// # Example
173///
174/// ```ignore
175/// let result = get_variable_size_field_optional::<48>(
176///     field_code,
177///     |fc, buf, size| unsafe { get_current_ledger_obj_field(fc, buf, size) },
178/// )?;
179/// ```
180#[inline]
181pub fn get_variable_size_field_optional<const N: usize, F>(
182    field_code: impl Into<i32>,
183    host_fn: F,
184) -> Result<Option<([u8; N], usize)>>
185where
186    F: FnOnce(i32, *mut u8, usize) -> i32,
187{
188    // zeroed (not uninit): result_code == 0 is a valid success for variable-size fields
189    // (e.g. an empty SigningPubKey signals a multi-signature transaction), so the buffer
190    // must be valid before the host writes anything.
191    let mut buffer = core::mem::MaybeUninit::<[u8; N]>::zeroed();
192    let result_code = host_fn(field_code.into(), buffer.as_mut_ptr().cast(), N);
193    match_result_code_optional(result_code, || {
194        Some((unsafe { buffer.assume_init() }, result_code as usize))
195    })
196}