byte_array_ops/core/
model.rs

1//! This is the main model for our ByteArray Object
2
3use super::common::Utils;
4use crate::core::errors::ByteArrayError;
5use crate::core::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::core::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    pub fn fill_zeros(self) -> Self {
57        if self.bytes.capacity() == 0 {
58            self
59        } else {
60            ByteArray {
61                bytes: vec![0u8; self.bytes.capacity()],
62            }
63        }
64    }
65
66    /// fills the byte array with the given `value` to capacity
67    /// this is usually only useful in the rare case where an odd word padding needs to be set
68    /// in all other cases use [`ByteArray::init_value`].
69    ///
70    /// # Note
71    /// This function does nothing (noop) if the ByteArray capacity has not been set (is 0)
72    ///
73    /// # Example
74    /// ```
75    /// use byte_array_ops::core::model::ByteArray;
76    /// let arr = ByteArray::default().fill_zeros(); // does nothing on an array with zero capacity
77    /// assert!(arr.as_bytes().is_empty());
78    ///
79    /// let arr = ByteArray::with_capacity(5).fill_with(126);
80    /// assert_eq!(arr.as_bytes(),[126u8,126,126,126,126]);
81    ///
82    /// // or in the rare cases where a different odd word padding is set
83    ///
84    /// let arr = ByteArray::with_capacity(7)
85    ///                 .fill_with(5);
86    ///
87    /// assert_eq!(arr.as_bytes(),[5u8,5,5,5,5,5,5]);
88    ///
89    ///
90    /// ```
91    ///
92    ///
93    pub fn fill_with(self, value: u8) -> Self {
94        if self.bytes.capacity() == 0 {
95            self
96        } else {
97            ByteArray {
98                bytes: vec![value; self.bytes.capacity()],
99            }
100        }
101    }
102
103    /// Create a byte array from a hex string (without 0x prefix)
104    ///
105    /// # Example
106    /// ```
107    /// use byte_array_ops::ByteArray;
108    /// let arr = ByteArray::from_hex("deadbeef")?;
109    /// assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
110    /// # Ok::<(), byte_array_ops::errors::ByteArrayError>(())
111    /// ```
112    pub fn from_hex(hex_str: &str) -> Result<Self, ByteArrayError> {
113        if hex_str.is_empty() {
114            return Err(ByteArrayError::EmptyInput);
115        }
116
117        // Filter out underscores and collect
118        let bytes: Vec<u8> = hex_str.bytes().filter(|&b| b != b'_').collect();
119
120        // Validate characters
121        if let Some(&invalid) = bytes.iter().find(|&&b| !b.is_ascii_hexdigit()) {
122            return Err(InvalidHexChar(invalid as char));
123        }
124
125        let hex_count = bytes.len();
126        let byte_count = hex_count / 2 + hex_count % 2;
127        let mut ret = ByteArray::with_capacity(byte_count);
128
129        let mut start = 0;
130
131        // Handle odd length - process first char alone (LEFT padding for network byte order)
132        if hex_count % 2 == 1 {
133            ret.bytes
134                .push(Utils::hex_char_to_nibble_unchecked(bytes[0]));
135            start = 1;
136        }
137
138        // Process remaining pairs
139        for i in (start..hex_count).step_by(2) {
140            let byte_val = Utils::hex_char_to_nibble_unchecked(bytes[i]) << 4
141                | Utils::hex_char_to_nibble_unchecked(bytes[i + 1]);
142            ret.bytes.push(byte_val);
143        }
144
145        Ok(ret)
146    }
147
148    /// Create a byte array from a binary string (without 0b prefix)
149    ///
150    /// # Example
151    /// ```
152    /// use byte_array_ops::ByteArray;
153    /// let arr = ByteArray::from_bin("1010010")?;
154    /// assert_eq!(arr.as_bytes(), [0x52]);
155    /// # Ok::<(), byte_array_ops::errors::ByteArrayError>(())
156    /// ```
157    pub fn from_bin(bin_str: &str) -> Result<Self, ByteArrayError> {
158        if bin_str.is_empty() {
159            return Err(ByteArrayError::EmptyInput);
160        }
161
162        // Filter out underscores and collect
163        let bytes: Vec<u8> = bin_str.bytes().filter(|&b| b != b'_').collect();
164
165        // Validate characters
166        if let Some(&invalid) = bytes.iter().find(|&&b| b != b'0' && b != b'1') {
167            return Err(InvalidBinaryChar(invalid as char));
168        }
169
170        let bit_count = bytes.len();
171        let byte_count = bit_count.div_ceil(8);
172        let mut ret = ByteArray::with_capacity(byte_count);
173
174        let rem = bit_count % 8;
175
176        // Handle partial first byte (left padding) OR entire input if < 8 bits
177        let start = if rem != 0 {
178            let mut byte = 0u8;
179            #[allow(clippy::needless_range_loop)] // our version is more readable than clippy's
180            for i in 0..rem {
181                let bit_value = bytes[i] - b'0';
182                byte |= bit_value << (rem - 1 - i);
183            }
184            ret.bytes.push(byte);
185            rem
186        } else {
187            0
188        };
189
190        // Process remaining full bytes (only if there are any left)
191        for i in (start..bit_count).step_by(8) {
192            let mut byte = 0u8;
193
194            for j in 0..8 {
195                let bit_value = bytes[i + j] - b'0';
196                byte |= bit_value << (7 - j);
197            }
198
199            ret.bytes.push(byte);
200        }
201
202        Ok(ret)
203    }
204
205    /// initialize the array with a certain amount of zeros
206    /// internally this creates the byte array representation with `vec![0u8;count]`
207    /// the rest is default initialized
208    pub fn init_zeros(count: usize) -> Self {
209        ByteArray {
210            bytes: vec![0u8; count],
211        }
212    }
213
214    /// initialize the array with a certain amount of `value`
215    /// internally this creates the byte array representation with `vec![value;count]`
216    /// the rest is default initialized
217    pub fn init_value(value: u8, count: usize) -> Self {
218        ByteArray {
219            bytes: vec![value; count],
220        }
221    }
222
223    /// returns a slices to the interior bytes
224    ///
225    /// # NOTE
226    /// There is another method that provides a zero-cost move of the interior bytes using the
227    /// [`Vec::from::<ByteArray>`] implementation. Please check the [`ByteArray`]'s [`From<ByteArray>`] implementation
228    /// documentation
229    ///
230    /// # Example
231    /// ```
232    /// use byte_array_ops::core::model::ByteArray;
233    ///
234    /// let arr : ByteArray = "0xff2569".parse().unwrap();
235    ///
236    /// let slice = arr.as_bytes();
237    ///
238    /// assert_eq!(slice, [0xff,0x25,0x69]);
239    /// ```
240    ///
241    /// # Alternative (Zero-cost move)
242    /// ```
243    /// use byte_array_ops::core::model::ByteArray;
244    /// let arr : ByteArray = "0b11101111".parse().unwrap();
245    ///
246    /// assert_eq!(arr.as_bytes(), [0xef]);
247    ///
248    /// let moved_data : Vec<u8> = arr.into();
249    /// assert_eq!(moved_data, vec![0xef]);
250    /// ```
251    ///
252    pub fn as_bytes(&self) -> &[u8] {
253        &self.bytes
254    }
255
256    #[cfg(feature = "experimental")]
257    pub fn resize(&mut self, new_capacity: usize, value: u8) {
258        unimplemented!(
259            "this is insecure and must find a way on how to handle this in a more secure fashion"
260        );
261        self.bytes.resize(new_capacity, value);
262    }
263
264    /// Tries to append an element to the back of a collection. Fails
265    ///
266    /// # Panics
267    ///  if the new capacity exceeds [`isize::MAX`] bytes.
268    ///
269    /// # Examples
270    ///```
271    /// use byte_array_ops::core::model::ByteArray;
272    /// let mut arr = ByteArray::default()
273    ///
274    /// arr.try_push(0xab).unwrap();
275    ///
276    /// assert_eq!(arr.as_bytes(), [1, 2, 0xab]);
277    /// ```
278    #[cfg(feature = "experimental")]
279    pub fn try_push(&mut self, value: u8) {
280        self.bytes.push(value);
281    }
282}
283
284// index handling
285
286impl Index<usize> for ByteArray {
287    type Output = u8;
288
289    /// index accessor for ByteArray
290    /// # Panics
291    /// When out of bound use `ByteArray::get()` (in hardened mode) or `Vec::get()` (via Deref in insecure mode) for a checked getter
292    fn index(&self, index: usize) -> &Self::Output {
293        &self.bytes[index]
294    }
295}
296
297impl IndexMut<usize> for ByteArray {
298    /// Mutable index accessor for ByteArray
299    /// # Panics
300    /// When out of bound use `ByteArray::get()` (in hardened mode) or `Vec::get()` (via Deref in insecure mode) for a checked getter
301    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
302        &mut self.bytes[index]
303    }
304}
305
306impl AsRef<[u8]> for ByteArray {
307    fn as_ref(&self) -> &[u8] {
308        self.as_bytes()
309    }
310}
311
312impl AsMut<[u8]> for ByteArray {
313    fn as_mut(&mut self) -> &mut [u8] {
314        &mut self.bytes
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use alloc::vec;
322    use core::str::FromStr;
323
324    #[test]
325    fn test_hex_string_constructor() {
326        let b1: ByteArray = "0xfe81eabd5".parse().unwrap();
327
328        assert_eq!(b1.len(), 5);
329        assert_eq!(b1.bytes, vec![0x0f, 0xe8, 0x1e, 0xab, 0xd5]);
330    }
331
332    #[test]
333    fn test_ba_with_capacity() {
334        let arr = ByteArray::with_capacity(10);
335
336        assert_eq!(arr.bytes.capacity(), 10);
337        assert!(ByteArray::try_from(arr).is_ok());
338    }
339
340    #[test]
341    fn test_with_hex() {
342        let arr = ByteArray::from_hex("ffabc");
343
344        assert_eq!(arr.unwrap().bytes, vec![0x0f, 0xfa, 0xbc]);
345    }
346
347    #[test]
348    fn test_with_bin_less_than_8() {
349        let arr = ByteArray::from_bin("1101111").unwrap();
350        assert_eq!(arr.bytes, vec![0x6f]);
351    }
352
353    #[test]
354    fn test_with_bin_more_than_8() {
355        let arr = ByteArray::from_bin("110111010110111").unwrap();
356        assert_eq!(arr.bytes, vec![0x6e, 0xb7]);
357    }
358
359    #[test]
360    fn test_equality_derives() {
361        let arr: ByteArray = "0xffab12345ffaf".parse().unwrap();
362        let arr_2 = ByteArray::from_hex("ffab12345ffafdeadbeef").unwrap();
363        let arr_3 = arr.clone();
364
365        assert_ne!(arr.bytes, arr_2.bytes);
366        assert_eq!(arr.bytes, arr_3.bytes);
367    }
368
369    #[test]
370    fn test_init_zeros() {
371        let arr = ByteArray::init_zeros(8);
372
373        assert_eq!(arr.bytes, [0u8, 0, 0, 0, 0, 0, 0, 0]);
374    }
375
376    #[test]
377    fn test_init_value() {
378        let arr = ByteArray::init_value(254, 8);
379
380        assert_eq!(arr.bytes, [254, 254, 254, 254, 254, 254, 254, 254]);
381    }
382
383    #[test]
384    fn test_mutable_idx_accessor() {
385        let mut arr: ByteArray = "0xffab1245".parse().unwrap();
386
387        arr[3] = 0xbe;
388        arr[1] = 0xfa;
389
390        assert_eq!(arr.bytes, ByteArray::from_str("0xfffa12be").unwrap().bytes);
391    }
392
393    #[test]
394    fn test_fill_zeros() {
395        let arr = ByteArray::default().fill_zeros();
396        assert!(arr.as_bytes().is_empty());
397
398        let arr = ByteArray::with_capacity(5).fill_zeros();
399        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0]);
400
401        let arr = ByteArray::with_capacity(7).fill_zeros();
402
403        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0, 0, 0])
404    }
405
406    #[test]
407    fn test_fill_with() {
408        let arr = ByteArray::default().fill_zeros();
409        assert!(arr.as_bytes().is_empty());
410
411        let arr = ByteArray::with_capacity(5).fill_with(126);
412        assert_eq!(arr.as_bytes(), [126u8, 126, 126, 126, 126]);
413
414        let arr = ByteArray::with_capacity(7).fill_with(5);
415
416        assert_eq!(arr.as_bytes(), [5u8, 5, 5, 5, 5, 5, 5]);
417    }
418
419    #[test]
420    #[cfg(feature = "experimental")]
421    fn test_push_element() {
422        let mut arr: ByteArray = vec![1, 2].into();
423
424        arr.try_push(0xab);
425
426        assert_eq!(arr.as_bytes(), [1, 2, 0xab]);
427    }
428
429    #[test]
430    fn test_hex_with_underscores() {
431        let arr = ByteArray::from_hex("de_ad_be_ef").unwrap();
432        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
433    }
434
435    #[test]
436    fn test_bin_with_underscores() {
437        let arr = ByteArray::from_bin("1010_0101").unwrap();
438        assert_eq!(arr.as_bytes(), [0xa5]);
439    }
440
441    #[test]
442    fn test_bin_with_underscores_odd_length() {
443        let arr = ByteArray::from_bin("110_1111").unwrap();
444        assert_eq!(arr.as_bytes(), [0x6f]);
445    }
446
447    #[test]
448    #[should_panic]
449    fn test_as_mut_empty() {
450        let mut bytes = ByteArray::default();
451
452        let mut_ref = bytes.as_mut();
453
454        mut_ref[0] = 0x00;
455    }
456
457    #[test]
458    fn test_as_ref() {
459        let bytes: ByteArray = "0x01_02_03_04_05_06_07_08".parse().unwrap();
460        let bytes_ref = bytes.as_ref();
461
462        assert_eq!(bytes.as_bytes(), bytes_ref);
463    }
464
465    #[test]
466    fn test_as_mut() {
467        let mut bytes: ByteArray = "0x01_02_03_04_05_06_07_08".parse().unwrap();
468
469        let mut_ref = bytes.as_mut();
470
471        mut_ref[0] = 0xFF;
472        mut_ref[5] = 0xab;
473        mut_ref[7] = 0x1a;
474
475        assert_eq!(
476            bytes.as_bytes(),
477            [0xff, 0x02, 0x03, 0x04, 0x05, 0xab, 0x07, 0x1a]
478        )
479    }
480}