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}