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}