Skip to main content

pktkit/
frame.rs

1use crate::{EtherType, MacAddr};
2use core::fmt;
3
4/// A raw Ethernet frame.
5///
6/// `Frame` is a `#[repr(transparent)]` newtype around `[u8]`, exactly mirroring
7/// Go's `type Frame []byte`. `&Frame` is `#[repr(transparent)]` around `&[u8]`,
8/// so the typed accessors never copy or allocate.
9///
10/// Owned construction goes through [`build_frame`], which returns a `Vec<u8>`
11/// that you can then borrow as `&Frame` via [`Frame::from_slice`].
12///
13/// ```
14/// # use pktkit::{Frame, MacAddr, EtherType, build_frame};
15/// let a: MacAddr = "00:11:22:33:44:55".parse().unwrap();
16/// let b: MacAddr = "aa:bb:cc:dd:ee:ff".parse().unwrap();
17/// let buf = build_frame(a, b, EtherType::IPV4, &[0u8; 20]);
18/// let f = Frame::from_slice(&buf);
19/// assert!(f.is_valid());
20/// assert_eq!(f.dst_mac(), Some(a));
21/// assert_eq!(f.src_mac(), Some(b));
22/// assert_eq!(f.ether_type(), EtherType::IPV4);
23/// ```
24#[repr(transparent)]
25pub struct Frame(pub [u8]);
26
27impl Frame {
28    /// Wrap an existing byte slice as a `&Frame`. No allocation, no copy.
29    #[inline]
30    pub fn from_slice(b: &[u8]) -> &Frame {
31        // SAFETY: Frame is `#[repr(transparent)]` over `[u8]`, so the layouts
32        // of `&[u8]` and `&Frame` are identical.
33        unsafe { &*(b as *const [u8] as *const Frame) }
34    }
35
36    /// Wrap an existing mutable byte slice as a `&mut Frame`.
37    #[inline]
38    pub fn from_mut(b: &mut [u8]) -> &mut Frame {
39        // SAFETY: see `from_slice`.
40        unsafe { &mut *(b as *mut [u8] as *mut Frame) }
41    }
42
43    /// Borrow the underlying bytes.
44    #[inline]
45    pub fn as_bytes(&self) -> &[u8] {
46        &self.0
47    }
48
49    /// Borrow the underlying bytes mutably.
50    #[inline]
51    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
52        &mut self.0
53    }
54
55    /// Copy the frame into an owned buffer.
56    #[inline]
57    pub fn to_vec(&self) -> Vec<u8> {
58        self.0.to_vec()
59    }
60
61    /// Total frame length in bytes (header + payload).
62    #[inline]
63    pub fn len(&self) -> usize {
64        self.0.len()
65    }
66
67    #[inline]
68    pub fn is_empty(&self) -> bool {
69        self.0.is_empty()
70    }
71
72    /// True if the frame is at least 14 bytes (one Ethernet header).
73    #[inline]
74    pub fn is_valid(&self) -> bool {
75        self.0.len() >= 14
76    }
77
78    /// Destination MAC, or `None` if the frame is too short.
79    pub fn dst_mac(&self) -> Option<MacAddr> {
80        if self.0.len() < 14 {
81            return None;
82        }
83        let mut o = [0u8; 6];
84        o.copy_from_slice(&self.0[0..6]);
85        Some(MacAddr(o))
86    }
87
88    /// Source MAC, or `None` if the frame is too short.
89    pub fn src_mac(&self) -> Option<MacAddr> {
90        if self.0.len() < 14 {
91            return None;
92        }
93        let mut o = [0u8; 6];
94        o.copy_from_slice(&self.0[6..12]);
95        Some(MacAddr(o))
96    }
97
98    /// True if the frame carries an 802.1Q VLAN tag.
99    #[inline]
100    pub fn has_vlan(&self) -> bool {
101        self.0.len() >= 18 && raw_ether_type(&self.0) == EtherType::VLAN.as_u16()
102    }
103
104    /// VLAN identifier (12 bits). Returns 0 when [`has_vlan`](Self::has_vlan) is false.
105    pub fn vlan_id(&self) -> u16 {
106        if !self.has_vlan() {
107            return 0;
108        }
109        u16::from_be_bytes([self.0[14], self.0[15]]) & 0x0FFF
110    }
111
112    /// VLAN priority code point (3 bits). Returns 0 when the frame is untagged.
113    pub fn vlan_pcp(&self) -> u8 {
114        if !self.has_vlan() {
115            return 0;
116        }
117        self.0[14] >> 5
118    }
119
120    /// VLAN drop-eligible indicator. False when the frame is untagged.
121    pub fn vlan_dei(&self) -> bool {
122        self.has_vlan() && self.0[14] & 0x10 != 0
123    }
124
125    /// The full 16-bit tag control information field, or `None` when untagged.
126    pub fn vlan_tci(&self) -> Option<u16> {
127        if !self.has_vlan() {
128            return None;
129        }
130        Some(u16::from_be_bytes([self.0[14], self.0[15]]))
131    }
132
133    /// Protocol type of the payload, transparently handling a VLAN tag.
134    pub fn ether_type(&self) -> EtherType {
135        if self.0.len() < 14 {
136            return EtherType(0);
137        }
138        let et = raw_ether_type(&self.0);
139        if et == EtherType::VLAN.as_u16() && self.0.len() >= 18 {
140            return EtherType(u16::from_be_bytes([self.0[16], self.0[17]]));
141        }
142        EtherType(et)
143    }
144
145    /// Number of header bytes (14 normally, 18 with a VLAN tag).
146    pub fn header_len(&self) -> usize {
147        if self.has_vlan() { 18 } else { 14 }
148    }
149
150    /// Frame payload (everything after the Ethernet header). Empty if the
151    /// frame is shorter than the header.
152    pub fn payload(&self) -> &[u8] {
153        let hl = self.header_len();
154        if self.0.len() < hl {
155            return &[];
156        }
157        &self.0[hl..]
158    }
159
160    /// Mutable view of the payload.
161    pub fn payload_mut(&mut self) -> &mut [u8] {
162        let hl = self.header_len();
163        if self.0.len() < hl {
164            return &mut [];
165        }
166        &mut self.0[hl..]
167    }
168
169    /// True if the destination is the all-ones broadcast.
170    pub fn is_broadcast(&self) -> bool {
171        self.0.len() >= 6 && self.0[..6] == [0xff; 6]
172    }
173
174    /// True if the destination has the IG bit set (broadcast is multicast).
175    pub fn is_multicast(&self) -> bool {
176        !self.0.is_empty() && self.0[0] & 1 != 0
177    }
178
179    /// Write a new destination MAC in place.
180    pub fn set_dst_mac(&mut self, mac: MacAddr) {
181        if self.0.len() < 14 {
182            return;
183        }
184        self.0[0..6].copy_from_slice(&mac.octets());
185    }
186
187    /// Write a new source MAC in place.
188    pub fn set_src_mac(&mut self, mac: MacAddr) {
189        if self.0.len() < 14 {
190            return;
191        }
192        self.0[6..12].copy_from_slice(&mac.octets());
193    }
194}
195
196#[inline]
197fn raw_ether_type(b: &[u8]) -> u16 {
198    u16::from_be_bytes([b[12], b[13]])
199}
200
201impl core::ops::Deref for Frame {
202    type Target = [u8];
203    #[inline]
204    fn deref(&self) -> &[u8] {
205        &self.0
206    }
207}
208
209impl AsRef<[u8]> for Frame {
210    #[inline]
211    fn as_ref(&self) -> &[u8] {
212        &self.0
213    }
214}
215
216impl PartialEq for Frame {
217    #[inline]
218    fn eq(&self, other: &Frame) -> bool {
219        self.0 == other.0
220    }
221}
222
223impl Eq for Frame {}
224
225impl core::hash::Hash for Frame {
226    #[inline]
227    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
228        self.0.hash(state)
229    }
230}
231
232impl fmt::Debug for Frame {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("Frame")
235            .field("len", &self.len())
236            .field("dst", &self.dst_mac())
237            .field("src", &self.src_mac())
238            .field("ether_type", &self.ether_type())
239            .finish()
240    }
241}
242
243/// Allocate and return a new Ethernet frame with the given header fields and
244/// payload. The result is a `Vec<u8>`; borrow it as `&Frame` via
245/// [`Frame::from_slice`].
246pub fn build_frame(dst: MacAddr, src: MacAddr, ether_type: EtherType, payload: &[u8]) -> Vec<u8> {
247    let mut v = Vec::with_capacity(14 + payload.len());
248    v.extend_from_slice(&dst.octets());
249    v.extend_from_slice(&src.octets());
250    v.extend_from_slice(&ether_type.as_u16().to_be_bytes());
251    v.extend_from_slice(payload);
252    v
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn build_and_inspect() {
261        let dst: MacAddr = "00:11:22:33:44:55".parse().unwrap();
262        let src: MacAddr = "aa:bb:cc:dd:ee:ff".parse().unwrap();
263        let payload = [1, 2, 3, 4, 5];
264        let buf = build_frame(dst, src, EtherType::IPV4, &payload);
265        let f = Frame::from_slice(&buf);
266
267        assert!(f.is_valid());
268        assert_eq!(f.dst_mac(), Some(dst));
269        assert_eq!(f.src_mac(), Some(src));
270        assert_eq!(f.ether_type(), EtherType::IPV4);
271        assert!(!f.has_vlan());
272        assert_eq!(f.vlan_tci(), None);
273        assert_eq!(f.header_len(), 14);
274        assert_eq!(f.payload(), &payload);
275        assert!(!f.is_broadcast());
276        assert!(!f.is_multicast());
277    }
278
279    #[test]
280    fn vlan_passthrough() {
281        // dst | src | 0x8100 | TCI(0x0001) | 0x0800 | ...
282        let mut buf = Vec::new();
283        buf.extend_from_slice(&[0xff; 6]); // dst broadcast
284        buf.extend_from_slice(&[0u8; 6]);
285        buf.extend_from_slice(&[0x81, 0x00]); // VLAN
286        buf.extend_from_slice(&[0x00, 0x01]); // VID=1
287        buf.extend_from_slice(&[0x08, 0x00]); // IPv4
288        buf.extend_from_slice(&[0xaa, 0xbb]); // payload
289
290        let f = Frame::from_slice(&buf);
291        assert!(f.is_valid());
292        assert!(f.has_vlan());
293        assert_eq!(f.vlan_id(), 1);
294        assert_eq!(f.vlan_pcp(), 0);
295        assert!(!f.vlan_dei());
296        assert_eq!(f.vlan_tci(), Some(1));
297        assert_eq!(f.ether_type(), EtherType::IPV4);
298        assert_eq!(f.header_len(), 18);
299        assert_eq!(f.payload(), &[0xaa, 0xbb]);
300        assert!(f.is_broadcast());
301        assert!(f.is_multicast());
302    }
303
304    #[test]
305    fn short_frame_is_invalid() {
306        let buf = [0u8; 5];
307        let f = Frame::from_slice(&buf);
308        assert!(!f.is_valid());
309        assert_eq!(f.dst_mac(), None);
310        assert_eq!(f.src_mac(), None);
311        assert_eq!(f.ether_type(), EtherType(0));
312        assert_eq!(f.payload(), &[] as &[u8]);
313    }
314
315    #[test]
316    fn mutable_setters() {
317        let buf = build_frame(MacAddr::zero(), MacAddr::zero(), EtherType::IPV4, &[]);
318        let mut owned = buf;
319        let f = Frame::from_mut(&mut owned);
320
321        let m: MacAddr = "12:34:56:78:9a:bc".parse().unwrap();
322        f.set_dst_mac(m);
323        f.set_src_mac(m);
324        assert_eq!(f.dst_mac(), Some(m));
325        assert_eq!(f.src_mac(), Some(m));
326    }
327}