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