byte_array_ops/byte_array/
model.rs

1//! This is the main model for our ByteArray Object
2
3use super::common::Utils;
4use crate::byte_array::errors::ByteArrayError;
5use crate::byte_array::errors::ByteArrayError::{InvalidBinaryChar, InvalidHexChar};
6use alloc::vec;
7use alloc::vec::Vec;
8use core::cmp::PartialEq;
9use core::ops::{Index, IndexMut};
10
11/// Enum controlling Padding behavior for odd byte arrays use by [`ByteArray`]
12///
13/// # Warning
14/// This is *NOT* padding for the entire array meaning this does *NOT* how to fill the array to capacity with zeros
15/// what this does is in case of odd byte arrays passed to the [`ByteArray`] constructor the first or last 8-bit word is padded
16/// to the left or to the right accordingly
17///     - Example left padding (default): 0xfeab123 becomes 0x0feab123
18///     - Example right padding: 0xfeab123 becomes 0xfeab1230
19///
20#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] // there is no risk of exposure here as this enum does not hold any sensitive data
21pub enum ByteArrayOddWordPad {
22    #[default]
23    LeftPadding,
24    #[cfg(feature = "experimental")]
25    RightPadding,
26}
27
28/// marker struct for uninitialized byte arrays that still require padding information for odd arrays
29/// from user. If you wish for a default constructor please check [`ByteArray::default`]. Please note
30/// that this struct represents an intermediate state and is not to be used by the client directly
31#[derive(Default)]
32pub struct UninitByteArray {
33    /// capacity of the to be create array for efficiency purposes
34    /// if this is [`None`] then no pre-allocation takes place
35    capacity: Option<usize>,
36}
37
38impl UninitByteArray {
39    /// pre initializes the capacity for more efficient pre-allocation
40    /// this returns an [Uninitialized Byte Array][`UninitByteArray`] and is used in
41    /// cases where the user wants to override the padding direction with [`UninitByteArray::with_odd_pad_dir`]
42    /// or [`UninitByteArray::create_with_odd_rpad`] for example
43    ///
44    /// # Note
45    /// in cases where the [Default odd array padding][`BytePadding`] is sufficient and more efficient
46    /// please use [`ByteArray::with_capacity`] instead
47    ///
48    /// # Example
49    /// ```
50    /// use byte_array_ops::byte_array::model::{ByteArray, ByteArrayOddWordPad};
51    ///
52    /// let arr = ByteArray::new_uninit()
53    ///         .with_capacity(20)
54    ///         .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
55    ///         .with_hex("efab123").expect("failed to convert hex");
56    ///
57    /// // In cases where the default padding is sufficient please use the direct constructor instead
58    /// let arr_2 = ByteArray::with_capacity(20)
59    ///         .with_hex("efab123").expect("failed to convert");
60    ///
61    /// assert_eq!(arr,arr_2);
62    /// assert_eq!(arr_2.as_bytes(),[0x0e,0xfa,0xb1,0x23]);
63    /// ```
64    pub fn with_capacity(self, byte_size: usize) -> Self {
65        Self {
66            capacity: Some(byte_size),
67        }
68    }
69    ///
70    /// Overrides the default odd array padding to use the less common
71    /// Right padding instead of the default padding
72    ///
73    /// # Warning
74    /// Please read documentation of [`ByteArrayOddWordPad`] for an understanding of what padding
75    /// means in this context
76    ///
77    /// # Examples
78    /// ```
79    /// use byte_array_ops::byte_array::model::ByteArray;
80    ///
81    /// let array = ByteArray::new()
82    ///     .create_with_odd_rpad();
83    ///
84    /// unimplemented!() // ADD assert
85    /// ```
86    #[cfg(feature = "experimental")]
87    pub fn create_with_odd_rpad(self) -> ByteArray {
88        unimplemented!("Currently only left padding is implemented");
89        /*        ByteArray {
90            bytes: self.capacity.map(Vec::with_capacity).unwrap_or_else(Vec::new),
91            byte_padding: BytePadding::RightPadding
92        }*/
93    }
94
95    /// Explicitly sets the padding direction. This is not needed in general
96    /// and is only provided in cases where padding must be explicitly set for
97    /// readability purposes. By default, [`ByteArray`] uses [Left Padding][`BytePadding::LeftPadding`]
98    /// and [`ByteArray::make_rpad`] is used to override and set to right padding.
99    ///
100    /// # Warning
101    /// Please read documentation of [`ByteArrayOddWordPad`] for an understanding of what padding
102    /// means in this context
103    ///
104    /// # Example
105    /// ```
106    /// use byte_array_ops::byte_array::model::{ByteArray, ByteArrayOddWordPad};
107    /// let arr = ByteArray::new_uninit()
108    ///         .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
109    ///         .with_hex("feab123").expect("failed to convert");
110    ///
111    /// assert_eq!(arr.as_bytes(),[0x0f,0xea,0xb1,0x23]);
112    /// ```
113    pub fn with_odd_pad_dir(self, pad_dir: ByteArrayOddWordPad) -> ByteArray {
114        ByteArray {
115            bytes: self.capacity.map(Vec::with_capacity).unwrap_or_default(),
116            byte_padding: pad_dir,
117        }
118    }
119}
120
121/// Debug derive and Display is intentionally left out to avoid any intentional data leakages through formatters
122#[derive(Default, PartialEq, Eq, Clone)] // TODO analyze security side effects of debug
123pub struct ByteArray {
124    pub(crate) bytes: Vec<u8>,
125    /// Defines how we pad if we have an od number of bytes should we pad left or right ? by default we use left
126    /// padding which is common in Cryptographic and Network Byte order
127    pub(super) byte_padding: ByteArrayOddWordPad,
128}
129
130impl ByteArray {
131    /// This constructor is usually needed when a custom odd array padding direction must be set.
132    /// creates a new empty byte array without reserving memory. this might not be the
133    /// most efficient option. For large byte arrays use [`ByteArray::with_capacity`] instead
134    ///
135    /// # Returns
136    ///
137    /// [`UninitByteArray`]
138    ///
139    /// # Note
140    ///
141    /// Use [`ByteArray::default`] or [`ByteArray::with_capacity`] to get a [`ByteArray`] without
142    /// intermediate structs if you do not require setting the odd word padding
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use core::str::FromStr;
148    ///
149    /// use byte_array_ops::byte_array::model::{ByteArray, ByteArrayOddWordPad};
150    ///
151    /// let arr = ByteArray::new_uninit()
152    ///         .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
153    ///         .with_hex("ffef48a").expect("failed to convert");
154    ///
155    /// assert_eq!(arr.as_bytes(),[0x0f,0xfe,0xf4,0x8a]);
156    /// ```
157    pub fn new_uninit() -> UninitByteArray {
158        UninitByteArray { capacity: None }
159    }
160
161    /// reserves memory for a certain number of bytes for efficiency purposes
162    /// uses the default [Byte padding direction][`BytePadding`]
163    pub fn with_capacity(size_in_bytes: usize) -> Self {
164        Self {
165            bytes: Vec::with_capacity(size_in_bytes),
166            byte_padding: ByteArrayOddWordPad::default(),
167        }
168    }
169
170    /// fills the byte array with zeros to capacity
171    /// this is usually only useful in the rare case where an odd word padding needs to be set
172    /// in all other cases use [`ByteArray::init_zeros`]. This function does nothing if the ByteArray
173    /// capacity has not been set (is 0)
174    ///
175    /// # Example
176    /// ```
177    /// use byte_array_ops::byte_array::model::{ByteArray, ByteArrayOddWordPad};
178    /// let arr = ByteArray::default().fill_zeros(); // does nothing on an array with zero capacity
179    /// assert!(arr.as_bytes().is_empty());
180    ///
181    /// let arr = ByteArray::with_capacity(5).fill_zeros();
182    /// assert_eq!(arr.as_bytes(),[0u8,0,0,0,0]);
183    ///
184    /// // or in the rare cases where a different odd word padding is set
185    ///
186    /// let arr = ByteArray::new_uninit()
187    ///                 .with_capacity(7)
188    ///                 .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
189    ///                 .fill_zeros();
190    ///
191    /// assert_eq!(arr.as_bytes(),[0u8,0,0,0,0,0,0])
192    ///
193    ///
194    /// ```
195    ///
196    ///
197    pub fn fill_zeros(self) -> Self {
198        if self.bytes.capacity() == 0 {
199            self
200        } else {
201            ByteArray {
202                bytes: vec![0u8; self.bytes.capacity()],
203                byte_padding: self.byte_padding,
204            }
205        }
206    }
207
208    /// fills the byte array with the given `value` to capacity
209    /// this is usually only useful in the rare case where an odd word padding needs to be set
210    /// in all other cases use [`ByteArray::init_value`].
211    ///
212    /// # Note
213    /// This function does nothing (noop) if the ByteArray capacity has not been set (is 0)
214    ///
215    /// # Example
216    /// ```
217    /// use byte_array_ops::byte_array::model::{ByteArray, ByteArrayOddWordPad};
218    /// let arr = ByteArray::default().fill_zeros(); // does nothing on an array with zero capacity
219    /// assert!(arr.as_bytes().is_empty());
220    ///
221    /// let arr = ByteArray::with_capacity(5).fill_with(126);
222    /// assert_eq!(arr.as_bytes(),[126u8,126,126,126,126]);
223    ///
224    /// // or in the rare cases where a different odd word padding is set
225    ///
226    /// let arr = ByteArray::new_uninit()
227    ///                 .with_capacity(7)
228    ///                 .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
229    ///                 .fill_with(5);
230    ///
231    /// assert_eq!(arr.as_bytes(),[5u8,5,5,5,5,5,5]);
232    ///
233    ///
234    /// ```
235    ///
236    ///
237    pub fn fill_with(self, value: u8) -> Self {
238        if self.bytes.capacity() == 0 {
239            self
240        } else {
241            ByteArray {
242                bytes: vec![value; self.bytes.capacity()],
243                byte_padding: self.byte_padding,
244            }
245        }
246    }
247
248    /// chaining function to create a byte array from hex
249    ///
250    /// TODO make this more resilient by accepting 0x too
251    ///
252    /// # Example
253    /// ```
254    /// use byte_array_ops::byte_array::model::ByteArray;
255    /// let arr = ByteArray::default()
256    ///         .with_hex("deadbeef").expect("failed to convert hex string");
257    ///
258    /// assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
259    /// ```
260    pub fn with_hex(self, hex_str: &str) -> Result<Self, ByteArrayError> {
261        if hex_str.is_empty() {
262            return Err(ByteArrayError::EmptyInput);
263        }
264
265        // Filter out underscores and collect
266        let bytes: Vec<u8> = hex_str.bytes().filter(|&b| b != b'_').collect();
267
268        // Validate characters
269        if let Some(&invalid) = bytes.iter().find(|&&b| !b.is_ascii_hexdigit()) {
270            return Err(InvalidHexChar(invalid as char));
271        }
272
273        let hex_count = bytes.len();
274        let byte_count = hex_count / 2 + hex_count % 2;
275        let mut ret = ByteArray::with_capacity(byte_count);
276        ret.byte_padding = self.byte_padding;
277
278        let mut start = 0;
279
280        // Handle odd length - process first char alone (LEFT padding for network byte order)
281        if hex_count % 2 == 1 {
282            ret.bytes
283                .push(Utils::hex_char_to_nibble_unchecked(bytes[0]));
284            start = 1;
285        }
286
287        // Process remaining pairs
288        for i in (start..hex_count).step_by(2) {
289            let byte_val = Utils::hex_char_to_nibble_unchecked(bytes[i]) << 4
290                | Utils::hex_char_to_nibble_unchecked(bytes[i + 1]);
291            ret.bytes.push(byte_val);
292        }
293
294        Ok(ret)
295    }
296
297    /// chaining function to create a byte array from a binary representation
298    ///
299    /// # Example
300    /// ```
301    /// use byte_array_ops::byte_array::model::ByteArray;
302    /// let arr = ByteArray::default()
303    ///         .with_bin("1010010").expect("failed to convert binary string");
304    ///
305    /// assert_eq!(arr.as_bytes(), [0x52]);
306    /// ```
307    pub fn with_bin(self, bin_str: &str) -> Result<Self, ByteArrayError> {
308        if bin_str.is_empty() {
309            return Err(ByteArrayError::EmptyInput);
310        }
311
312        // Filter out underscores and collect
313        let bytes: Vec<u8> = bin_str.bytes().filter(|&b| b != b'_').collect();
314
315        // Validate characters
316        if let Some(&invalid) = bytes.iter().find(|&&b| b != b'0' && b != b'1') {
317            return Err(InvalidBinaryChar(invalid as char));
318        }
319
320        let bit_count = bytes.len();
321        let byte_count = bit_count.div_ceil(8);
322        let mut ret = ByteArray::with_capacity(byte_count);
323        ret.byte_padding = self.byte_padding;
324
325        let rem = bit_count % 8;
326
327        // Handle partial first byte (left padding) OR entire input if < 8 bits
328        let start = if rem != 0 {
329            let mut byte = 0u8;
330            #[allow(clippy::needless_range_loop)] // our version is more readable than clippy's
331            for i in 0..rem {
332                let bit_value = bytes[i] - b'0';
333                byte |= bit_value << (rem - 1 - i);
334            }
335            ret.bytes.push(byte);
336            rem
337        } else {
338            0
339        };
340
341        // Process remaining full bytes (only if there are any left)
342        for i in (start..bit_count).step_by(8) {
343            let mut byte = 0u8;
344
345            for j in 0..8 {
346                let bit_value = bytes[i + j] - b'0';
347                byte |= bit_value << (7 - j);
348            }
349
350            ret.bytes.push(byte);
351        }
352
353        Ok(ret)
354    }
355
356    /// initialize the array with a certain amount of zeros
357    /// internally this creates the byte array representation with `vec![0u8;count]`
358    /// the rest is default initialized
359    pub fn init_zeros(count: usize) -> Self {
360        ByteArray {
361            bytes: vec![0u8; count],
362            byte_padding: ByteArrayOddWordPad::default(),
363        }
364    }
365
366    /// initialize the array with a certain amount of `value`
367    /// internally this creates the byte array representation with `vec![value;count]`
368    /// the rest is default initialized
369    pub fn init_value(value: u8, count: usize) -> Self {
370        ByteArray {
371            bytes: vec![value; count],
372            byte_padding: ByteArrayOddWordPad::default(),
373        }
374    }
375
376    /// returns a slices to the interior bytes
377    ///
378    /// # NOTE
379    /// There is another method that provides a zero-cost move of the interior bytes using the
380    /// [`Vec::from::<ByteArray>`] implementation. Please check the [`ByteArray`]'s [`From<ByteArray>`] implementation
381    /// documentation
382    ///
383    /// # Example
384    /// ```
385    /// use byte_array_ops::byte_array::model::ByteArray;
386    ///
387    /// let arr : ByteArray = "0xff2569".parse().unwrap();
388    ///
389    /// let slice = arr.as_bytes();
390    ///
391    /// assert_eq!(slice, [0xff,0x25,0x69]);
392    /// ```
393    ///
394    /// # Alternative (Zero-cost move)
395    /// ```
396    /// use byte_array_ops::byte_array::model::ByteArray;
397    /// let arr : ByteArray = "0b11101111".parse().unwrap();
398    ///
399    /// assert_eq!(arr.as_bytes(), [0xef]);
400    ///
401    /// let moved_data : Vec<u8> = arr.into();
402    /// assert_eq!(moved_data, vec![0xef]);
403    /// ```
404    ///
405    pub fn as_bytes(&self) -> &[u8] {
406        &self.bytes
407    }
408
409    /// returns the set padding
410    pub fn get_padding(&self) -> ByteArrayOddWordPad {
411        self.byte_padding
412    }
413
414    /// returns the number of bytes in the array
415    /// TODO add example
416    pub fn len(&self) -> usize {
417        self.bytes.len()
418    }
419
420    /// returns true if there are no bytes in the array
421    /// TODO example
422    pub fn is_empty(&self) -> bool {
423        self.bytes.is_empty()
424    }
425
426    #[cfg(feature = "experimental")]
427    pub fn resize(&mut self, new_capacity: usize, value: u8) {
428        unimplemented!(
429            "this is insecure and must find a way on how to handle this in a more secure fashion"
430        );
431        self.bytes.resize(new_capacity, value);
432    }
433
434    /// Tries to append an element to the back of a collection. Fails
435    ///
436    /// # Panics
437    ///  if the new capacity exceeds [`isize::MAX`] bytes.
438    ///
439    /// # Examples
440    ///```
441    /// use byte_array_ops::byte_array::model::ByteArray;
442    /// let mut arr = ByteArray::default()
443    ///
444    /// arr.try_push(0xab).unwrap();
445    ///
446    /// assert_eq!(arr.as_bytes(), [1, 2, 0xab]);
447    /// ```
448    #[cfg(feature = "experimental")]
449    pub fn try_push(&mut self, value: u8) {
450        self.bytes.push(value);
451    }
452}
453
454// index handling
455
456impl Index<usize> for ByteArray {
457    type Output = u8;
458
459    /// index accessor for ByteArray
460    /// # Panics
461    /// When out of bound use [`ByteArray::get`] instead if you want a checked getter
462    fn index(&self, index: usize) -> &Self::Output {
463        &self.bytes[index]
464    }
465}
466
467impl IndexMut<usize> for ByteArray {
468    /// Mutable index accessor for ByteArray
469    /// # Panics
470    /// When out of bound use [`ByteArray::get`] instead if you want a checked getter
471    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
472        &mut self.bytes[index]
473    }
474}
475
476impl ByteArray {
477    pub fn get(&self, index: usize) -> Option<&u8> {
478        self.bytes.get(index)
479    }
480}
481
482impl AsRef<[u8]> for ByteArray {
483    fn as_ref(&self) -> &[u8] {
484        self.as_bytes()
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491    use alloc::vec;
492    use core::str::FromStr;
493
494    #[test]
495    fn test_hex_string_constructor() {
496        let b1: ByteArray = "0xfe81eabd5".parse().unwrap();
497
498        assert_eq!(b1.len(), 5);
499        assert_eq!(b1.bytes, vec![0x0f, 0xe8, 0x1e, 0xab, 0xd5]);
500    }
501    #[test]
502    fn test_ba_new() {
503        let arr = ByteArray::new_uninit();
504
505        assert_eq!(arr.capacity, None);
506        assert!(UninitByteArray::try_from(arr).is_ok())
507    }
508
509    #[test]
510    fn test_ba_with_capacity() {
511        let arr = ByteArray::with_capacity(10);
512
513        assert_eq!(arr.bytes.capacity(), 10);
514        assert!(ByteArray::try_from(arr).is_ok());
515    }
516
517    #[test]
518    fn test_with_hex() {
519        let arr = ByteArray::default().with_hex("ffabc");
520
521        assert_eq!(arr.unwrap().bytes, vec![0x0f, 0xfa, 0xbc]);
522    }
523
524    #[test]
525    fn test_with_bin_less_than_8() {
526        let arr = ByteArray::with_capacity(1).with_bin("1101111").unwrap();
527        assert_eq!(arr.bytes, vec![0x6f]);
528    }
529
530    #[test]
531    fn test_with_bin_more_than_8() {
532        let arr = ByteArray::with_capacity(1)
533            .with_bin("110111010110111")
534            .unwrap();
535        assert_eq!(arr.bytes, vec![0x6e, 0xb7]);
536    }
537
538    #[test]
539    fn test_get_padding() {
540        let arr = ByteArray::default();
541
542        assert_eq!(
543            ByteArrayOddWordPad::default(),
544            ByteArrayOddWordPad::LeftPadding
545        );
546        assert_eq!(arr.byte_padding, ByteArrayOddWordPad::LeftPadding);
547    }
548
549    #[test]
550    fn test_len() {
551        let arr = ByteArray::default()
552            .with_hex("ffab12345bc")
553            .expect("error converting");
554
555        assert_eq!(arr.len(), 6);
556    }
557
558    #[test]
559    fn test_is_empty() {
560        let arr = ByteArray::default();
561        assert_eq!(arr.is_empty(), true);
562
563        let arr = ByteArray::with_capacity(1)
564            .with_hex("bb")
565            .expect("error converting");
566
567        assert_eq!(arr.is_empty(), false);
568    }
569
570    #[test]
571    fn test_equality_derives() {
572        let arr: ByteArray = "0xffab12345ffaf".parse().unwrap();
573        let arr_2 = ByteArray::default()
574            .with_hex("ffab12345ffafdeadbeef")
575            .unwrap();
576        let arr_3 = arr.clone();
577
578        assert_ne!(arr.bytes, arr_2.bytes);
579        assert_eq!(arr.bytes, arr_3.bytes);
580    }
581
582    #[test]
583    fn test_init_zeros() {
584        let arr = ByteArray::init_zeros(8);
585
586        assert_eq!(arr.bytes, [0u8, 0, 0, 0, 0, 0, 0, 0]);
587    }
588
589    #[test]
590    fn test_init_value() {
591        let arr = ByteArray::init_value(254, 8);
592
593        assert_eq!(arr.bytes, [254, 254, 254, 254, 254, 254, 254, 254]);
594    }
595
596    #[test]
597    fn test_mutable_idx_accessor() {
598        let mut arr: ByteArray = "0xffab1245".parse().unwrap();
599
600        arr[3] = 0xbe;
601        arr[1] = 0xfa;
602
603        assert_eq!(arr.bytes, ByteArray::from_str("0xfffa12be").unwrap().bytes);
604    }
605
606    #[test]
607    fn test_fill_zeros() {
608        let arr = ByteArray::default().fill_zeros();
609        assert!(arr.as_bytes().is_empty());
610
611        let arr = ByteArray::with_capacity(5).fill_zeros();
612        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0]);
613
614        let arr = ByteArray::new_uninit()
615            .with_capacity(7)
616            .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
617            .fill_zeros();
618
619        assert_eq!(arr.as_bytes(), [0u8, 0, 0, 0, 0, 0, 0])
620    }
621
622    #[test]
623    fn test_fill_with() {
624        let arr = ByteArray::default().fill_zeros();
625        assert!(arr.as_bytes().is_empty());
626
627        let arr = ByteArray::with_capacity(5).fill_with(126);
628        assert_eq!(arr.as_bytes(), [126u8, 126, 126, 126, 126]);
629
630        let arr = ByteArray::new_uninit()
631            .with_capacity(7)
632            .with_odd_pad_dir(ByteArrayOddWordPad::LeftPadding)
633            .fill_with(5);
634
635        assert_eq!(arr.as_bytes(), [5u8, 5, 5, 5, 5, 5, 5]);
636    }
637
638    #[test]
639    #[cfg(feature = "experimental")]
640    fn test_push_element() {
641        let mut arr: ByteArray = vec![1, 2].into();
642
643        arr.try_push(0xab);
644
645        assert_eq!(arr.as_bytes(), [1, 2, 0xab]);
646    }
647
648    #[test]
649    fn test_hex_with_underscores() {
650        let arr = ByteArray::default().with_hex("de_ad_be_ef").unwrap();
651        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
652    }
653
654    #[test]
655    fn test_bin_with_underscores() {
656        let arr = ByteArray::default().with_bin("1010_0101").unwrap();
657        assert_eq!(arr.as_bytes(), [0xa5]);
658    }
659
660    #[test]
661    fn test_bin_with_underscores_odd_length() {
662        let arr = ByteArray::default().with_bin("110_1111").unwrap();
663        assert_eq!(arr.as_bytes(), [0x6f]);
664    }
665}