alloy_primitives/bits/
fixed.rs

1use crate::aliases;
2use core::{fmt, iter, ops, str};
3use derive_more::{Deref, DerefMut, From, Index, IndexMut, IntoIterator};
4use hex::FromHex;
5
6/// A byte array of fixed length (`[u8; N]`).
7///
8/// This type allows us to more tightly control serialization, deserialization.
9/// rlp encoding, decoding, and other type-level attributes for fixed-length
10/// byte arrays.
11///
12/// Users looking to prevent type-confusion between byte arrays of different
13/// lengths should use the [`wrap_fixed_bytes!`](crate::wrap_fixed_bytes) macro
14/// to create a new fixed-length byte array type.
15#[derive(
16    Clone,
17    Copy,
18    PartialEq,
19    Eq,
20    PartialOrd,
21    Ord,
22    Hash,
23    Deref,
24    DerefMut,
25    From,
26    Index,
27    IndexMut,
28    IntoIterator,
29)]
30#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
31#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
32#[repr(transparent)]
33pub struct FixedBytes<const N: usize>(#[into_iterator(owned, ref, ref_mut)] pub [u8; N]);
34
35crate::impl_fb_traits!(FixedBytes<N>, N, const);
36
37impl<const N: usize> Default for FixedBytes<N> {
38    #[inline]
39    fn default() -> Self {
40        Self::ZERO
41    }
42}
43
44impl<const N: usize> Default for &FixedBytes<N> {
45    #[inline]
46    fn default() -> Self {
47        &FixedBytes::ZERO
48    }
49}
50
51impl<const N: usize> From<&[u8; N]> for FixedBytes<N> {
52    #[inline]
53    fn from(bytes: &[u8; N]) -> Self {
54        Self(*bytes)
55    }
56}
57
58impl<const N: usize> From<&mut [u8; N]> for FixedBytes<N> {
59    #[inline]
60    fn from(bytes: &mut [u8; N]) -> Self {
61        Self(*bytes)
62    }
63}
64
65/// Tries to create a `FixedBytes<N>` by copying from a slice `&[u8]`. Succeeds
66/// if `slice.len() == N`.
67impl<const N: usize> TryFrom<&[u8]> for FixedBytes<N> {
68    type Error = core::array::TryFromSliceError;
69
70    #[inline]
71    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
72        <&Self>::try_from(slice).copied()
73    }
74}
75
76/// Tries to create a `FixedBytes<N>` by copying from a mutable slice `&mut
77/// [u8]`. Succeeds if `slice.len() == N`.
78impl<const N: usize> TryFrom<&mut [u8]> for FixedBytes<N> {
79    type Error = core::array::TryFromSliceError;
80
81    #[inline]
82    fn try_from(slice: &mut [u8]) -> Result<Self, Self::Error> {
83        Self::try_from(&*slice)
84    }
85}
86
87/// Tries to create a ref `FixedBytes<N>` by copying from a slice `&[u8]`.
88/// Succeeds if `slice.len() == N`.
89impl<'a, const N: usize> TryFrom<&'a [u8]> for &'a FixedBytes<N> {
90    type Error = core::array::TryFromSliceError;
91
92    #[inline]
93    fn try_from(slice: &'a [u8]) -> Result<&'a FixedBytes<N>, Self::Error> {
94        // SAFETY: `FixedBytes<N>` is `repr(transparent)` for `[u8; N]`
95        <&[u8; N]>::try_from(slice).map(|array_ref| unsafe { core::mem::transmute(array_ref) })
96    }
97}
98
99/// Tries to create a ref `FixedBytes<N>` by copying from a mutable slice `&mut
100/// [u8]`. Succeeds if `slice.len() == N`.
101impl<'a, const N: usize> TryFrom<&'a mut [u8]> for &'a mut FixedBytes<N> {
102    type Error = core::array::TryFromSliceError;
103
104    #[inline]
105    fn try_from(slice: &'a mut [u8]) -> Result<&'a mut FixedBytes<N>, Self::Error> {
106        // SAFETY: `FixedBytes<N>` is `repr(transparent)` for `[u8; N]`
107        <&mut [u8; N]>::try_from(slice).map(|array_ref| unsafe { core::mem::transmute(array_ref) })
108    }
109}
110
111// Ideally this would be:
112// `impl<const N: usize> From<FixedBytes<N>> for Uint<N * 8>`
113// `impl<const N: usize> From<Uint<N / 8>> for FixedBytes<N>`
114macro_rules! fixed_bytes_uint_conversions {
115    ($($int:ty => $fb:ty),* $(,)?) => {$(
116        impl From<$int> for $fb {
117            /// Converts a fixed-width unsigned integer into a fixed byte array
118            /// by interpreting the bytes as big-endian.
119            #[inline]
120            fn from(value: $int) -> Self {
121                Self(value.to_be_bytes())
122            }
123        }
124
125        impl From<$fb> for $int {
126            /// Converts a fixed byte array into a fixed-width unsigned integer
127            /// by interpreting the bytes as big-endian.
128            #[inline]
129            fn from(value: $fb) -> Self {
130                Self::from_be_bytes(value.0)
131            }
132        }
133
134        const _: () = assert!(<$int>::BITS as usize == <$fb>::len_bytes() * 8);
135    )*};
136}
137
138fixed_bytes_uint_conversions! {
139    u8            => aliases::B8,
140    aliases::U8   => aliases::B8,
141    i8            => aliases::B8,
142    aliases::I8   => aliases::B8,
143
144    u16           => aliases::B16,
145    aliases::U16  => aliases::B16,
146    i16           => aliases::B16,
147    aliases::I16  => aliases::B16,
148
149    u32           => aliases::B32,
150    aliases::U32  => aliases::B32,
151    i32           => aliases::B32,
152    aliases::I32  => aliases::B32,
153
154    u64           => aliases::B64,
155    aliases::U64  => aliases::B64,
156    i64           => aliases::B64,
157    aliases::I64  => aliases::B64,
158
159    u128          => aliases::B128,
160    aliases::U128 => aliases::B128,
161    i128          => aliases::B128,
162    aliases::I128 => aliases::B128,
163
164    aliases::U160 => aliases::B160,
165    aliases::I160 => aliases::B160,
166
167    aliases::U256 => aliases::B256,
168    aliases::I256 => aliases::B256,
169
170    aliases::U512 => aliases::B512,
171    aliases::I512 => aliases::B512,
172
173}
174
175impl<const N: usize> From<FixedBytes<N>> for [u8; N] {
176    #[inline]
177    fn from(s: FixedBytes<N>) -> Self {
178        s.0
179    }
180}
181
182impl<const N: usize> AsRef<[u8; N]> for FixedBytes<N> {
183    #[inline]
184    fn as_ref(&self) -> &[u8; N] {
185        &self.0
186    }
187}
188
189impl<const N: usize> AsMut<[u8; N]> for FixedBytes<N> {
190    #[inline]
191    fn as_mut(&mut self) -> &mut [u8; N] {
192        &mut self.0
193    }
194}
195
196impl<const N: usize> AsRef<[u8]> for FixedBytes<N> {
197    #[inline]
198    fn as_ref(&self) -> &[u8] {
199        &self.0
200    }
201}
202
203impl<const N: usize> AsMut<[u8]> for FixedBytes<N> {
204    #[inline]
205    fn as_mut(&mut self) -> &mut [u8] {
206        &mut self.0
207    }
208}
209
210impl<const N: usize> fmt::Debug for FixedBytes<N> {
211    #[inline]
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        self.fmt_hex::<false>(f, true)
214    }
215}
216
217impl<const N: usize> fmt::Display for FixedBytes<N> {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        // If the alternate flag is NOT set, we write the full hex.
220        if N <= 4 || !f.alternate() {
221            return self.fmt_hex::<false>(f, true);
222        }
223
224        // If the alternate flag is set, we use middle-out compression.
225        const SEP_LEN: usize = '…'.len_utf8();
226        let mut buf = [0; 2 + 4 + SEP_LEN + 4];
227        buf[0] = b'0';
228        buf[1] = b'x';
229        hex::encode_to_slice(&self.0[0..2], &mut buf[2..6]).unwrap();
230        '…'.encode_utf8(&mut buf[6..]);
231        hex::encode_to_slice(&self.0[N - 2..N], &mut buf[6 + SEP_LEN..]).unwrap();
232
233        // SAFETY: always valid UTF-8
234        f.write_str(unsafe { str::from_utf8_unchecked(&buf) })
235    }
236}
237
238impl<const N: usize> fmt::LowerHex for FixedBytes<N> {
239    #[inline]
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        self.fmt_hex::<false>(f, f.alternate())
242    }
243}
244
245impl<const N: usize> fmt::UpperHex for FixedBytes<N> {
246    #[inline]
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        self.fmt_hex::<true>(f, f.alternate())
249    }
250}
251
252impl<const N: usize> ops::BitAnd for FixedBytes<N> {
253    type Output = Self;
254
255    #[inline]
256    fn bitand(mut self, rhs: Self) -> Self::Output {
257        self &= rhs;
258        self
259    }
260}
261
262impl<const N: usize> ops::BitAndAssign for FixedBytes<N> {
263    #[inline]
264    fn bitand_assign(&mut self, rhs: Self) {
265        // Note: `slice::Iter` has better codegen than `array::IntoIter`
266        iter::zip(self, &rhs).for_each(|(a, b)| *a &= *b);
267    }
268}
269
270impl<const N: usize> ops::BitOr for FixedBytes<N> {
271    type Output = Self;
272
273    #[inline]
274    fn bitor(mut self, rhs: Self) -> Self::Output {
275        self |= rhs;
276        self
277    }
278}
279
280impl<const N: usize> ops::BitOrAssign for FixedBytes<N> {
281    #[inline]
282    fn bitor_assign(&mut self, rhs: Self) {
283        // Note: `slice::Iter` has better codegen than `array::IntoIter`
284        iter::zip(self, &rhs).for_each(|(a, b)| *a |= *b);
285    }
286}
287
288impl<const N: usize> ops::BitXor for FixedBytes<N> {
289    type Output = Self;
290
291    #[inline]
292    fn bitxor(mut self, rhs: Self) -> Self::Output {
293        self ^= rhs;
294        self
295    }
296}
297
298impl<const N: usize> ops::BitXorAssign for FixedBytes<N> {
299    #[inline]
300    fn bitxor_assign(&mut self, rhs: Self) {
301        // Note: `slice::Iter` has better codegen than `array::IntoIter`
302        iter::zip(self, &rhs).for_each(|(a, b)| *a ^= *b);
303    }
304}
305
306impl<const N: usize> ops::Not for FixedBytes<N> {
307    type Output = Self;
308
309    #[inline]
310    fn not(mut self) -> Self::Output {
311        self.iter_mut().for_each(|byte| *byte = !*byte);
312        self
313    }
314}
315
316impl<const N: usize> str::FromStr for FixedBytes<N> {
317    type Err = hex::FromHexError;
318
319    #[inline]
320    fn from_str(s: &str) -> Result<Self, Self::Err> {
321        Self::from_hex(s)
322    }
323}
324
325#[cfg(feature = "rand")]
326impl<const N: usize> rand::distributions::Distribution<FixedBytes<N>>
327    for rand::distributions::Standard
328{
329    #[inline]
330    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> FixedBytes<N> {
331        FixedBytes::random_with(rng)
332    }
333}
334
335impl<const N: usize> FixedBytes<N> {
336    /// Array of Zero bytes.
337    pub const ZERO: Self = Self([0u8; N]);
338
339    /// Wraps the given byte array in [`FixedBytes`].
340    #[inline]
341    pub const fn new(bytes: [u8; N]) -> Self {
342        Self(bytes)
343    }
344
345    /// Creates a new [`FixedBytes`] with the last byte set to `x`.
346    #[inline]
347    pub const fn with_last_byte(x: u8) -> Self {
348        let mut bytes = [0u8; N];
349        if N > 0 {
350            bytes[N - 1] = x;
351        }
352        Self(bytes)
353    }
354
355    /// Creates a new [`FixedBytes`] where all bytes are set to `byte`.
356    #[inline]
357    pub const fn repeat_byte(byte: u8) -> Self {
358        Self([byte; N])
359    }
360
361    /// Returns the size of this byte array (`N`).
362    #[inline(always)]
363    pub const fn len_bytes() -> usize {
364        N
365    }
366
367    /// Creates a new [`FixedBytes`] with the default cryptographic random number generator.
368    ///
369    /// This is `rand::thread_rng` if the "rand" and "std" features are enabled, otherwise
370    /// it uses `getrandom::getrandom`. Both are cryptographically secure.
371    #[cfg(feature = "getrandom")]
372    #[inline]
373    #[track_caller]
374    pub fn random() -> Self {
375        // SAFETY: `bytes` is only accessible after random initialization.
376        #[allow(clippy::uninit_assumed_init)]
377        let mut bytes = unsafe { core::mem::MaybeUninit::<Self>::uninit().assume_init() };
378        bytes.randomize();
379        bytes
380    }
381
382    /// Tries to create a new [`FixedBytes`] with the default cryptographic random number
383    /// generator.
384    ///
385    /// See [`random`](Self::random) for more details.
386    #[cfg(feature = "getrandom")]
387    #[inline]
388    pub fn try_random() -> Result<Self, getrandom::Error> {
389        // SAFETY: `bytes` is only accessible after random initialization.
390        #[allow(clippy::uninit_assumed_init)]
391        let mut bytes = unsafe { core::mem::MaybeUninit::<Self>::uninit().assume_init() };
392        bytes.try_randomize()?;
393        Ok(bytes)
394    }
395
396    /// Creates a new [`FixedBytes`] with the given random number generator.
397    ///
398    /// See [`random`](Self::random) for more details.
399    #[cfg(feature = "rand")]
400    #[inline]
401    #[doc(alias = "random_using")]
402    pub fn random_with<R: rand::RngCore + ?Sized>(rng: &mut R) -> Self {
403        // SAFETY: `bytes` is only accessible after random initialization.
404        #[allow(clippy::uninit_assumed_init)]
405        let mut bytes = unsafe { core::mem::MaybeUninit::<Self>::uninit().assume_init() };
406        bytes.randomize_with(rng);
407        bytes
408    }
409
410    /// Fills this [`FixedBytes`] with the default cryptographic random number generator.
411    ///
412    /// See [`random`](Self::random) for more details.
413    #[cfg(feature = "getrandom")]
414    #[inline]
415    #[track_caller]
416    pub fn randomize(&mut self) {
417        self.try_randomize().unwrap_or_else(|e| panic!("failed to fill with random bytes: {e}"));
418    }
419
420    /// Tries to fill this [`FixedBytes`] with the default cryptographic random number
421    /// generator.
422    ///
423    /// See [`random`](Self::random) for more details.
424    #[inline]
425    #[cfg(feature = "getrandom")]
426    pub fn try_randomize(&mut self) -> Result<(), getrandom::Error> {
427        #[cfg(all(feature = "rand", feature = "std"))]
428        {
429            self.randomize_with(&mut rand::thread_rng());
430            Ok(())
431        }
432        #[cfg(not(all(feature = "rand", feature = "std")))]
433        {
434            getrandom::getrandom(&mut self.0)
435        }
436    }
437
438    /// Fills this [`FixedBytes`] with the given random number generator.
439    #[cfg(feature = "rand")]
440    #[inline]
441    #[doc(alias = "randomize_using")]
442    pub fn randomize_with<R: rand::RngCore + ?Sized>(&mut self, rng: &mut R) {
443        rng.fill_bytes(&mut self.0);
444    }
445
446    /// Concatenate two `FixedBytes`.
447    ///
448    /// Due to constraints in the language, the user must specify the value of
449    /// the output size `Z`.
450    ///
451    /// # Panics
452    ///
453    /// Panics if `Z` is not equal to `N + M`.
454    pub const fn concat_const<const M: usize, const Z: usize>(
455        self,
456        other: FixedBytes<M>,
457    ) -> FixedBytes<Z> {
458        assert!(N + M == Z, "Output size `Z` must equal the sum of the input sizes `N` and `M`");
459
460        let mut result = [0u8; Z];
461        let mut i = 0;
462        while i < Z {
463            result[i] = if i >= N { other.0[i - N] } else { self.0[i] };
464            i += 1;
465        }
466        FixedBytes(result)
467    }
468
469    /// Create a new [`FixedBytes`] from the given slice `src`.
470    ///
471    /// For a fallible version, use the `TryFrom<&[u8]>` implementation.
472    ///
473    /// # Note
474    ///
475    /// The given bytes are interpreted in big endian order.
476    ///
477    /// # Panics
478    ///
479    /// If the length of `src` and the number of bytes in `Self` do not match.
480    #[inline]
481    #[track_caller]
482    pub fn from_slice(src: &[u8]) -> Self {
483        match Self::try_from(src) {
484            Ok(x) => x,
485            Err(_) => panic!("cannot convert a slice of length {} to FixedBytes<{N}>", src.len()),
486        }
487    }
488
489    /// Create a new [`FixedBytes`] from the given slice `src`, left-padding it
490    /// with zeroes if necessary.
491    ///
492    /// # Note
493    ///
494    /// The given bytes are interpreted in big endian order.
495    ///
496    /// # Panics
497    ///
498    /// Panics if `src.len() > N`.
499    #[inline]
500    #[track_caller]
501    pub fn left_padding_from(value: &[u8]) -> Self {
502        let len = value.len();
503        assert!(len <= N, "slice is too large. Expected <={N} bytes, got {len}");
504        let mut bytes = Self::ZERO;
505        bytes[N - len..].copy_from_slice(value);
506        bytes
507    }
508
509    /// Create a new [`FixedBytes`] from the given slice `src`, right-padding it
510    /// with zeroes if necessary.
511    ///
512    /// # Note
513    ///
514    /// The given bytes are interpreted in big endian order.
515    ///
516    /// # Panics
517    ///
518    /// Panics if `src.len() > N`.
519    #[inline]
520    #[track_caller]
521    pub fn right_padding_from(value: &[u8]) -> Self {
522        let len = value.len();
523        assert!(len <= N, "slice is too large. Expected <={N} bytes, got {len}");
524        let mut bytes = Self::ZERO;
525        bytes[..len].copy_from_slice(value);
526        bytes
527    }
528
529    /// Returns a slice containing the entire array. Equivalent to `&s[..]`.
530    #[inline]
531    pub const fn as_slice(&self) -> &[u8] {
532        &self.0
533    }
534
535    /// Returns a mutable slice containing the entire array. Equivalent to
536    /// `&mut s[..]`.
537    #[inline]
538    pub fn as_mut_slice(&mut self) -> &mut [u8] {
539        &mut self.0
540    }
541
542    /// Returns `true` if all bits set in `self` are also set in `b`.
543    #[inline]
544    pub fn covers(&self, other: &Self) -> bool {
545        (*self & *other) == *other
546    }
547
548    /// Returns `true` if all bits set in `self` are also set in `b`.
549    pub const fn const_covers(self, other: Self) -> bool {
550        // (self & other) == other
551        other.const_eq(&self.bit_and(other))
552    }
553
554    /// Compile-time equality. NOT constant-time equality.
555    pub const fn const_eq(&self, other: &Self) -> bool {
556        let mut i = 0;
557        while i < N {
558            if self.0[i] != other.0[i] {
559                return false;
560            }
561            i += 1;
562        }
563        true
564    }
565
566    /// Returns `true` if no bits are set.
567    #[inline]
568    pub fn is_zero(&self) -> bool {
569        *self == Self::ZERO
570    }
571
572    /// Returns `true` if no bits are set.
573    #[inline]
574    pub const fn const_is_zero(&self) -> bool {
575        self.const_eq(&Self::ZERO)
576    }
577
578    /// Computes the bitwise AND of two `FixedBytes`.
579    pub const fn bit_and(self, rhs: Self) -> Self {
580        let mut ret = Self::ZERO;
581        let mut i = 0;
582        while i < N {
583            ret.0[i] = self.0[i] & rhs.0[i];
584            i += 1;
585        }
586        ret
587    }
588
589    /// Computes the bitwise OR of two `FixedBytes`.
590    pub const fn bit_or(self, rhs: Self) -> Self {
591        let mut ret = Self::ZERO;
592        let mut i = 0;
593        while i < N {
594            ret.0[i] = self.0[i] | rhs.0[i];
595            i += 1;
596        }
597        ret
598    }
599
600    /// Computes the bitwise XOR of two `FixedBytes`.
601    pub const fn bit_xor(self, rhs: Self) -> Self {
602        let mut ret = Self::ZERO;
603        let mut i = 0;
604        while i < N {
605            ret.0[i] = self.0[i] ^ rhs.0[i];
606            i += 1;
607        }
608        ret
609    }
610
611    fn fmt_hex<const UPPER: bool>(&self, f: &mut fmt::Formatter<'_>, prefix: bool) -> fmt::Result {
612        let mut buf = hex::Buffer::<N, true>::new();
613        let s = if UPPER { buf.format_upper(self) } else { buf.format(self) };
614        // SAFETY: The buffer is guaranteed to be at least 2 bytes in length.
615        f.write_str(unsafe { s.get_unchecked((!prefix as usize) * 2..) })
616    }
617}
618
619#[cfg(test)]
620mod tests {
621    use super::*;
622
623    macro_rules! test_fmt {
624        ($($fmt:literal, $hex:literal => $expected:literal;)+) => {$(
625            assert_eq!(
626                format!($fmt, fixed_bytes!($hex)),
627                $expected
628            );
629        )+};
630    }
631
632    #[test]
633    fn concat_const() {
634        const A: FixedBytes<2> = fixed_bytes!("0x0123");
635        const B: FixedBytes<2> = fixed_bytes!("0x4567");
636        const EXPECTED: FixedBytes<4> = fixed_bytes!("0x01234567");
637        const ACTUAL: FixedBytes<4> = A.concat_const(B);
638
639        assert_eq!(ACTUAL, EXPECTED);
640    }
641
642    #[test]
643    fn display() {
644        test_fmt! {
645            "{}", "0123456789abcdef" => "0x0123456789abcdef";
646            "{:#}", "0123" => "0x0123";
647            "{:#}", "01234567" => "0x01234567";
648            "{:#}", "0123456789" => "0x0123…6789";
649        }
650    }
651
652    #[test]
653    fn debug() {
654        test_fmt! {
655            "{:?}", "0123456789abcdef" => "0x0123456789abcdef";
656            "{:#?}", "0123456789abcdef" => "0x0123456789abcdef";
657        }
658    }
659
660    #[test]
661    fn lower_hex() {
662        test_fmt! {
663            "{:x}", "0123456789abcdef" => "0123456789abcdef";
664            "{:#x}", "0123456789abcdef" => "0x0123456789abcdef";
665        }
666    }
667
668    #[test]
669    fn upper_hex() {
670        test_fmt! {
671            "{:X}", "0123456789abcdef" => "0123456789ABCDEF";
672            "{:#X}", "0123456789abcdef" => "0x0123456789ABCDEF";
673        }
674    }
675
676    #[test]
677    fn left_padding_from() {
678        assert_eq!(FixedBytes::<4>::left_padding_from(&[0x01, 0x23]), fixed_bytes!("0x00000123"));
679
680        assert_eq!(
681            FixedBytes::<4>::left_padding_from(&[0x01, 0x23, 0x45, 0x67]),
682            fixed_bytes!("0x01234567")
683        );
684    }
685
686    #[test]
687    #[should_panic(expected = "slice is too large. Expected <=4 bytes, got 5")]
688    fn left_padding_from_too_large() {
689        FixedBytes::<4>::left_padding_from(&[0x01, 0x23, 0x45, 0x67, 0x89]);
690    }
691
692    #[test]
693    fn right_padding_from() {
694        assert_eq!(FixedBytes::<4>::right_padding_from(&[0x01, 0x23]), fixed_bytes!("0x01230000"));
695
696        assert_eq!(
697            FixedBytes::<4>::right_padding_from(&[0x01, 0x23, 0x45, 0x67]),
698            fixed_bytes!("0x01234567")
699        );
700    }
701
702    #[test]
703    #[should_panic(expected = "slice is too large. Expected <=4 bytes, got 5")]
704    fn right_padding_from_too_large() {
705        FixedBytes::<4>::right_padding_from(&[0x01, 0x23, 0x45, 0x67, 0x89]);
706    }
707}