1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause

//! Explicit endian types useful for embedding in structs or reinterpreting data.
//!
//! Each endian type is guaarnteed to have the same size and alignment as a regular unsigned
//! primitive of the equal size.
//!
//! # Examples
//!
//! ```
//! # use vm_memory::{Be32, Le32};
//! #
//! let b: Be32 = From::from(3);
//! let l: Le32 = From::from(3);
//!
//! assert_eq!(b.to_native(), 3);
//! assert_eq!(l.to_native(), 3);
//! assert!(b == 3);
//! assert!(l == 3);
//!
//! let b_trans: u32 = unsafe { std::mem::transmute(b) };
//! let l_trans: u32 = unsafe { std::mem::transmute(l) };
//!
//! #[cfg(target_endian = "little")]
//! assert_eq!(l_trans, 3);
//! #[cfg(target_endian = "big")]
//! assert_eq!(b_trans, 3);
//!
//! assert_ne!(b_trans, l_trans);
//! ```

use std::mem::{align_of, size_of};

use crate::bytes::ByteValued;

macro_rules! const_assert {
    ($condition:expr) => {
        let _ = [(); 0 - !$condition as usize];
    };
}

macro_rules! endian_type {
    ($old_type:ident, $new_type:ident, $to_new:ident, $from_new:ident) => {
        /// An unsigned integer type of with an explicit endianness.
        ///
        /// See module level documentation for examples.
        #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
        pub struct $new_type($old_type);

        impl $new_type {
            fn _assert() {
                const_assert!(align_of::<$new_type>() == align_of::<$old_type>());
                const_assert!(size_of::<$new_type>() == size_of::<$old_type>());
            }

            /// Converts `self` to the native endianness.
            pub fn to_native(self) -> $old_type {
                $old_type::$from_new(self.0)
            }
        }

        // SAFETY: Safe because we are using this for implementing ByteValued for endian types
        // which are POD.
        unsafe impl ByteValued for $new_type {}

        impl PartialEq<$old_type> for $new_type {
            fn eq(&self, other: &$old_type) -> bool {
                self.0 == $old_type::$to_new(*other)
            }
        }

        impl PartialEq<$new_type> for $old_type {
            fn eq(&self, other: &$new_type) -> bool {
                $old_type::$to_new(other.0) == *self
            }
        }

        impl From<$new_type> for $old_type {
            fn from(v: $new_type) -> $old_type {
                v.to_native()
            }
        }

        impl From<$old_type> for $new_type {
            fn from(v: $old_type) -> $new_type {
                $new_type($old_type::$to_new(v))
            }
        }
    };
}

endian_type!(u16, Le16, to_le, from_le);
endian_type!(u32, Le32, to_le, from_le);
endian_type!(u64, Le64, to_le, from_le);
endian_type!(usize, LeSize, to_le, from_le);
endian_type!(u16, Be16, to_be, from_be);
endian_type!(u32, Be32, to_be, from_be);
endian_type!(u64, Be64, to_be, from_be);
endian_type!(usize, BeSize, to_be, from_be);

#[cfg(test)]
mod tests {
    #![allow(clippy::undocumented_unsafe_blocks)]
    use super::*;

    use std::convert::From;
    use std::mem::transmute;

    #[cfg(target_endian = "little")]
    const NATIVE_LITTLE: bool = true;
    #[cfg(target_endian = "big")]
    const NATIVE_LITTLE: bool = false;
    const NATIVE_BIG: bool = !NATIVE_LITTLE;

    macro_rules! endian_test {
        ($old_type:ty, $new_type:ty, $test_name:ident, $native:expr) => {
            mod $test_name {
                use super::*;

                #[allow(overflowing_literals)]
                #[test]
                fn test_endian_type() {
                    <$new_type>::_assert();

                    let v = 0x0123_4567_89AB_CDEF as $old_type;
                    let endian_v: $new_type = From::from(v);
                    let endian_into: $old_type = endian_v.into();
                    let endian_transmute: $old_type = unsafe { transmute(endian_v) };

                    if $native {
                        assert_eq!(endian_v, endian_transmute);
                    } else {
                        assert_eq!(endian_v, endian_transmute.swap_bytes());
                    }

                    assert_eq!(endian_into, v);
                    assert_eq!(endian_v.to_native(), v);

                    assert!(v == endian_v);
                    assert!(endian_v == v);
                }
            }
        };
    }

    endian_test!(u16, Le16, test_le16, NATIVE_LITTLE);
    endian_test!(u32, Le32, test_le32, NATIVE_LITTLE);
    endian_test!(u64, Le64, test_le64, NATIVE_LITTLE);
    endian_test!(usize, LeSize, test_le_size, NATIVE_LITTLE);
    endian_test!(u16, Be16, test_be16, NATIVE_BIG);
    endian_test!(u32, Be32, test_be32, NATIVE_BIG);
    endian_test!(u64, Be64, test_be64, NATIVE_BIG);
    endian_test!(usize, BeSize, test_be_size, NATIVE_BIG);
}