xrpl_common_stdlib/types/
blob.rs1use crate::fields::decoder::{FieldDecoder, FromCurrentTx, FromLedger};
2use crate::types::decode_error::DecodeError;
3
4pub const DEFAULT_BLOB_SIZE: usize = 1024;
6
7pub const CONDITION_BLOB_SIZE: usize = 128;
10
11pub const DOMAIN_BLOB_SIZE: usize = 256;
12
13pub const FULFILLMENT_BLOB_SIZE: usize = 256;
17
18pub const PUBLIC_KEY_BLOB_SIZE: usize = 33;
21
22pub const SIGNATURE_BLOB_SIZE: usize = 72;
27
28pub const URI_BLOB_SIZE: usize = 256;
30
31pub const WASM_BLOB_SIZE: usize = 4096;
34
35#[repr(C)]
60pub struct Blob<const N: usize> {
61 pub data: [u8; N],
62
63 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 #[inline]
101 pub const fn new() -> Self {
102 Self {
103 data: [0u8; N],
104 len: 0,
105 }
106 }
107
108 #[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 #[inline]
119 pub const fn len(&self) -> usize {
120 self.len
121 }
122
123 #[inline]
125 pub const fn is_empty(&self) -> bool {
126 self.len == 0
127 }
128
129 #[inline]
131 pub const fn capacity(&self) -> usize {
132 N
133 }
134
135 #[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
157pub type StandardBlob = Blob<DEFAULT_BLOB_SIZE>;
159
160pub type ConditionBlob = Blob<CONDITION_BLOB_SIZE>;
162
163pub type FulfillmentBlob = Blob<FULFILLMENT_BLOB_SIZE>;
165
166pub type SignatureBlob = Blob<SIGNATURE_BLOB_SIZE>;
168
169pub type UriBlob = Blob<URI_BLOB_SIZE>;
171
172pub type WasmBlob = Blob<WASM_BLOB_SIZE>;
174
175pub type PublicKeyBlob = Blob<PUBLIC_KEY_BLOB_SIZE>;
177
178pub type EmptyBlob = Blob<0>;
179
180pub const EMPTY_BLOB: EmptyBlob = Blob {
182 data: [0u8; 0],
183 len: 0usize,
184};
185
186impl<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 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 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; 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 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 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 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 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}