Skip to main content

xrpl_common_stdlib/types/
blob.rs

1use crate::fields::decoder::{FieldDecoder, FromCurrentTx, FromLedger};
2use crate::types::decode_error::DecodeError;
3
4/// Default blob size for general use (memos, etc.)
5pub const DEFAULT_BLOB_SIZE: usize = 1024;
6
7/// The maximum number of bytes in a Condition. Xrpld currently caps this value at 128 bytes
8/// (see `maxSerializedCondition` in xrpld source code), so we do the same here.
9pub const CONDITION_BLOB_SIZE: usize = 128;
10
11pub const DOMAIN_BLOB_SIZE: usize = 256;
12
13/// The maximum number of bytes in a Fulfillment. Theoretically, the crypto-condition format allows for much larger
14/// fulfillments, but xrpld currently caps this value at 256 bytes (see `maxSerializedFulfillment` in xrpld source
15/// code), so we do the same here.
16pub const FULFILLMENT_BLOB_SIZE: usize = 256;
17
18/// The number of bytes in a Public key. In XRPL, ed25519 public keys are prefixed with a one-byte prefix (i.e., `0xED`)
19/// to be consistent with secp256k1 public keys, which always have 33 bytes.
20pub const PUBLIC_KEY_BLOB_SIZE: usize = 33;
21
22/// Maximum size of a signature in bytes.
23///
24/// ECDSA signatures can be up to 72 bytes, which is the maximum signature size in XRPL.
25/// EdDSA signatures are always 64 bytes.
26pub const SIGNATURE_BLOB_SIZE: usize = 72;
27
28/// Maximum size of a URI in bytes (applies to DIDs, Oracles, Credentials, NFTs, etc.)
29pub const URI_BLOB_SIZE: usize = 256;
30
31/// Buffer size for WASM bytecode (Bytecode field)
32/// Set to 4KB to match the maximum allocation limit enforced by the host
33pub const WASM_BLOB_SIZE: usize = 4096;
34
35/// A variable-length binary data container with a fixed maximum size.
36///
37/// The `Blob` type is generic over its maximum capacity `N`, allowing you to
38/// create blobs of different sizes for different use cases. The actual data
39/// length is tracked separately in the `len` field.
40///
41/// # Type Parameters
42///
43/// * `N` - The maximum capacity of the blob in bytes
44///
45/// # Examples
46///
47/// ```
48/// use xrpl_common_stdlib::types::blob::{Blob, StandardBlob, UriBlob, DEFAULT_BLOB_SIZE};
49///
50/// // Create a standard 1024-byte blob
51/// let standard_blob: Blob<DEFAULT_BLOB_SIZE> = Blob::new();
52///
53/// // Create a standard 1024-byte blob
54/// let standard_blob_typed: StandardBlob = StandardBlob::new();
55///
56/// // Create a smaller 256-byte blob for URIs
57/// let uri_blob: UriBlob = UriBlob::new();
58/// ```
59#[repr(C)]
60pub struct Blob<const N: usize> {
61    pub data: [u8; N],
62
63    /// The actual length of this blob, if less than data.len()
64    pub len: usize,
65}
66
67impl<const N: usize> core::fmt::Debug for Blob<N> {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        f.debug_struct("Blob")
70            .field("data", &self.as_slice())
71            .field("len", &self.len)
72            .finish()
73    }
74}
75
76impl<const N: usize> PartialEq for Blob<N> {
77    fn eq(&self, other: &Self) -> bool {
78        self.as_slice() == other.as_slice()
79    }
80}
81
82impl<const N: usize> Eq for Blob<N> {}
83
84impl<const N: usize> Clone for Blob<N> {
85    fn clone(&self) -> Self {
86        Self::from_slice(self.as_slice())
87    }
88}
89
90impl<const N: usize> core::ops::Deref for Blob<N> {
91    type Target = [u8];
92
93    fn deref(&self) -> &Self::Target {
94        self.as_slice()
95    }
96}
97
98impl<const N: usize> Blob<N> {
99    /// Creates a new empty blob with the specified capacity.
100    #[inline]
101    pub const fn new() -> Self {
102        Self {
103            data: [0u8; N],
104            len: 0,
105        }
106    }
107
108    /// Creates a blob from a byte slice, copying up to N bytes.
109    #[inline]
110    pub fn from_slice(slice: &[u8]) -> Self {
111        let mut data = [0u8; N];
112        let len = slice.len().min(N);
113        data[..len].copy_from_slice(&slice[..len]);
114        Self { data, len }
115    }
116
117    /// Returns the actual length of the data in the blob.
118    #[inline]
119    pub const fn len(&self) -> usize {
120        self.len
121    }
122
123    /// Returns true if the blob contains no data.
124    #[inline]
125    pub const fn is_empty(&self) -> bool {
126        self.len == 0
127    }
128
129    /// Returns the maximum capacity of the blob.
130    #[inline]
131    pub const fn capacity(&self) -> usize {
132        N
133    }
134
135    /// Returns a slice of the actual data (not including unused capacity).
136    #[inline]
137    pub fn as_slice(&self) -> &[u8] {
138        &self.data[..self.len]
139    }
140}
141
142impl<const N: usize> From<[u8; N]> for Blob<N> {
143    fn from(bytes: [u8; N]) -> Self {
144        Self {
145            data: bytes,
146            len: N,
147        }
148    }
149}
150
151impl<const N: usize> Default for Blob<N> {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157/// Type alias for the standard 1024-byte blob.
158pub type StandardBlob = Blob<DEFAULT_BLOB_SIZE>;
159
160/// Type alias for 128-byte blob (for Condition fields)
161pub type ConditionBlob = Blob<CONDITION_BLOB_SIZE>;
162
163/// Type alias for 256-byte blob (for Fulfillment fields)
164pub type FulfillmentBlob = Blob<FULFILLMENT_BLOB_SIZE>;
165
166/// Type alias for 72-byte blob (for Signature fields).
167pub type SignatureBlob = Blob<SIGNATURE_BLOB_SIZE>;
168
169/// Type alias for 256-byte blob (applies to DIDs, Oracles, Credentials, NFTs, etc.)
170pub type UriBlob = Blob<URI_BLOB_SIZE>;
171
172/// Type alias for 4KB blob (for WASM bytecode)
173pub type WasmBlob = Blob<WASM_BLOB_SIZE>;
174
175/// Type alias for 33-byte blob (for Public Key fields)
176pub type PublicKeyBlob = Blob<PUBLIC_KEY_BLOB_SIZE>;
177
178pub type EmptyBlob = Blob<0>;
179
180/// Empty blob constant.
181pub const EMPTY_BLOB: EmptyBlob = Blob {
182    data: [0u8; 0],
183    len: 0usize,
184};
185
186/// `FieldDecoder` for any `Blob<N>`: copies whatever bytes the host wrote (at most `N`) into a
187/// `Blob<N>`, recording the actual length. Unlike fixed-size types, this never fails — blobs are
188/// variable-length by design.
189impl<const N: usize> FieldDecoder for Blob<N> {
190    type Buffer = [u8; N];
191
192    #[inline]
193    fn empty_buffer() -> Self::Buffer {
194        [0u8; N]
195    }
196
197    #[inline]
198    fn decode(buf: Self::Buffer, bytes_written: usize) -> core::result::Result<Self, DecodeError> {
199        Ok(Blob {
200            data: buf,
201            len: bytes_written,
202        })
203    }
204}
205
206impl<const N: usize> FromCurrentTx for Blob<N> {}
207impl<const N: usize> FromLedger for Blob<N> {}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_new_creates_empty_blob() {
215        let blob: Blob<32> = Blob::new();
216        assert_eq!(blob.len(), 0);
217        assert!(blob.is_empty());
218        assert_eq!(blob.capacity(), 32);
219        assert_eq!(blob.as_slice(), &[] as &[u8]);
220    }
221
222    #[test]
223    fn test_from_slice_with_exact_capacity() {
224        let data = [1, 2, 3, 4, 5];
225        let blob: Blob<5> = Blob::from_slice(&data);
226
227        assert_eq!(blob.len(), 5);
228        assert!(!blob.is_empty());
229        assert_eq!(blob.as_slice(), &[1, 2, 3, 4, 5]);
230    }
231
232    #[test]
233    fn test_from_slice_with_excess_capacity() {
234        let data = [1, 2, 3];
235        let blob: Blob<10> = Blob::from_slice(&data);
236
237        assert_eq!(blob.len(), 3);
238        assert_eq!(blob.capacity(), 10);
239        assert_eq!(blob.as_slice(), &[1, 2, 3]);
240        // Verify unused capacity is zeroed
241        assert_eq!(blob.data[3], 0);
242        assert_eq!(blob.data[9], 0);
243    }
244
245    #[test]
246    fn test_from_slice_truncates_when_too_large() {
247        let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
248        let blob: Blob<5> = Blob::from_slice(&data);
249
250        // Should only copy first 5 bytes
251        assert_eq!(blob.len(), 5);
252        assert_eq!(blob.as_slice(), &[1, 2, 3, 4, 5]);
253    }
254
255    #[test]
256    fn test_from_slice_with_empty_slice() {
257        let data: &[u8] = &[];
258        let blob: Blob<10> = Blob::from_slice(data);
259
260        assert_eq!(blob.len(), 0);
261        assert!(blob.is_empty());
262        assert_eq!(blob.as_slice(), &[] as &[u8]);
263    }
264
265    #[test]
266    fn test_from_array_sets_full_length() {
267        let data = [42u8; 8];
268        let blob: Blob<8> = Blob::from(data);
269
270        assert_eq!(blob.len(), 8);
271        assert_eq!(blob.capacity(), 8);
272        assert_eq!(blob.as_slice(), &[42, 42, 42, 42, 42, 42, 42, 42]);
273    }
274
275    #[test]
276    fn test_default_creates_empty_blob() {
277        let blob: Blob<16> = Blob::default();
278
279        assert_eq!(blob.len(), 0);
280        assert!(blob.is_empty());
281        assert_eq!(blob.capacity(), 16);
282    }
283
284    #[test]
285    fn test_as_slice_respects_length() {
286        let mut blob: Blob<10> = Blob::new();
287        blob.data[0] = 1;
288        blob.data[1] = 2;
289        blob.data[2] = 3;
290        blob.len = 2; // Only first 2 bytes are "valid"
291
292        assert_eq!(blob.as_slice(), &[1, 2]);
293        assert_ne!(blob.as_slice(), &[1, 2, 3]);
294    }
295
296    #[test]
297    fn test_equality_compares_data_and_len() {
298        let blob1: Blob<5> = Blob::from_slice(&[1, 2, 3]);
299        let blob2: Blob<5> = Blob::from_slice(&[1, 2, 3]);
300        let blob3: Blob<5> = Blob::from_slice(&[1, 2, 3, 4]);
301
302        assert_eq!(blob1, blob2);
303        assert_ne!(blob1, blob3);
304    }
305
306    #[test]
307    fn test_equality_ignores_bytes_beyond_len() {
308        // Same len, same logical content, but differing bytes past `len` in the backing array.
309        let blob1: Blob<5> = Blob {
310            data: [1, 2, 3, 0, 0],
311            len: 3,
312        };
313        let blob2: Blob<5> = Blob {
314            data: [1, 2, 3, 0xAA, 0xBB],
315            len: 3,
316        };
317
318        assert_eq!(blob1, blob2);
319    }
320
321    #[test]
322    fn test_clone_does_not_carry_over_bytes_beyond_len() {
323        let original: Blob<5> = Blob {
324            data: [1, 2, 0xFF, 0xFF, 0xFF],
325            len: 2,
326        };
327
328        let cloned = original.clone();
329
330        assert_eq!(cloned, original);
331        assert_eq!(cloned.data, [1, 2, 0, 0, 0]);
332    }
333
334    #[test]
335    fn test_debug_output_excludes_bytes_beyond_len() {
336        let blob: Blob<5> = Blob {
337            data: [1, 2, 0xFF, 0xFF, 0xFF],
338            len: 2,
339        };
340
341        let debug_str = format!("{:?}", blob);
342        assert!(!debug_str.contains("255"));
343    }
344
345    #[test]
346    fn test_deref_returns_slice_up_to_len() {
347        let blob: Blob<5> = Blob::from_slice(&[9, 8, 7]);
348        let slice: &[u8] = &blob;
349
350        assert_eq!(slice, &[9, 8, 7]);
351    }
352
353    #[test]
354    fn test_standard_blob_type_alias() {
355        let blob: StandardBlob = StandardBlob::new();
356        assert_eq!(blob.capacity(), DEFAULT_BLOB_SIZE);
357        assert_eq!(blob.capacity(), 1024);
358    }
359
360    #[test]
361    fn test_condition_blob_type_alias() {
362        let blob: ConditionBlob = ConditionBlob::new();
363        assert_eq!(blob.capacity(), CONDITION_BLOB_SIZE);
364        assert_eq!(blob.capacity(), 128);
365    }
366
367    #[test]
368    fn test_fulfillment_blob_type_alias() {
369        let blob: FulfillmentBlob = FulfillmentBlob::new();
370        assert_eq!(blob.capacity(), FULFILLMENT_BLOB_SIZE);
371        assert_eq!(blob.capacity(), 256);
372    }
373
374    #[test]
375    fn test_signature_blob_type_alias() {
376        let blob: SignatureBlob = SignatureBlob::new();
377        assert_eq!(blob.capacity(), SIGNATURE_BLOB_SIZE);
378        assert_eq!(blob.capacity(), 72);
379    }
380
381    #[test]
382    fn test_uri_blob_type_alias() {
383        let blob: UriBlob = UriBlob::new();
384        assert_eq!(blob.capacity(), URI_BLOB_SIZE);
385        assert_eq!(blob.capacity(), 256);
386    }
387
388    #[test]
389    fn test_empty_blob_constant() {
390        assert_eq!(EMPTY_BLOB.len(), 0);
391        assert_eq!(EMPTY_BLOB.capacity(), 0);
392        assert!(EMPTY_BLOB.is_empty());
393        assert_eq!(EMPTY_BLOB.as_slice(), &[] as &[u8]);
394    }
395
396    #[test]
397    fn test_zero_capacity_blob() {
398        let blob: Blob<0> = Blob::new();
399        assert_eq!(blob.capacity(), 0);
400        assert_eq!(blob.len(), 0);
401        assert!(blob.is_empty());
402    }
403
404    #[test]
405    fn test_from_slice_with_zero_capacity_truncates_all() {
406        let data = [1, 2, 3];
407        let blob: Blob<0> = Blob::from_slice(&data);
408
409        assert_eq!(blob.len(), 0);
410        assert_eq!(blob.as_slice(), &[] as &[u8]);
411    }
412
413    #[test]
414    fn test_legacy_signature_blob_size() {
415        // This test verifies that a 32-byte blob works (the old SIGNATURE_BLOB_SIZE value)
416        // Note: The actual signature type now uses 72 bytes (see signature module)
417        let blob: Blob<32> = Blob::new();
418        assert_eq!(blob.capacity(), 32);
419    }
420
421    #[test]
422    fn test_large_blob_from_slice() {
423        let data = [42u8; 2048];
424        let blob: Blob<1024> = Blob::from_slice(&data);
425
426        // Should truncate to capacity
427        assert_eq!(blob.len(), 1024);
428        assert_eq!(blob.as_slice().len(), 1024);
429        assert!(blob.as_slice().iter().all(|&b| b == 42));
430    }
431
432    #[test]
433    fn test_blob_with_binary_data() {
434        let data = [0xFF, 0x00, 0xAB, 0xCD, 0xEF];
435        let blob: Blob<10> = Blob::from_slice(&data);
436
437        assert_eq!(blob.len(), 5);
438        assert_eq!(blob.as_slice(), &[0xFF, 0x00, 0xAB, 0xCD, 0xEF]);
439    }
440
441    #[test]
442    fn test_capacity_is_const() {
443        let blob1: Blob<10> = Blob::new();
444        let blob2: Blob<10> = Blob::from_slice(&[1, 2, 3, 4, 5]);
445
446        // Capacity should always be N regardless of actual data
447        assert_eq!(blob1.capacity(), 10);
448        assert_eq!(blob2.capacity(), 10);
449    }
450
451    #[test]
452    fn test_wasm_blob_type_alias() {
453        let blob: WasmBlob = WasmBlob::new();
454        assert_eq!(blob.capacity(), WASM_BLOB_SIZE);
455        assert_eq!(blob.capacity(), 4096);
456    }
457}