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 alloc::vec;
7use alloc::vec::Vec;
8use core::cmp::PartialEq;
9use core::ops::{Index, IndexMut};
10
11/// Debug derive and Display is intentionally left out to avoid any intentional data leakages through formatters
12#[derive(Default, PartialEq, Eq, 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    #[cfg(feature = "experimental")]
254    pub fn resize(&mut self, new_capacity: usize, value: u8) {
255        unimplemented!(
256            "this is insecure and must find a way on how to handle this in a more secure fashion"
257        );
258        self.bytes.resize(new_capacity, value);
259    }
260
261    /// Tries to append an element to the back of a collection. Fails
262    ///
263    /// # Panics
264    ///  if the new capacity exceeds [`isize::MAX`] bytes.
265    ///
266    /// # Examples
267    ///```
268    /// use byte_array_ops::model::ByteArray;
269    /// let mut arr = ByteArray::default()
270    ///
271    /// arr.try_push(0xab).unwrap();
272    ///
273    /// assert_eq!(arr.as_bytes(), [1, 2, 0xab]);
274    /// ```
275    #[cfg(feature = "experimental")]
276    pub fn try_push(&mut self, value: u8) {
277        self.bytes.push(value);
278    }
279}
280
281// index handling
282
283impl Index<usize> for ByteArray {
284    type Output = u8;
285
286    /// index accessor for ByteArray
287    /// # Panics
288    /// When out of bounds. Use `ByteArray::get()` for a checked getter that returns `Option<&u8>`
289    fn index(&self, index: usize) -> &Self::Output {
290        &self.bytes[index]
291    }
292}
293
294impl IndexMut<usize> for ByteArray {
295    /// Mutable index accessor for ByteArray
296    /// # Panics
297    /// When out of bounds. Use `ByteArray::get()` for a checked getter that returns `Option<&u8>`
298    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
299        &mut self.bytes[index]
300    }
301}
302
303impl AsRef<[u8]> for ByteArray {
304    fn as_ref(&self) -> &[u8] {
305        self.as_bytes()
306    }
307}
308
309impl AsMut<[u8]> for ByteArray {
310    fn as_mut(&mut self) -> &mut [u8] {
311        &mut self.bytes
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318    use alloc::vec;
319    use core::str::FromStr;
320
321    fn is_normal<T: Sized + Send + Sync + Unpin>() {}
322
323    #[test]
324    fn test_thread_safety_autotraits() {
325        is_normal::<ByteArray>();
326    }
327
328    #[test]
329    fn test_hex_string_constructor() {
330        let b1: ByteArray = "0xfe81eabd5".parse().unwrap();
331
332        assert_eq!(b1.len(), 5);
333        assert_eq!(b1.bytes, vec![0x0f, 0xe8, 0x1e, 0xab, 0xd5]);
334    }
335
336    #[test]
337    fn test_ba_with_capacity() {
338        let arr = ByteArray::with_capacity(10);
339
340        assert_eq!(arr.bytes.capacity(), 10);
341        assert!(ByteArray::try_from(arr).is_ok());
342    }
343
344    #[test]
345    fn test_with_hex() {
346        let arr = ByteArray::from_hex("ffabc");
347
348        assert_eq!(arr.unwrap().bytes, vec![0x0f, 0xfa, 0xbc]);
349    }
350
351    #[test]
352    fn test_with_bin_less_than_8() {
353        let arr = ByteArray::from_bin("1101111").unwrap();
354        assert_eq!(arr.bytes, vec![0x6f]);
355    }
356
357    #[test]
358    fn test_with_bin_more_than_8() {
359        let arr = ByteArray::from_bin("110111010110111").unwrap();
360        assert_eq!(arr.bytes, vec![0x6e, 0xb7]);
361    }
362
363    #[test]
364    fn test_equality_derives() {
365        let arr: ByteArray = "0xffab12345ffaf".parse().unwrap();
366        let arr_2 = ByteArray::from_hex("ffab12345ffafdeadbeef").unwrap();
367        let arr_3 = arr.clone();
368
369        assert_ne!(arr.bytes, arr_2.bytes);
370        assert_eq!(arr.bytes, arr_3.bytes);
371    }
372
373    #[test]
374    fn test_init_zeros() {
375        let arr = ByteArray::init_zeros(8);
376
377        assert_eq!(arr.bytes, [0u8, 0, 0, 0, 0, 0, 0, 0]);
378    }
379
380    #[test]
381    fn test_init_value() {
382        let arr = ByteArray::init_value(254, 8);
383
384        assert_eq!(arr.bytes, [254, 254, 254, 254, 254, 254, 254, 254]);
385    }
386
387    #[test]
388    fn test_mutable_idx_accessor() {
389        let mut arr: ByteArray = "0xffab1245".parse().unwrap();
390
391        arr[3] = 0xbe;
392        arr[1] = 0xfa;
393
394        assert_eq!(arr.bytes, ByteArray::from_str("0xfffa12be").unwrap().bytes);
395    }
396
397    #[test]
398    #[allow(deprecated)]
399    fn test_fill_zeros() {
400        let arr = ByteArray::default().fill_zeros();
401        assert!(arr.as_bytes().is_empty());
402
403        let arr = ByteArray::with_capacity(5).fill_zeros();
404        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0]);
405
406        let arr = ByteArray::with_capacity(7).fill_zeros();
407
408        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0, 0, 0])
409    }
410
411    #[test]
412    #[allow(deprecated)]
413    fn test_fill_with() {
414        let arr = ByteArray::default().fill_zeros();
415        assert!(arr.as_bytes().is_empty());
416
417        let arr = ByteArray::with_capacity(5).fill_with(126);
418        assert_eq!(arr.as_bytes(), [126u8, 126, 126, 126, 126]);
419
420        let arr = ByteArray::with_capacity(7).fill_with(5);
421
422        assert_eq!(arr.as_bytes(), [5u8, 5, 5, 5, 5, 5, 5]);
423    }
424
425    #[test]
426    #[cfg(feature = "experimental")]
427    fn test_push_element() {
428        let mut arr: ByteArray = vec![1, 2].into();
429
430        arr.try_push(0xab);
431
432        assert_eq!(arr.as_bytes(), [1, 2, 0xab]);
433    }
434
435    #[test]
436    fn test_hex_with_underscores() {
437        let arr = ByteArray::from_hex("de_ad_be_ef").unwrap();
438        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
439    }
440
441    #[test]
442    fn test_bin_with_underscores() {
443        let arr = ByteArray::from_bin("1010_0101").unwrap();
444        assert_eq!(arr.as_bytes(), [0xa5]);
445    }
446
447    #[test]
448    fn test_bin_with_underscores_odd_length() {
449        let arr = ByteArray::from_bin("110_1111").unwrap();
450        assert_eq!(arr.as_bytes(), [0x6f]);
451    }
452
453    #[test]
454    #[should_panic]
455    fn test_as_mut_empty() {
456        let mut bytes = ByteArray::default();
457
458        let mut_ref = bytes.as_mut();
459
460        mut_ref[0] = 0x00;
461    }
462
463    #[test]
464    fn test_as_ref() {
465        let bytes: ByteArray = "0x01_02_03_04_05_06_07_08".parse().unwrap();
466        let bytes_ref = bytes.as_ref();
467
468        assert_eq!(bytes.as_bytes(), bytes_ref);
469    }
470
471    #[test]
472    fn test_as_mut() {
473        let mut bytes: ByteArray = "0x01_02_03_04_05_06_07_08".parse().unwrap();
474
475        let mut_ref = bytes.as_mut();
476
477        mut_ref[0] = 0xFF;
478        mut_ref[5] = 0xab;
479        mut_ref[7] = 0x1a;
480
481        assert_eq!(
482            bytes.as_bytes(),
483            [0xff, 0x02, 0x03, 0x04, 0x05, 0xab, 0x07, 0x1a]
484        )
485    }
486}