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}