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}