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}