byte_array_ops/
model.rs

1//! This is the main model for our ByteArray Object
2
3use super::common::Utils;
4use crate::errors::ByteArrayError;
5use crate::errors::ByteArrayError::{InvalidBinaryChar, InvalidHexChar};
6use crate::errors::ByteArraySecurityError::DataRemnanceRisk;
7use alloc::vec;
8use alloc::vec::Vec;
9use core::ops::{Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo};
10
11/// Debug derive and Display is intentionally left out to avoid any intentional data leakages through formatters
12#[derive(Default, Clone)] // TODO analyze security side effects of debug
13pub struct ByteArray {
14    pub(crate) bytes: Vec<u8>,
15}
16
17impl ByteArray {
18    /// Standard constructor, use [`Self::with_capacity`] if you already know the capacity of your bytes for performance
19    /// reasons
20    pub fn new() -> Self {
21        ByteArray { bytes: vec![] }
22    }
23
24    /// reserves memory for a certain number of bytes for efficiency purposes
25    pub fn with_capacity(size_in_bytes: usize) -> Self {
26        Self {
27            bytes: Vec::with_capacity(size_in_bytes),
28        }
29    }
30
31    /// fills the byte array with zeros to capacity
32    /// this is usually only useful in the rare case where an odd word padding needs to be set
33    /// in all other cases use [`ByteArray::init_zeros`]. This function does nothing if the ByteArray
34    /// capacity has not been set (is 0)
35    ///
36    /// # Example
37    /// ```
38    /// use byte_array_ops::model::ByteArray;
39    /// let arr = ByteArray::default().fill_zeros(); // does nothing on an array with zero capacity
40    /// assert!(arr.as_bytes().is_empty());
41    ///
42    /// let arr = ByteArray::with_capacity(5).fill_zeros();
43    /// assert_eq!(arr.as_bytes(),[0u8,0,0,0,0]);
44    ///
45    /// // or in the rare cases where a different odd word padding is set
46    ///
47    /// let arr = ByteArray::with_capacity(7)
48    ///                 .fill_zeros();
49    ///
50    /// assert_eq!(arr.as_bytes(),[0u8,0,0,0,0,0,0])
51    ///
52    ///
53    /// ```
54    ///
55    ///
56    #[deprecated(
57        since = "0.3.3",
58        note = "This will be renamed to with_zeros at a future version"
59    )]
60    pub fn fill_zeros(self) -> Self {
61        if self.bytes.capacity() == 0 {
62            self
63        } else {
64            ByteArray {
65                bytes: vec![0u8; self.bytes.capacity()],
66            }
67        }
68    }
69
70    /// fills the byte array with the given `value` to capacity
71    /// this is usually only useful in the rare case where an odd word padding needs to be set
72    /// in all other cases use [`ByteArray::init_value`].
73    ///
74    /// # Note
75    /// This function does nothing (noop) if the ByteArray capacity has not been set (is 0)
76    ///
77    /// # Example
78    /// ```
79    /// use byte_array_ops::model::ByteArray;
80    /// let arr = ByteArray::default().fill_zeros(); // does nothing on an array with zero capacity
81    /// assert!(arr.as_bytes().is_empty());
82    ///
83    /// let arr = ByteArray::with_capacity(5).fill_with(126);
84    /// assert_eq!(arr.as_bytes(),[126u8,126,126,126,126]);
85    ///
86    /// // or in the rare cases where a different odd word padding is set
87    ///
88    /// let arr = ByteArray::with_capacity(7)
89    ///                 .fill_with(5);
90    ///
91    /// assert_eq!(arr.as_bytes(),[5u8,5,5,5,5,5,5]);
92    ///
93    ///
94    /// ```
95    ///
96    ///
97    #[deprecated(
98        since = "0.3.3",
99        note = "This will be renamed to with_value at a future version"
100    )]
101    pub fn fill_with(self, value: u8) -> Self {
102        if self.bytes.capacity() == 0 {
103            self
104        } else {
105            ByteArray {
106                bytes: vec![value; self.bytes.capacity()],
107            }
108        }
109    }
110
111    /// Create a byte array from a hex string (without 0x prefix)
112    ///
113    /// # Example
114    /// ```
115    /// use byte_array_ops::ByteArray;
116    /// let arr = ByteArray::from_hex("deadbeef")?;
117    /// assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
118    /// # Ok::<(), byte_array_ops::errors::ByteArrayError>(())
119    /// ```
120    pub fn from_hex(hex_str: &str) -> Result<Self, ByteArrayError> {
121        if hex_str.is_empty() {
122            return Err(ByteArrayError::EmptyInput);
123        }
124
125        // Filter out underscores and collect
126        let bytes: Vec<u8> = hex_str.bytes().filter(|&b| b != b'_').collect();
127
128        // Validate characters
129        if let Some(&invalid) = bytes.iter().find(|&&b| !b.is_ascii_hexdigit()) {
130            return Err(InvalidHexChar(invalid as char));
131        }
132
133        let hex_count = bytes.len();
134        let byte_count = hex_count / 2 + hex_count % 2;
135        let mut ret = ByteArray::with_capacity(byte_count);
136
137        let mut start = 0;
138
139        // Handle odd length - process first char alone (LEFT padding for network byte order)
140        if hex_count % 2 == 1 {
141            ret.bytes
142                .push(Utils::hex_char_to_nibble_unchecked(bytes[0]));
143            start = 1;
144        }
145
146        // Process remaining pairs
147        for i in (start..hex_count).step_by(2) {
148            let byte_val = Utils::hex_char_to_nibble_unchecked(bytes[i]) << 4
149                | Utils::hex_char_to_nibble_unchecked(bytes[i + 1]);
150            ret.bytes.push(byte_val);
151        }
152
153        Ok(ret)
154    }
155
156    /// Create a byte array from a binary string (without 0b prefix)
157    ///
158    /// # Example
159    /// ```
160    /// use byte_array_ops::ByteArray;
161    /// let arr = ByteArray::from_bin("1010010")?;
162    /// assert_eq!(arr.as_bytes(), [0x52]);
163    /// # Ok::<(), byte_array_ops::errors::ByteArrayError>(())
164    /// ```
165    pub fn from_bin(bin_str: &str) -> Result<Self, ByteArrayError> {
166        if bin_str.is_empty() {
167            return Err(ByteArrayError::EmptyInput);
168        }
169
170        // Filter out underscores and collect
171        let bytes: Vec<u8> = bin_str.bytes().filter(|&b| b != b'_').collect();
172
173        // Validate characters
174        if let Some(&invalid) = bytes.iter().find(|&&b| b != b'0' && b != b'1') {
175            return Err(InvalidBinaryChar(invalid as char));
176        }
177
178        let bit_count = bytes.len();
179        let byte_count = bit_count.div_ceil(8);
180        let mut ret = ByteArray::with_capacity(byte_count);
181
182        let rem = bit_count % 8;
183
184        // Handle partial first byte (left padding) OR entire input if < 8 bits
185        let start = if rem != 0 {
186            let mut byte = 0u8;
187            #[allow(clippy::needless_range_loop)] // our version is more readable than clippy's
188            for i in 0..rem {
189                let bit_value = bytes[i] - b'0';
190                byte |= bit_value << (rem - 1 - i);
191            }
192            ret.bytes.push(byte);
193            rem
194        } else {
195            0
196        };
197
198        // Process remaining full bytes (only if there are any left)
199        for i in (start..bit_count).step_by(8) {
200            let mut byte = 0u8;
201
202            for j in 0..8 {
203                let bit_value = bytes[i + j] - b'0';
204                byte |= bit_value << (7 - j);
205            }
206
207            ret.bytes.push(byte);
208        }
209
210        Ok(ret)
211    }
212
213    /// initialize the array with a certain amount of zeros
214    /// internally this creates the byte array representation with `vec![0u8;count]`
215    /// the rest is default initialized
216    pub fn init_zeros(count: usize) -> Self {
217        ByteArray {
218            bytes: vec![0u8; count],
219        }
220    }
221
222    /// initialize the array with a certain amount of `value`
223    /// internally this creates the byte array representation with `vec![value;count]`
224    /// the rest is default initialized
225    pub fn init_value(value: u8, count: usize) -> Self {
226        ByteArray {
227            bytes: vec![value; count],
228        }
229    }
230
231    /// returns a slices to the interior bytes
232    ///
233    /// # NOTE
234    /// There is another method that provides a zero-cost move of the interior bytes using the
235    /// [`Vec::from::<ByteArray>`] implementation. Please check the [`ByteArray`]'s [`From<ByteArray>`] implementation
236    /// documentation
237    ///
238    /// # Example
239    /// ```
240    /// use byte_array_ops::model::ByteArray;
241    ///
242    /// let arr : ByteArray = "0xff2569".parse().unwrap();
243    ///
244    /// let slice = arr.as_bytes();
245    ///
246    /// assert_eq!(slice, [0xff,0x25,0x69]);
247    /// ```
248    ///
249    pub fn as_bytes(&self) -> &[u8] {
250        &self.bytes
251    }
252
253    /// internal Utility to combine two bytearrays while ensuring secure reallocation
254    #[doc(hidden)]
255    #[inline(always)]
256    fn combine(
257        lhs: ByteArray,
258        rhs: ByteArray,
259        prealloc_cap: usize,
260    ) -> Result<Self, ByteArrayError> {
261        const CAP_SAFETY_EXTENSION: usize = 10;
262        let total_cap = prealloc_cap + CAP_SAFETY_EXTENSION;
263        let mut buf = Vec::<u8>::with_capacity(total_cap);
264
265        let addr_pre_extend = buf.as_ptr();
266        buf.extend_from_slice(lhs.as_bytes());
267        if addr_pre_extend != buf.as_ptr() {
268            return Err(DataRemnanceRisk.into());
269        }
270        buf.extend_from_slice(rhs.as_bytes());
271        if addr_pre_extend != buf.as_ptr() {
272            return Err(DataRemnanceRisk.into());
273        }
274
275        // drop old byte buffers before creating a new byte array to avoid leaking information in case the from method was interrupted
276        // Note: we implement zeroize on drop
277        drop(lhs);
278        drop(rhs);
279
280        Ok(ByteArray::from(buf))
281    }
282
283    /// Chained constructor helper for concatening ByteArrays together into one while ensuring to unintended reallocations happens
284    /// to avoid data remnance.
285    /// Preallocate the correct size with `ByteArray::with_capacity` then unintended reallocations might happen)
286    ///
287    /// # Capacity allocation caveat
288    /// this function adjusts the inner bytes capacity according to the sum of the lengths of the actual data of both ByteArrays,
289    /// if you want to preserve the capacity to be the sum of the total capacities of both arrays; then use [`ByteArray::with_extend_preserve_cap`]
290    ///
291    /// # Errors
292    ///
293    /// This function fails if a reallocation happens with [`crate::errors::ByteArraySecurityError::DataRemnanceRisk`]
294    ///
295    /// # Example
296    /// ```
297    /// use byte_array_ops::ByteArray;
298    ///
299    /// let arr1 = ByteArray::from_hex("dead")?;
300    /// let arr2 = ByteArray::from_hex("beef")?;
301    /// let result = arr1.try_extend(arr2)?;
302    ///
303    /// assert_eq!(result.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
304    /// # Ok::<(), byte_array_ops::errors::ByteArrayError>(())
305    /// ```
306    #[inline]
307    pub fn try_extend(self, other: ByteArray) -> Result<Self, ByteArrayError> {
308        let cap = self.bytes.len() + other.bytes.len();
309        Self::combine(self, other, cap)
310    }
311
312    /// Same as [`ByteArray::try_extend`] but instead of reserving actual lengths, this reserves the
313    /// sum of capacities of both bytearrays
314    ///
315    /// # Warning
316    /// For Bytearrays with very large capacities but whose actual length is very small this tends to be
317    /// very inefficient and might allocate too much memory on constrained environments.
318    /// For these cases use [`ByteArray::try_extend`] chained , this might be safer at the cost of more
319    /// secure reallocations
320    ///
321    /// # Example
322    /// ```
323    /// use byte_array_ops::ByteArray;
324    ///
325    /// let arr1 = ByteArray::with_capacity(100);
326    /// let arr2 = ByteArray::from_hex("ff")?;
327    /// let result = arr1.try_extend_with_preserve_cap(arr2)?;
328    ///
329    /// // Result has the data from arr2
330    /// assert_eq!(result.as_bytes(), [0xff]);
331    /// assert_eq!(result.len(), 1);
332    /// # Ok::<(), byte_array_ops::errors::ByteArrayError>(())
333    /// ```
334    #[inline]
335    pub fn try_extend_with_preserve_cap(self, other: ByteArray) -> Result<Self, ByteArrayError> {
336        let cap = self.bytes.capacity() + other.bytes.capacity();
337        Self::combine(self, other, cap)
338    }
339}
340
341// index handling
342
343impl Index<usize> for ByteArray {
344    type Output = u8;
345
346    /// index accessor for ByteArray
347    /// # Panics
348    /// When out of bounds. Use `ByteArray::get()` for a checked getter that returns `Option<&u8>`
349    fn index(&self, index: usize) -> &Self::Output {
350        &self.bytes[index]
351    }
352}
353
354impl IndexMut<usize> for ByteArray {
355    /// Mutable index accessor for ByteArray
356    /// # Panics
357    /// When out of bounds. Use `ByteArray::get()` for a checked getter that returns `Option<&u8>`
358    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
359        &mut self.bytes[index]
360    }
361}
362
363impl Index<Range<usize>> for ByteArray {
364    type Output = [u8];
365
366    fn index(&self, range: Range<usize>) -> &Self::Output {
367        &self.bytes[range]
368    }
369}
370
371impl Index<RangeFrom<usize>> for ByteArray {
372    type Output = [u8];
373
374    fn index(&self, range: RangeFrom<usize>) -> &Self::Output {
375        &self.bytes[range] // [2..]
376    }
377}
378impl Index<RangeTo<usize>> for ByteArray {
379    type Output = [u8];
380
381    fn index(&self, range: RangeTo<usize>) -> &Self::Output {
382        &self.bytes[range] // [..5]
383    }
384}
385impl Index<RangeInclusive<usize>> for ByteArray {
386    type Output = [u8];
387
388    fn index(&self, range: RangeInclusive<usize>) -> &Self::Output {
389        &self.bytes[range] // [2..=5]
390    }
391}
392impl Index<RangeFull> for ByteArray {
393    type Output = [u8];
394
395    fn index(&self, range: RangeFull) -> &Self::Output {
396        &self.bytes[range] // [..]
397    }
398}
399
400impl IndexMut<Range<usize>> for ByteArray {
401    fn index_mut(&mut self, range: Range<usize>) -> &mut Self::Output {
402        &mut self.bytes[range]
403    }
404}
405
406impl IndexMut<RangeFrom<usize>> for ByteArray {
407    fn index_mut(&mut self, range: RangeFrom<usize>) -> &mut Self::Output {
408        &mut self.bytes[range]
409    }
410}
411impl IndexMut<RangeTo<usize>> for ByteArray {
412    fn index_mut(&mut self, range: RangeTo<usize>) -> &mut Self::Output {
413        &mut self.bytes[range]
414    }
415}
416impl IndexMut<RangeInclusive<usize>> for ByteArray {
417    fn index_mut(&mut self, range: RangeInclusive<usize>) -> &mut Self::Output {
418        &mut self.bytes[range] // [2..=5]
419    }
420}
421impl IndexMut<RangeFull> for ByteArray {
422    fn index_mut(&mut self, range: RangeFull) -> &mut Self::Output {
423        &mut self.bytes[range] // [..]
424    }
425}
426
427impl AsRef<[u8]> for ByteArray {
428    fn as_ref(&self) -> &[u8] {
429        self.as_bytes()
430    }
431}
432
433impl AsMut<[u8]> for ByteArray {
434    fn as_mut(&mut self) -> &mut [u8] {
435        &mut self.bytes
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use crate::try_hex;
443    use alloc::vec;
444    use core::str::FromStr;
445
446    fn is_normal<T: Sized + Send + Sync + Unpin>() {}
447
448    #[test]
449    fn test_thread_safety_autotraits() {
450        is_normal::<ByteArray>();
451    }
452
453    #[test]
454    fn test_hex_string_constructor() {
455        let b1: ByteArray = "0xfe81eabd5".parse().unwrap();
456
457        assert_eq!(b1.len(), 5);
458        assert_eq!(b1.bytes, vec![0x0f, 0xe8, 0x1e, 0xab, 0xd5]);
459    }
460
461    #[test]
462    fn test_ba_with_capacity() {
463        let arr = ByteArray::with_capacity(10);
464
465        assert_eq!(arr.bytes.capacity(), 10);
466        assert!(ByteArray::try_from(arr).is_ok());
467    }
468
469    #[test]
470    fn test_with_hex() {
471        let arr = ByteArray::from_hex("ffabc");
472
473        assert_eq!(arr.unwrap().bytes, vec![0x0f, 0xfa, 0xbc]);
474    }
475
476    #[test]
477    fn test_with_bin_less_than_8() {
478        let arr = ByteArray::from_bin("1101111").unwrap();
479        assert_eq!(arr.bytes, vec![0x6f]);
480    }
481
482    #[test]
483    fn test_with_bin_more_than_8() {
484        let arr = ByteArray::from_bin("110111010110111").unwrap();
485        assert_eq!(arr.bytes, vec![0x6e, 0xb7]);
486    }
487
488    #[test]
489    fn test_equality_derives() {
490        let arr: ByteArray = "0xffab12345ffaf".parse().unwrap();
491        let arr_2 = ByteArray::from_hex("ffab12345ffafdeadbeef").unwrap();
492        let arr_3 = arr.clone();
493
494        assert_ne!(arr.bytes, arr_2.bytes);
495        assert_eq!(arr.bytes, arr_3.bytes);
496    }
497
498    #[test]
499    fn test_init_zeros() {
500        let arr = ByteArray::init_zeros(8);
501
502        assert_eq!(arr.bytes, [0u8, 0, 0, 0, 0, 0, 0, 0]);
503    }
504
505    #[test]
506    fn test_init_value() {
507        let arr = ByteArray::init_value(254, 8);
508
509        assert_eq!(arr.bytes, [254, 254, 254, 254, 254, 254, 254, 254]);
510    }
511
512    #[test]
513    fn test_mutable_idx_accessor() {
514        let mut arr: ByteArray = "0xffab1245".parse().unwrap();
515
516        arr[3] = 0xbe;
517        arr[1] = 0xfa;
518
519        assert_eq!(arr.bytes, ByteArray::from_str("0xfffa12be").unwrap().bytes);
520    }
521
522    #[test]
523    #[allow(deprecated)]
524    fn test_fill_zeros() {
525        let arr = ByteArray::default().fill_zeros();
526        assert!(arr.as_bytes().is_empty());
527
528        let arr = ByteArray::with_capacity(5).fill_zeros();
529        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0]);
530
531        let arr = ByteArray::with_capacity(7).fill_zeros();
532
533        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0, 0, 0])
534    }
535
536    #[test]
537    #[allow(deprecated)]
538    fn test_fill_with() {
539        let arr = ByteArray::default().fill_zeros();
540        assert!(arr.as_bytes().is_empty());
541
542        let arr = ByteArray::with_capacity(5).fill_with(126);
543        assert_eq!(arr.as_bytes(), [126u8, 126, 126, 126, 126]);
544
545        let arr = ByteArray::with_capacity(7).fill_with(5);
546
547        assert_eq!(arr.as_bytes(), [5u8, 5, 5, 5, 5, 5, 5]);
548    }
549
550    #[test]
551    fn test_hex_with_underscores() {
552        let arr = ByteArray::from_hex("de_ad_be_ef").unwrap();
553        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
554    }
555
556    #[test]
557    fn test_bin_with_underscores() {
558        let arr = ByteArray::from_bin("1010_0101").unwrap();
559        assert_eq!(arr.as_bytes(), [0xa5]);
560    }
561
562    #[test]
563    fn test_bin_with_underscores_odd_length() {
564        let arr = ByteArray::from_bin("110_1111").unwrap();
565        assert_eq!(arr.as_bytes(), [0x6f]);
566    }
567
568    #[test]
569    #[should_panic]
570    fn test_as_mut_empty() {
571        let mut bytes = ByteArray::default();
572
573        let mut_ref = bytes.as_mut();
574
575        mut_ref[0] = 0x00;
576    }
577
578    #[test]
579    fn test_ranges() {
580        let bytes = try_hex!("ff_aa_fa_b8_ca_12_15_5a_5c_6f").unwrap();
581
582        assert_eq!(bytes[1..2], [0xaa]);
583        assert_eq!(bytes[0..=2], [0xff, 0xaa, 0xfa]);
584        assert_eq!(bytes[6..], [0x15, 0x5a, 0x5c, 0x6f]);
585        assert_eq!(bytes[..3], [0xff, 0xaa, 0xfa]);
586        assert_eq!(
587            bytes[..],
588            [0xff, 0xaa, 0xfa, 0xb8, 0xca, 0x12, 0x15, 0x5a, 0x5c, 0x6f]
589        );
590    }
591
592    #[test]
593    fn test_ranges_mut() {
594        let mut bytes = try_hex!("ff_aa_fa_b8_ca_12_15_5a_5c_6f").unwrap();
595
596        let slice = &mut bytes[1..3];
597        slice[0] = 0x00;
598        slice[1] = 0x01;
599        let expected: ByteArray = "0xff_00_01_b8_ca_12_15_5a_5c_6f".parse().unwrap();
600
601        assert_eq!(bytes, expected);
602
603        let slice = &mut bytes[0..=2];
604        slice[0] = 0x12;
605        slice[1] = 0xab;
606        slice[2] = 0xbc;
607
608        let expected: ByteArray = "0x12_ab_bc_b8_ca_12_15_5a_5c_6f".parse().unwrap();
609
610        assert_eq!(bytes, expected);
611
612        let slice = &mut bytes[6..];
613        slice[0] = 0x01;
614        slice[1] = 0x02;
615        slice[2] = 0x03;
616        slice[3] = 0x04;
617
618        let expected: ByteArray = "0x12_ab_bc_b8_ca_12_01_02_03_04".parse().unwrap();
619
620        assert_eq!(bytes, expected);
621
622        let slice = &mut bytes[..3];
623        slice[0] = 0x00;
624        slice[1] = 0x02;
625        slice[2] = 0x03;
626
627        let expected: ByteArray = "0x00_02_03_b8_ca_12_01_02_03_04".parse().unwrap();
628
629        assert_eq!(bytes, expected);
630
631        let slice = &mut bytes[..];
632
633        slice.iter_mut().for_each(|e| *e = 0x01);
634
635        let expected: ByteArray = "0x01_01_01_01_01_01_01_01_01_01".parse().unwrap();
636
637        assert_eq!(bytes, expected);
638    }
639
640    #[test]
641    fn test_as_ref() {
642        let bytes: ByteArray = "0x01_02_03_04_05_06_07_08".parse().unwrap();
643        let bytes_ref = bytes.as_ref();
644
645        assert_eq!(bytes.as_bytes(), bytes_ref);
646    }
647
648    #[test]
649    fn test_as_mut() {
650        let mut bytes: ByteArray = "0x01_02_03_04_05_06_07_08".parse().unwrap();
651
652        let mut_ref = bytes.as_mut();
653
654        mut_ref[0] = 0xFF;
655        mut_ref[5] = 0xab;
656        mut_ref[7] = 0x1a;
657
658        assert_eq!(
659            bytes.as_bytes(),
660            [0xff, 0x02, 0x03, 0x04, 0x05, 0xab, 0x07, 0x1a]
661        )
662    }
663
664    // try_extend edge case tests
665
666    #[test]
667    fn test_try_extend_both_empty() {
668        let arr1 = ByteArray::default();
669        let arr2 = ByteArray::default();
670        let result = arr1.try_extend(arr2).unwrap();
671        assert_eq!(result.len(), 0);
672    }
673
674    #[test]
675    fn test_try_extend_first_empty() {
676        let arr1 = ByteArray::default();
677        let arr2 = ByteArray::from_hex("beef").unwrap();
678        let result = arr1.try_extend(arr2).unwrap();
679        assert_eq!(result.as_bytes(), [0xbe, 0xef]);
680    }
681
682    #[test]
683    fn test_try_extend_second_empty() {
684        let arr1 = ByteArray::from_hex("dead").unwrap();
685        let arr2 = ByteArray::default();
686        let result = arr1.try_extend(arr2).unwrap();
687        assert_eq!(result.as_bytes(), [0xde, 0xad]);
688    }
689
690    #[test]
691    fn test_try_extend_small_arrays() {
692        let arr1 = ByteArray::from_hex("aa").unwrap();
693        let arr2 = ByteArray::from_hex("bb").unwrap();
694        let result = arr1.try_extend(arr2).unwrap();
695        assert_eq!(result.as_bytes(), [0xaa, 0xbb]);
696        assert_eq!(result.len(), 2);
697    }
698
699    #[test]
700    fn test_try_extend_preserve_cap_empty_with_capacity() {
701        let arr1 = ByteArray::with_capacity(50); // capacity 50, length 0
702        let arr2 = ByteArray::with_capacity(30); // capacity 30, length 0
703        let result = arr1.try_extend_with_preserve_cap(arr2).unwrap();
704
705        assert_eq!(result.len(), 0);
706        assert_eq!(result.bytes.capacity(), 90); // 50 + 30 + 10 safety
707    }
708
709    #[test]
710    fn test_try_extend_capacity_based_on_length() {
711        let arr1 = ByteArray::from_hex("aa").unwrap(); // length = 1
712        let arr2 = ByteArray::from_hex("bb").unwrap(); // length = 1
713        let result = arr1.try_extend(arr2).unwrap();
714
715        // Capacity should be based on lengths: 1 + 1 + 10 = 12
716        assert_eq!(result.bytes.capacity(), 12);
717        assert_eq!(result.as_bytes(), [0xaa, 0xbb]);
718    }
719
720    #[test]
721    fn test_try_extend_chaining() {
722        let arr1 = ByteArray::from_hex("aa").unwrap();
723        let arr2 = ByteArray::from_hex("bb").unwrap();
724        let arr3 = ByteArray::from_hex("cc").unwrap();
725
726        let result = arr1.try_extend(arr2).unwrap().try_extend(arr3).unwrap();
727        assert_eq!(result.as_bytes(), [0xaa, 0xbb, 0xcc]);
728    }
729}