Skip to main content

smb_server_proto/
endian.rs

1//! Explicit-endian integer reads/writes.
2//!
3//! SMB is little-endian on the wire regardless of host architecture. These
4//! helpers make the byte order explicit at every call site so the code is
5//! correct on big-endian machines too.
6
7/// Read a little-endian unsigned integer of `N` bytes starting at `buf[pos]`.
8///
9/// Works for any N ≤ 8; the result is zero-extended to u64 and the caller
10/// casts/truncates to the desired width.
11#[inline(always)]
12pub fn read_le(buf: &[u8], pos: usize, n: usize) -> Result<u64, crate::error::ProtoError> {
13    if pos + n > buf.len() {
14        return Err(crate::error::ProtoError::Overrun {
15            need: n,
16            at: pos,
17            have: buf.len(),
18        });
19    }
20    let mut v: u64 = 0;
21    for i in 0..n {
22        v |= (buf[pos + i] as u64) << (8 * i);
23    }
24    Ok(v)
25}
26
27/// Write a little-endian unsigned integer of `n` bytes into `buf` at `pos`.
28#[inline(always)]
29pub fn write_le(buf: &mut [u8], pos: usize, val: u64, n: usize) {
30    for i in 0..n {
31        buf[pos + i] = ((val >> (8 * i)) & 0xff) as u8;
32    }
33}
34
35macro_rules! impl_le {
36    ($name:ident, $ty:ty, $size:expr) => {
37        /// Read a little-endian value from the buffer at `pos`.
38        #[inline(always)]
39        pub fn $name(buf: &[u8], pos: usize) -> Result<$ty, crate::error::ProtoError> {
40            if pos + $size > buf.len() {
41                return Err(crate::error::ProtoError::Overrun {
42                    need: $size,
43                    at: pos,
44                    have: buf.len(),
45                });
46            }
47            let mut bytes = [0u8; $size];
48            bytes.copy_from_slice(&buf[pos..pos + $size]);
49            Ok(<$ty>::from_le_bytes(bytes))
50        }
51    };
52}
53
54impl_le!(read_u8_le, u8, 1);
55impl_le!(read_u16_le, u16, 2);
56impl_le!(read_u32_le, u32, 4);
57impl_le!(read_u64_le, u64, 8);
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn little_endian_reads() {
65        let buf = [0x34, 0x12, 0x78, 0x56, 0xEF, 0xCD, 0xAB, 0x90];
66        assert_eq!(read_u8_le(&buf, 0).unwrap(), 0x34);
67        assert_eq!(read_u16_le(&buf, 0).unwrap(), 0x1234);
68        assert_eq!(read_u32_le(&buf, 0).unwrap(), 0x5678_1234);
69        assert_eq!(read_u64_le(&buf, 0).unwrap(), 0x90AB_CDEF_5678_1234);
70    }
71
72    #[test]
73    fn overrun_returns_error() {
74        let buf = [0u8; 2];
75        assert!(read_u32_le(&buf, 0).is_err());
76    }
77
78    #[test]
79    fn works_on_synthetic_big_endian_host() {
80        // We cannot actually switch the host's endianness, but the
81        // from_le_bytes / to_le_bytes approach guarantees correctness
82        // regardless of what `as` casting would do.
83        let raw: [u8; 2] = [0x01, 0x02];
84        // On LE: from_le_bytes([1,2]) = 0x0201
85        // On BE: same, because we use from_le_bytes explicitly
86        assert_eq!(u16::from_le_bytes(raw), 0x0201);
87    }
88}