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}