vmm/dumbo/pdu/
bytes.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Defines traits which allow byte slices to be interpreted as sequences of bytes that stand for
5//! different values packed together using network byte ordering (such as network packets).
6//!
7//! The main use of these traits is reading and writing numerical values at a given offset in the
8//! underlying slice. Why are they needed? Given a byte slice, there are two approaches to
9//! reading/writing packet data that come to mind:
10//!
11//! (1) Have structs which represent the potential contents of each packet type, unsafely cast the
12//! bytes slice to a struct pointer/reference (after doing the required checks), and then use the
13//! newly obtained pointer/reference to access the data.
14//!
15//! (2) Access fields by reading bytes at the appropriate offset from the original slice.
16//!
17//! The first solution looks more appealing at first, but it requires some unsafe code. Moreover,
18//! de-referencing unaligned pointers or references is considered undefined behaviour in Rust, and
19//! it's not clear whether this undermines the approach or not. Until any further developments,
20//! the second option is used, based on the `NetworkBytes` implementation.
21//!
22//! What's with the `T: Deref<Target = [u8]>`? Is there really a need to be that generic?
23//! Not really. The logic in this crate currently expects to work with byte slices (`&[u8]` and
24//! `&mut [u8]`), but there's a significant inconvenience. Consider `NetworkBytes` is defined as:
25//!
26//! ```
27//! struct NetworkBytes<'a> {
28//!     bytes: &'a [u8],
29//! }
30//! ```
31//!
32//! This is perfectly fine for reading values from immutable slices, but what about writing values?
33//! Implementing methods such as `fn write_something(&mut self)`, is not really possible, because
34//! even with a mutable reference to `self`, `self.bytes` is still an immutable slice. On the other
35//! hand, `NetworkBytes` can be defined as:
36//!
37//! ```
38//! struct NetworkBytes<'a> {
39//!     bytes: &'a mut [u8],
40//! }
41//! ```
42//!
43//! This allows both reads and writes, but requires a mutable reference at all times (and it looks
44//! weird to use one for immutable operations). This is where one interesting feature of Rust
45//! comes in handy; given a type `Something<T>`, it's possible to  implement different features
46//! depending on trait bounds on `T`. For `NetworkBytes`, if `T` implements `Deref<Target = [u8]>`
47//! (which `&[u8]` does), read operations are possible to define. If `T` implements
48//! `DerefMut<Target = [u8]>`, write operations are also a possibility. Since
49//! `DerefMut<Target = [u8]>` implies `Deref<Target = [u8]>`, `NetworkBytes<&mut [u8]>` implements
50//! both read and write operations.
51//!
52//! This can theoretically lead to code bloat when using both `&[u8]` and `&mut [u8]` (as opposed
53//! to just `&mut [u8]`), but most calls should be inlined anyway, so it probably doesn't matter
54//! in the end. `NetworkBytes` itself implements `Deref` (and `DerefMut` when `T: DerefMut`), so
55//! this line of reasoning can be extended to structs which represent different kinds of protocol
56//! data units (such as IPv4 packets, Ethernet frames, etc.).
57//!
58//! Finally, why `Deref` and not something like `AsRef`? The answer is `Deref` coercion, which in
59//! this case means that a `NetworkBytes` value will automatically coerce to `&[u8]`
60//! (or `&mut [u8]`), without having to go through an explicit `as_ref()` call, which makes the
61//! code easier to work with.
62//!
63//! Method names have the **unchecked** suffix as a reminder they do not check whether the
64//! read/write goes beyond the boundaries of a slice. Callers must take the necessary precautions
65//! to avoid panics.
66
67use std::fmt::Debug;
68use std::marker::PhantomData;
69use std::ops::{Deref, DerefMut};
70
71use crate::utils::byte_order;
72
73/// Represents an immutable view into a sequence of bytes which stands for different values packed
74/// together using network byte ordering.
75pub trait NetworkBytes: Deref<Target = [u8]> {
76    /// Reads an `u16` value from the specified offset, converting it to host byte ordering.
77    ///
78    /// # Panics
79    ///
80    /// This method will panic if `offset` is invalid.
81    #[inline]
82    fn ntohs_unchecked(&self, offset: usize) -> u16 {
83        // The unwrap() can fail when the offset is invalid, or there aren't enough bytes (2 in this
84        // case) left until the end of the slice. The caller must ensure this doesn't happen (hence
85        // the `unchecked` suffix).
86        byte_order::read_be_u16(&self[offset..])
87    }
88
89    /// Reads an `u32` value from the specified offset, converting it to host byte ordering.
90    ///
91    /// # Panics
92    ///
93    /// This method will panic if `offset` is invalid.
94    #[inline]
95    fn ntohl_unchecked(&self, offset: usize) -> u32 {
96        byte_order::read_be_u32(&self[offset..])
97    }
98
99    /// Shrinks the current slice to the given `len`.
100    ///
101    /// Does not check whether `len` is actually smaller than `self.len()`.
102    ///
103    /// # Panics
104    ///
105    /// This method will panic if `len` is greater than `self.len()`.
106    fn shrink_unchecked(&mut self, len: usize);
107}
108
109/// Offers mutable access to a sequence of bytes which stands for different values packed
110/// together using network byte ordering.
111pub trait NetworkBytesMut: NetworkBytes + DerefMut<Target = [u8]> {
112    /// Writes the given `u16` value at the specified `offset` using network byte ordering.
113    ///
114    /// # Panics
115    ///
116    /// If `value` cannot be written into `self` at the given `offset`
117    /// (e.g. if `offset > self.len() - size_of::<u16>()`).
118    #[inline]
119    fn htons_unchecked(&mut self, offset: usize, value: u16) {
120        assert!(offset <= self.len() - std::mem::size_of::<u16>());
121        byte_order::write_be_u16(&mut self[offset..], value)
122    }
123
124    /// Writes the given `u32` value at the specified `offset` using network byte ordering.
125    ///
126    /// # Panics
127    ///
128    /// If `value` cannot be written into `self` at the given `offset`
129    /// (e.g. if `offset > self.len() - size_of::<u32>()`).
130    #[inline]
131    fn htonl_unchecked(&mut self, offset: usize, value: u32) {
132        assert!(offset <= self.len() - std::mem::size_of::<u32>());
133        byte_order::write_be_u32(&mut self[offset..], value)
134    }
135}
136
137impl NetworkBytes for &[u8] {
138    #[inline]
139    fn shrink_unchecked(&mut self, len: usize) {
140        *self = &self[..len];
141    }
142}
143impl NetworkBytes for &mut [u8] {
144    #[inline]
145    fn shrink_unchecked(&mut self, len: usize) {
146        *self = &mut std::mem::take(self)[..len];
147    }
148}
149
150impl NetworkBytesMut for &mut [u8] {}
151
152// This struct is used as a convenience for any type which contains a generic member implementing
153// NetworkBytes with a lifetime, so we don't have to also add the PhantomData member each time. We
154// use pub(super) here because we only want this to be usable by the child modules of `pdu`.
155#[derive(Debug)]
156pub(super) struct InnerBytes<'a, T: 'a> {
157    bytes: T,
158    phantom: PhantomData<&'a T>,
159}
160
161impl<T: Debug> InnerBytes<'_, T> {
162    /// Creates a new instance as a wrapper around `bytes`.
163    #[inline]
164    pub fn new(bytes: T) -> Self {
165        InnerBytes {
166            bytes,
167            phantom: PhantomData,
168        }
169    }
170}
171
172impl<T: Deref<Target = [u8]> + Debug> Deref for InnerBytes<'_, T> {
173    type Target = [u8];
174
175    #[inline]
176    fn deref(&self) -> &[u8] {
177        self.bytes.deref()
178    }
179}
180
181impl<T: DerefMut<Target = [u8]> + Debug> DerefMut for InnerBytes<'_, T> {
182    #[inline]
183    fn deref_mut(&mut self) -> &mut [u8] {
184        self.bytes.deref_mut()
185    }
186}
187
188impl<T: NetworkBytes + Debug> NetworkBytes for InnerBytes<'_, T> {
189    #[inline]
190    fn shrink_unchecked(&mut self, len: usize) {
191        self.bytes.shrink_unchecked(len);
192    }
193}
194
195impl<T: NetworkBytesMut + Debug> NetworkBytesMut for InnerBytes<'_, T> {}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    #[should_panic]
203    fn test_htons_unchecked() {
204        let mut buf = [u8::default(); std::mem::size_of::<u16>()];
205        let mut a = buf.as_mut();
206        a.htons_unchecked(1, u16::default());
207    }
208
209    #[test]
210    #[should_panic]
211    fn test_htonl_unchecked() {
212        let mut buf = [u8::default(); std::mem::size_of::<u32>()];
213        let mut a = buf.as_mut();
214        a.htonl_unchecked(1, u32::default());
215    }
216
217    #[test]
218    fn test_network_bytes() {
219        let mut buf = [0u8; 1000];
220
221        {
222            let mut a = buf.as_mut();
223
224            a.htons_unchecked(1, 123);
225            a.htonl_unchecked(100, 1234);
226
227            assert_eq!(a.ntohs_unchecked(1), 123);
228            assert_eq!(a.ntohl_unchecked(100), 1234);
229
230            a.shrink_unchecked(500);
231
232            assert_eq!(a.len(), 500);
233            assert_eq!(a.ntohs_unchecked(1), 123);
234            assert_eq!(a.ntohl_unchecked(100), 1234);
235        }
236
237        {
238            let mut b = buf.as_ref();
239            b.shrink_unchecked(500);
240
241            assert_eq!(b.len(), 500);
242            assert_eq!(b.ntohs_unchecked(1), 123);
243            assert_eq!(b.ntohl_unchecked(100), 1234);
244        }
245    }
246}