catnip/
lib.rs

1//! A no-std, panic-never, heapless, minimally-featured UDP/IP stack for bare-metal.
2//! Intended for fixed-time data acquisition and controls on LAN.
3//!
4//! This crate currently relies on the nightly channel, and as a result, may break regularly
5//! until the required features stabilize.
6//!
7//! Makes use of const generic expressions to provide flexibility in,
8//! and guaranteed correctness of, lengths of headers and data segments without an allocator.
9//!
10//! This library is under active development; major functionality is yet to
11//! be implemented and I'm sure some bugs are yet to be found.
12//!
13//! ```rust
14//! use catnip::*;
15//!
16//! // Some made-up data with two 32-bit words' worth of bytes and some arbitrary addresses
17//! let data: ByteArray<8> = ByteArray([0, 1, 2, 3, 4, 5, 6, 7]);
18//!
19//! // Build frame
20//! let mut frame = EthernetFrame::<IpV4Frame<UdpFrame<ByteArray<8>>>> {
21//!     header: EthernetHeader {
22//!         dst_macaddr: MacAddr::BROADCAST,
23//!         src_macaddr: MacAddr::new([0x02, 0xAF, 0xFF, 0x1A, 0xE5, 0x3C]),
24//!         ethertype: EtherType::IpV4,
25//!     },
26//!     data: IpV4Frame::<UdpFrame<ByteArray<8>>> {
27//!         header: IpV4Header {
28//!             version_and_header_length: VersionAndHeaderLength::new().with_version(4).with_header_length((IpV4Header::BYTE_LEN / 4) as u8),
29//!             dscp: DSCP::Standard,
30//!             total_length: IpV4Frame::<UdpFrame<ByteArray<8>>>::BYTE_LEN as u16,
31//!             identification: 0,
32//!             fragmentation: Fragmentation::default(),
33//!             time_to_live: 10,
34//!             protocol: Protocol::Udp,
35//!             checksum: 0,
36//!             src_ipaddr: IpV4Addr::new([10, 0, 0, 120]),
37//!             dst_ipaddr: IpV4Addr::new([10, 0, 0, 121]),
38//!         },
39//!         data: UdpFrame::<ByteArray<8>> {
40//!             header: UdpHeader {
41//!                 src_port: 8123,
42//!                 dst_port: 8125,
43//!                 length: UdpFrame::<ByteArray<8>>::BYTE_LEN as u16,
44//!                 checksum: 0,
45//!             },
46//!             data: data,
47//!         },
48//!     },
49//!     checksum: 0_u32,
50//! };
51//!
52//! // Calculate IP and UDP checksums
53//! frame.data.data.header.checksum = calc_udp_checksum(&frame.data);
54//! frame.data.header.checksum = calc_ip_checksum(&frame.data.header.to_be_bytes());
55//!
56//! // Reduce to bytes
57//! let bytes = frame.to_be_bytes();
58//!
59//! // Parse from bytes
60//! let frame_parsed = EthernetFrame::<IpV4Frame<UdpFrame<ByteArray<8>>>>::read_bytes(&bytes);
61//! assert_eq!(frame_parsed, frame);
62//! ```
63
64#![no_std]
65#![allow(dead_code)]
66#![deny(missing_docs)]
67#![feature(generic_const_exprs)]
68
69#[cfg(feature = "panic_never")]
70use panic_never as _;
71
72pub use byte_struct::{ByteStruct, ByteStructLen};
73pub use modular_bitfield;
74pub use ufmt::{derive::uDebug, uDebug, uDisplay, uWrite};
75
76pub mod enet; // Link Layer
77pub mod ip; // Internet layer
78pub mod udp; // Transport layer
79
80pub mod arp; // Address Resolution Protocol - not a distinct layer (between link and transport), but required for IP and UDP to function on most networks.
81pub mod dhcp; // Dynamic Host Configuration Protocol - for negotiating an IP address from a router/switch. Uses UDP.
82
83pub use arp::*;
84pub use dhcp::*;
85pub use enet::*;
86pub use ip::*;
87pub use udp::*;
88
89/// Standard 6-byte MAC address.
90/// Split 24/24 format, Block ID | Device ID .
91/// Locally-administered addresses are [0x02, ...], [0x06, ...], [0x0A, ...], [0x0E, ...]
92pub type MacAddr = ByteArray<6>;
93
94impl MacAddr {
95    /// New from bytes
96    pub fn new(v: [u8; 6]) -> Self {
97        ByteArray(v)
98    }
99
100    /// Broadcast address (all ones)
101    pub const BROADCAST: MacAddr = ByteArray([0xFF_u8; 6]);
102
103    /// Any address (all zeroes)
104    pub const ANY: MacAddr = ByteArray([0x0_u8; 6]);
105}
106
107/// IPV4 address as bytes
108pub type IpV4Addr = ByteArray<4>;
109
110impl IpV4Addr {
111    /// New from bytes
112    pub fn new(v: [u8; 4]) -> Self {
113        ByteArray(v)
114    }
115
116    /// Broadcast address (all ones)
117    pub const BROADCAST: IpV4Addr = ByteArray([0xFF_u8; 4]);
118
119    /// LAN broadcast address (all ones)
120    pub const BROADCAST_LOCAL: IpV4Addr = ByteArray([0x0, 0x0, 0x0, 0xFF]);
121
122    /// Any address (all zeroes)
123    pub const ANY: IpV4Addr = ByteArray([0x0_u8; 4]);
124}
125
126/// Common choices of transport-layer protocols and their IP header values.
127/// There are many more protocols not listed here.
128/// See <https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers>.
129#[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)]
130#[repr(u8)]
131pub enum Protocol {
132    /// Transmission Control Protocol
133    Tcp = 0x06,
134    /// User Datagram Protocol
135    Udp = 0x11,
136    /// Unimplemented
137    Unimplemented,
138}
139
140impl ByteStructLen for Protocol {
141    const BYTE_LEN: usize = 1;
142}
143
144impl ByteStruct for Protocol {
145    fn read_bytes(bytes: &[u8]) -> Self {
146        return match bytes[0] {
147            x if x == (Protocol::Tcp as u8) => Protocol::Tcp,
148            x if x == (Protocol::Udp as u8) => Protocol::Udp,
149            _ => Protocol::Unimplemented,
150        };
151    }
152
153    fn write_bytes(&self, bytes: &mut [u8]) {
154        bytes[0] = *self as u8;
155    }
156}
157
158impl Protocol {
159    fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] {
160        (*self as u8).to_be_bytes()
161    }
162}
163
164/// Type-of-Service for networks with differentiated services.
165/// See <https://en.wikipedia.org/wiki/Differentiated_services>.
166#[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)]
167#[repr(u8)]
168pub enum DSCP {
169    /// Standard is almost always fine
170    Standard = 0,
171    /// Realtime is rarely used
172    Realtime = 32 << 2,
173    /// Catch-all for the many other kinds or invalid bit patterns
174    Unimplemented,
175}
176
177impl ByteStructLen for DSCP {
178    const BYTE_LEN: usize = 1;
179}
180
181impl ByteStruct for DSCP {
182    fn read_bytes(bytes: &[u8]) -> Self {
183        return match bytes[0] {
184            x if x == (DSCP::Standard as u8) => DSCP::Standard,
185            x if x == (DSCP::Realtime as u8) => DSCP::Realtime,
186            _ => DSCP::Unimplemented,
187        };
188    }
189
190    fn write_bytes(&self, bytes: &mut [u8]) {
191        bytes[0] = *self as u8;
192    }
193}
194
195impl DSCP {
196    fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] {
197        (*self as u8).to_be_bytes()
198    }
199}
200
201/// Newtype for [u8; N] in order to be able to implement traits.
202#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
203#[repr(transparent)]
204pub struct ByteArray<const N: usize>(pub [u8; N]);
205
206impl<const N: usize> ByteStructLen for ByteArray<N> {
207    const BYTE_LEN: usize = N;
208}
209
210impl<const N: usize> ByteStruct for ByteArray<N> {
211    fn read_bytes(bytes: &[u8]) -> Self {
212        let mut out = [0_u8; N];
213        out.copy_from_slice(&bytes[0..N]);
214        ByteArray(out)
215    }
216
217    fn write_bytes(&self, bytes: &mut [u8]) {
218        for i in 0..N {
219            bytes[i] = self.0[i];
220        }
221    }
222}
223
224impl<const N: usize> ByteArray<N> {
225    /// Convert to big-endian byte array
226    pub fn to_be_bytes(&self) -> [u8; N] {
227        self.0
228    }
229}
230
231impl uDebug for ByteArray<4> {
232    fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
233    where
234        W: uWrite + ?Sized,
235    {
236        <[u8; 4] as uDebug>::fmt(&self.0, f)
237    }
238}
239
240impl uDebug for ByteArray<6> {
241    fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
242    where
243        W: uWrite + ?Sized,
244    {
245        <[u8; 6] as uDebug>::fmt(&self.0, f)
246    }
247}
248
249/// Derive To/From with an added "Unknown" variant catch-all for converting
250/// from numerical values that do not match a valid variant in order to
251/// avoid either panicking or cumbersome error handling.
252///
253/// Yoinked shamelessly (with some modification) from smoltcp.
254#[macro_export]
255macro_rules! enum_with_unknown {
256    (
257        $( #[$enum_attr:meta] )*
258        pub enum $name:ident($ty:ty) {
259            $(
260              $( #[$variant_attr:meta] )*
261              $variant:ident = $value:expr
262            ),+ $(,)?
263        }
264    ) => {
265        #[derive(Debug, uDebug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
266        $( #[$enum_attr] )*
267        pub enum $name {
268            $(
269              $( #[$variant_attr] )*
270              $variant
271            ),*,
272            /// Catch-all for values that do not match a variant
273            Unknown($ty)
274        }
275
276        impl ::core::convert::From<$ty> for $name {
277            fn from(value: $ty) -> Self {
278                match value {
279                    $( $value => $name::$variant ),*,
280                    other => $name::Unknown(other)
281                }
282            }
283        }
284
285        impl ::core::convert::From<$name> for $ty {
286            fn from(value: $name) -> Self {
287                match value {
288                    $( $name::$variant => $value ),*,
289                    $name::Unknown(other) => other
290                }
291            }
292        }
293    }
294}
295
296/// Calculate IP checksum per IETF-RFC-768
297/// following implementation guide in IETF-RFC-1071 section 4.1 .
298/// See <https://datatracker.ietf.org/doc/html/rfc1071#section-4> .
299/// This function is provided for convenience and is not used directly.
300pub fn calc_ip_checksum(data: &[u8]) -> u16 {
301    // Partial calc
302    let sum = calc_ip_checksum_incomplete(data);
303    // Fold and flip
304    let checksum = calc_ip_checksum_finalize(sum);
305
306    checksum
307}
308
309/// Finalize an IP checksum by folding the accumulator from an [i32]
310/// to a [u16] and taking the one's complement
311pub fn calc_ip_checksum_finalize(sum: u32) -> u16 {
312    // Copy to avoid mutating the input, which may be used for something else
313    // since some checksums relate to overlapping data
314    let mut sum = sum;
315
316    // Fold 32-bit accumulator into 16 bits
317    // The spec does this in a while-loop, but the maximum possible number of times
318    // needed to guarantee success is 2, so we do it 3 times here to provide both
319    // guaranteed correctness and deterministic operation.
320    sum = (sum & 0xffff).wrapping_add(sum >> 16);
321    sum = (sum & 0xffff).wrapping_add(sum >> 16);
322    sum = (sum & 0xffff).wrapping_add(sum >> 16);
323
324    // Convert to u16 and take bitwise complement
325    let checksum = !(sum as u16);
326
327    checksum
328}
329
330/// Calculate an IP checksum on incomplete data
331/// returning the unfolded accumulator as [i32]
332///
333/// This is a slowish method by about a factor of 2-4.
334/// It would be faster to case pairs of bytes to u16,
335/// but this method avoids generating panic branches in slice operations.
336pub fn calc_ip_checksum_incomplete(data: &[u8]) -> u32 {
337    let mut sum: u32 = 0;
338    let mut i: usize = 0;
339
340    for x in data {
341        if i % 2 == 0 {
342            sum += (*x as u32) << 8;
343        } else {
344            sum += *x as u32;
345        };
346
347        i += 1;
348    }
349
350    sum
351}
352
353#[cfg(test)]
354mod test {
355
356    use crate::*;
357    extern crate std;
358    use std::*;
359
360    #[test]
361    fn test_calc_ip_checksum() -> () {
362        let src_ipaddr: IpV4Addr = IpV4Addr::new([10, 0, 0, 1]);
363        let dst_ipaddr: IpV4Addr = IpV4Addr::new([10, 0, 0, 2]);
364        let mut sample_ipv4_header = IpV4Header {
365            version_and_header_length: VersionAndHeaderLength::new()
366                .with_version(4)
367                .with_header_length((IpV4Header::BYTE_LEN / 4) as u8),
368            dscp: DSCP::Standard,
369            total_length: IpV4Frame::<UdpFrame<ByteArray<8>>>::BYTE_LEN as u16,
370            identification: 0,
371            fragmentation: Fragmentation::default(),
372            time_to_live: 10,
373            protocol: Protocol::Udp,
374            checksum: 0,
375            src_ipaddr: src_ipaddr,
376            dst_ipaddr: dst_ipaddr,
377        };
378        let checksum_pre = calc_ip_checksum(&sample_ipv4_header.to_be_bytes());
379        sample_ipv4_header.checksum = checksum_pre;
380        let checksum_post = calc_ip_checksum(&sample_ipv4_header.to_be_bytes());
381
382        assert!(checksum_post == 0)
383    }
384}