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