xrpl_wasm_stdlib/core/locator.rs
1//! Builder for nested field access locators.
2//!
3//! Locators encode a path to a nested field (sfields and array indices) in a compact
4//! binary format understood by the host. Use it to access fields like `Memos[0].MemoType`.
5//!
6//! Example
7//! ```no_run
8//! use xrpl_wasm_stdlib::core::locator::Locator;
9//! use xrpl_wasm_stdlib::sfield;
10//! let mut l = Locator::new();
11//! l.pack(sfield::Memos);
12//! l.pack(0);
13//! l.pack(sfield::MemoType);
14//! # let _ = (l.len() >= 3);
15//! ```
16
17use core::mem::MaybeUninit;
18
19/// The size of the buffer, in bytes, to use for any new locator
20const LOCATOR_BUFFER_SIZE: usize = 64;
21
22// /// A Locator may only pack this many levels deep in an object hierarchy (inclusive of the first
23// /// field)
24// const MAX_DEPTH: u8 = 12; // 1 byte for slot; 5 bytes for each packed object.
25
26/// A Locator allows a WASM developer located any field in any object (even nested fields) by
27/// specifying a `slot_num` (1 byte); a `locator_field_type` (1 byte); then one of an `sfield` (4
28/// bytes) or an `index` (4 bytes).
29///
30/// ## Derived Traits
31///
32/// - `Debug`: Useful for development and debugging
33/// - `Clone`: Reasonable for this 72-byte struct when explicit copying is needed
34/// - `Eq, PartialEq`: Enable comparisons between locators
35///
36/// Note: `Copy` is intentionally not derived due to the struct's size (72 bytes).
37/// Large `Copy` types can lead to accidental expensive copies and poor performance.
38/// Use `.clone()` when you need to duplicate a locator.
39#[derive(Clone, PartialEq, Eq, Debug)]
40#[repr(C)]
41pub struct Locator {
42 // The first packed value is 6 bytes; All nested/packed values are 5 bytes; so 64 bytes allow
43 // 12 nested levels of access.
44 buffer: [u8; LOCATOR_BUFFER_SIZE],
45
46 /// An index into `buffer` where the next packing operation can be stored.
47 cur_buffer_index: usize,
48}
49
50impl Default for Locator {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl Locator {
57 /// Create a new Locator using an unsigned 8-bit slot number. Valid slots are 0 to 255.
58 pub fn new_with_slot(slot_num: u8) -> Locator {
59 let mut buffer = MaybeUninit::<[u8; 64]>::uninit();
60 unsafe {
61 buffer.as_mut_ptr().cast::<u8>().write(slot_num);
62 }
63 Self {
64 buffer: unsafe { buffer.assume_init() },
65 cur_buffer_index: 1,
66 }
67 }
68
69 /// Create a new Locator. Valid slots are 0 to 255.
70 pub fn new() -> Locator {
71 let mut buffer = MaybeUninit::<[u8; 64]>::uninit();
72 // Initialize only the first byte to 0 for safety
73 unsafe {
74 buffer.as_mut_ptr().cast::<u8>().write(0);
75 }
76 Self {
77 buffer: unsafe { buffer.assume_init() },
78 cur_buffer_index: 0,
79 }
80 }
81
82 pub fn pack(&mut self, sfield_or_index: i32) -> bool {
83 if self.cur_buffer_index + 4 > LOCATOR_BUFFER_SIZE {
84 return false;
85 }
86
87 let value_bytes: [u8; 4] = sfield_or_index.to_le_bytes();
88 self.buffer[self.cur_buffer_index..self.cur_buffer_index + 4].copy_from_slice(&value_bytes);
89 self.cur_buffer_index += 4;
90
91 true
92 }
93
94 pub fn as_ptr(&self) -> *const u8 {
95 self.buffer.as_ptr()
96 }
97
98 pub fn num_packed_bytes(&self) -> usize {
99 self.cur_buffer_index
100 }
101
102 pub fn len(&self) -> usize {
103 self.cur_buffer_index
104 }
105
106 pub fn is_empty(&self) -> bool {
107 self.cur_buffer_index == 0
108 }
109
110 pub fn repack_last(&mut self, sfield_or_index: i32) -> bool {
111 self.cur_buffer_index -= 4;
112
113 let value_bytes: [u8; 4] = sfield_or_index.to_le_bytes();
114 self.buffer[self.cur_buffer_index..self.cur_buffer_index + 4].copy_from_slice(&value_bytes);
115 self.cur_buffer_index += 4;
116
117 true
118 }
119}