Skip to main content

xrpl_common_stdlib/fields/
decoder.rs

1//! Context-independent decode logic for typed field values.
2//!
3//! Reading a field always looks the same: call a host function into a buffer, then turn the
4//! bytes it wrote into a typed value. [`FieldDecoder`] captures only that second step, so a type
5//! implements it once regardless of how many contexts (current transaction, ledger object, ...)
6//! can produce those bytes. The marker traits below record which contexts are valid for a given
7//! type at compile time; the context-specific `get_field` functions (see
8//! [`crate::fields::current_tx`], [`crate::fields::ledger_obj`]) require the matching marker.
9
10use crate::host;
11use crate::types::decode_error::DecodeError;
12
13/// Decodes a typed value from the raw bytes a host function wrote.
14pub trait FieldDecoder: Sized {
15    /// The buffer a `get_field` caller allocates before invoking the host function. Each type
16    /// picks its own size (an associated type, not a `const`, so this stays on stable Rust).
17    // TODO: once `generic_const_exprs` stabilises (tracking issue rust-lang/rust#76560), replace
18    // this with `const SIZE: usize` and change the bound to `[u8; Self::SIZE]`, removing the
19    // need for `empty_buffer()` entirely.
20    type Buffer: AsMut<[u8]> + AsRef<[u8]>;
21
22    /// Returns a zero-initialized buffer of this type's `Buffer` size.
23    fn empty_buffer() -> Self::Buffer;
24
25    /// Decodes `Self` from the full `Buffer` (as written by the host, then zero-padded to the
26    /// buffer's size), given `bytes_written` — the number of bytes the host actually wrote.
27    ///
28    /// `bytes_written` carries the length that a `&[u8]` slice would otherwise bundle inside its
29    /// fat pointer — passing the whole buffer plus `bytes_written` lets fixed-layout types (e.g.
30    /// `Amount`) read the padded buffer in place, with no re-slice or re-copy.
31    ///
32    /// Most fixed-size types require the host to have written *exactly* `Buffer`'s length and can
33    /// then be built with a plain `From<Buffer>` — for those, implement this as
34    /// `decode_exact(*buf, bytes_written)` (see [`decode_exact`]). Types with different semantics
35    /// — e.g. `Amount`, where a shorter write is legitimate (XRP is 8 bytes, MPT 33, of a 48-byte
36    /// buffer) and the variant is determined by the leading flag bits rather than the byte count
37    /// — write a bespoke body instead.
38    fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError>;
39}
40
41/// Marker: this type can be read from the current transaction via [`crate::fields::current_tx`].
42pub trait FromCurrentTx: FieldDecoder {}
43
44/// Marker: this type can be read from a ledger object via [`crate::fields::ledger_obj`].
45pub trait FromLedger: FieldDecoder {}
46
47/// Shared step behind every `get_field`/`get_field_optional` in [`crate::fields::current_tx`]
48/// and [`crate::fields::ledger_obj`]: turn a host result code and the buffer it (partially)
49/// filled into a typed value.
50///
51/// Callers handle the "field not found" case themselves (only `get_field_optional` has one)
52/// before reaching here; `n` is assumed to be either a real byte count or a hard error.
53#[inline]
54pub(crate) fn decode_host_result<T: FieldDecoder>(buf: T::Buffer, n: i32) -> host::Result<T> {
55    if n < 0 {
56        return host::Result::Err(host::Error::from_code(n));
57    }
58    let n = n as usize;
59    if n > buf.as_ref().len() {
60        // A conformant host never reports writing more bytes than the buffer holds; a positive
61        // count past our buffer means it described memory outside the allowed region.
62        return host::Result::Err(host::Error::PointerOutOfBounds);
63    }
64    match T::decode(buf, n) {
65        Ok(value) => host::Result::Ok(value),
66        Err(_) => host::Result::Err(host::Error::InvalidDecoding),
67    }
68}
69
70/// Shared `FieldDecoder::decode` body for fixed-size types that require an exact-length write and
71/// build `Self` via `From<Buffer>` (`AccountID`, `TransactionType`, `UInt<N>`). Not a default
72/// trait method: a `where Self: From<Self::Buffer>` bound on a trait method applies to every
73/// implementor, including ones that override the body, so a type without that `From` impl (e.g.
74/// `Amount`, whose variant is decided by flag bits rather than length) couldn't compile at all.
75/// A plain function sidesteps that — callers opt in explicitly.
76#[inline]
77pub(crate) fn decode_exact<T, Buf>(buf: Buf, bytes_written: usize) -> Result<T, DecodeError>
78where
79    Buf: AsRef<[u8]>,
80    T: From<Buf>,
81{
82    if bytes_written != buf.as_ref().len() {
83        return Err(DecodeError);
84    }
85    Ok(T::from(buf))
86}
87
88impl FieldDecoder for u8 {
89    type Buffer = [u8; 1];
90
91    #[inline]
92    fn empty_buffer() -> Self::Buffer {
93        [0u8; 1]
94    }
95
96    #[inline]
97    fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
98        if bytes_written != buf.len() {
99            return Err(DecodeError);
100        }
101        Ok(u8::from_le_bytes(buf))
102    }
103}
104
105impl FromCurrentTx for u8 {}
106impl FromLedger for u8 {}
107
108impl FieldDecoder for u16 {
109    type Buffer = [u8; 2];
110
111    #[inline]
112    fn empty_buffer() -> Self::Buffer {
113        [0u8; 2]
114    }
115
116    #[inline]
117    fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
118        if bytes_written != buf.len() {
119            return Err(DecodeError);
120        }
121        Ok(u16::from_le_bytes(buf))
122    }
123}
124
125impl FromCurrentTx for u16 {}
126impl FromLedger for u16 {}
127
128impl FieldDecoder for u32 {
129    type Buffer = [u8; 4];
130
131    #[inline]
132    fn empty_buffer() -> Self::Buffer {
133        [0u8; 4]
134    }
135
136    #[inline]
137    fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
138        if bytes_written != buf.len() {
139            return Err(DecodeError);
140        }
141        Ok(u32::from_le_bytes(buf))
142    }
143}
144
145impl FromCurrentTx for u32 {}
146impl FromLedger for u32 {}
147
148impl FieldDecoder for u64 {
149    type Buffer = [u8; 8];
150
151    #[inline]
152    fn empty_buffer() -> Self::Buffer {
153        [0u8; 8]
154    }
155
156    #[inline]
157    fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
158        if bytes_written != buf.len() {
159            return Err(DecodeError);
160        }
161        Ok(u64::from_le_bytes(buf))
162    }
163}
164
165impl FromCurrentTx for u64 {}
166impl FromLedger for u64 {}
167
168impl FieldDecoder for i32 {
169    type Buffer = [u8; 4];
170
171    #[inline]
172    fn empty_buffer() -> Self::Buffer {
173        [0u8; 4]
174    }
175
176    #[inline]
177    fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
178        if bytes_written != buf.len() {
179            return Err(DecodeError);
180        }
181        Ok(i32::from_le_bytes(buf))
182    }
183}
184
185impl FromCurrentTx for i32 {}
186impl FromLedger for i32 {}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[derive(Debug, PartialEq, Eq)]
193    struct TxOnly(u8);
194
195    impl FieldDecoder for TxOnly {
196        type Buffer = [u8; 1];
197
198        fn empty_buffer() -> Self::Buffer {
199            [0u8; 1]
200        }
201
202        fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
203            if bytes_written == 0 {
204                return Err(DecodeError);
205            }
206            Ok(TxOnly(buf[0]))
207        }
208    }
209    impl FromCurrentTx for TxOnly {}
210
211    #[derive(Debug, PartialEq, Eq)]
212    struct ObjOnly(u8);
213
214    impl FieldDecoder for ObjOnly {
215        type Buffer = [u8; 1];
216
217        fn empty_buffer() -> Self::Buffer {
218            [0u8; 1]
219        }
220
221        fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
222            if bytes_written == 0 {
223                return Err(DecodeError);
224            }
225            Ok(ObjOnly(buf[0]))
226        }
227    }
228    impl FromLedger for ObjOnly {}
229
230    #[derive(Debug, PartialEq, Eq)]
231    struct TxAndObj(u8);
232
233    impl FieldDecoder for TxAndObj {
234        type Buffer = [u8; 1];
235
236        fn empty_buffer() -> Self::Buffer {
237            [0u8; 1]
238        }
239
240        fn decode(buf: Self::Buffer, bytes_written: usize) -> Result<Self, DecodeError> {
241            if bytes_written == 0 {
242                return Err(DecodeError);
243            }
244            Ok(TxAndObj(buf[0]))
245        }
246    }
247    impl FromCurrentTx for TxAndObj {}
248    impl FromLedger for TxAndObj {}
249
250    // These take no arguments and are never called; if a type didn't actually implement
251    // the trait, the crate would fail to compile. The negative direction (a type that
252    // implements only one marker being rejected by the other) is covered by the
253    // `tests/decoder_compile_fail.rs` trybuild cases.
254    fn assert_from_current_tx<T: FromCurrentTx>() {}
255    fn assert_from_ledger<T: FromLedger>() {}
256
257    #[test]
258    fn tx_only_implements_from_current_tx_only() {
259        assert_from_current_tx::<TxOnly>();
260    }
261
262    #[test]
263    fn obj_only_implements_from_ledger_only() {
264        assert_from_ledger::<ObjOnly>();
265    }
266
267    #[test]
268    fn tx_and_obj_implements_both() {
269        assert_from_current_tx::<TxAndObj>();
270        assert_from_ledger::<TxAndObj>();
271    }
272
273    #[test]
274    fn decode_returns_value_on_success() {
275        assert_eq!(TxOnly::decode([42], 1), Ok(TxOnly(42)));
276    }
277
278    #[test]
279    fn decode_returns_error_on_empty_input() {
280        assert_eq!(TxOnly::decode([0], 0), Err(DecodeError));
281    }
282
283    #[test]
284    fn empty_buffer_has_expected_length() {
285        let mut buffer = <TxOnly as FieldDecoder>::empty_buffer();
286        assert_eq!(buffer.as_mut().len(), 1);
287    }
288}