├── .github ├── dependabot.yml └── workflows │ └── github-actions.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── arp.rs ├── dhcp.rs └── udp.rs ├── rust-toolchain.toml ├── semver-check.sh ├── src ├── arp.rs ├── dhcp.rs ├── enet.rs ├── ip.rs ├── lib.rs └── udp.rs └── test └── test_no_std ├── Cargo.toml └── src └── main.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/github-actions.yaml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-22.04 6 | steps: 7 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 8 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 9 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 10 | - name: Check out repository code 11 | uses: actions/checkout@v3 12 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." 13 | - run: echo "🖥️ The workflow is now ready to test your code on the runner." 14 | - name: Install deps 15 | run: | 16 | #sudo apt update 17 | sudo apt install build-essential 18 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 19 | 20 | # A target with no base runtime is required to test no-std and panic-never 21 | rustup target add thumbv7em-none-eabihf 22 | - name: Build and test 23 | run: | 24 | # Run tests of all functionality except compatibility with panic-never and no-std 25 | cargo test --no-default-features -- --nocapture 26 | 27 | # Test docs 28 | cargo test --doc 29 | 30 | # Check for panic branches and no-std compatibility by building for an anonymous platform 31 | cd ./test/test_no_std 32 | cargo build --target thumbv7em-none-eabihf 33 | 34 | - run: echo "🍏 This job's status is ${{ job.status }}." 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | *.DS_Store 13 | 14 | tmp/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "catnip" 3 | version = "0.3.1" 4 | edition = "2021" 5 | description = "No-std, panic-never, heapless UDP/IP ethernet stack for data acquisition and real-time controls" 6 | homepage = "https://github.com/jlogan03/catnip" 7 | repository = "https://github.com/jlogan03/catnip" 8 | readme = "./README.md" 9 | license = "MIT OR Apache-2.0" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | byte_struct = "0.9.0" 15 | modular-bitfield = "0.11.2" 16 | static_assertions = "1.1.0" 17 | ufmt = "0.2.0" 18 | panic-never = { version = "0.1.0", optional = true } 19 | 20 | [features] 21 | default = ["no_std"] # Just for clarity; in fact, we are always no-std and panic-never compatible 22 | panic_never = ["panic-never"] # Bring in the actual panic-never panic handler 23 | no_std = [] 24 | 25 | [[example]] 26 | name = "udp" 27 | test = true 28 | 29 | [[example]] 30 | name = "arp" 31 | test = true 32 | 33 | [[example]] 34 | name = "dhcp" 35 | test = true -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 James Logan 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 James Logan 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # catnip 2 | 3 | A no-std, panic-never, heapless, minimally-featured UDP/IP stack for bare-metal. 4 | Intended for realtime data acquisition and controls on LAN. 5 | 6 | Makes use of const generic expressions to provide flexibility in, 7 | and guaranteed correctness of, lengths of headers and data segments without 8 | dynamic allocation. 9 | 10 | Because of this, the crate currently relies on the nightly channel, and as a result, may break regularly 11 | until the required features stabilize. 12 | 13 | This library is under active development; major functionality is yet to 14 | be implemented and I'm sure some bugs are yet to be found. 15 | 16 | Docs: https://docs.rs/catnip. 17 | 18 | # Example 19 | 20 | ```rust 21 | use catnip::*; 22 | 23 | // Some made-up data with two 32-bit words' worth of bytes and some arbitrary addresses 24 | let data: ByteArray<8> = ByteArray([0, 1, 2, 3, 4, 5, 6, 7]); 25 | 26 | // Build frame 27 | let frame = EthernetFrame::>>> { 28 | header: EthernetHeader { 29 | dst_macaddr: MacAddr::BROADCAST, 30 | src_macaddr: MacAddr::new([0x02, 0xAF, 0xFF, 0x1A, 0xE5, 0x3C]), 31 | ethertype: EtherType::IpV4, 32 | }, 33 | data: IpV4Frame::>> { 34 | header: IpV4Header { 35 | version_and_header_length: VersionAndHeaderLength::new().with_version(4).with_header_length((IpV4Header::BYTE_LEN / 4) as u8), 36 | dscp: DSCP::Standard, 37 | total_length: IpV4Frame::>>::BYTE_LEN as u16, 38 | identification: 0, 39 | fragmentation: Fragmentation::default(), 40 | time_to_live: 10, 41 | protocol: Protocol::Udp, 42 | checksum: 0, 43 | src_ipaddr: IpV4Addr::new([10, 0, 0, 120]), 44 | dst_ipaddr: IpV4Addr::new([10, 0, 0, 121]), 45 | }, 46 | data: UdpFrame::> { 47 | header: UdpHeader { 48 | src_port: 8123, 49 | dst_port: 8125, 50 | length: UdpFrame::>::BYTE_LEN as u16, 51 | checksum: 0, 52 | }, 53 | data: data, 54 | }, 55 | }, 56 | checksum: 0_u32, 57 | }; 58 | 59 | // Calculate IP and UDP checksums 60 | frame.data.data.header.checksum = calc_udp_checksum(&frame.data); 61 | frame.data.header.checksum = calc_ip_checksum(&frame.data.header.to_be_bytes()); 62 | 63 | // Reduce to big-endian network bytes 64 | let bytes = frame.to_be_bytes(); 65 | 66 | // Parse from bytes 67 | let frame_parsed = EthernetFrame::>>>::read_bytes(&bytes); 68 | assert_eq!(frame_parsed, frame); 69 | ``` 70 | 71 | # Features 72 | 73 | - Ethernet II frames 74 | - IPV4 75 | - UDP 76 | - ARP 77 | - DHCP (INFORM only) 78 | 79 | # To-do 80 | 81 | - Add UDP psuedo-socket trait w/ arbitrary sync/async send & receive functions 82 | - Move to stable once constants defined in traits become available for parametrizing generics 83 | 84 | # License 85 | 86 | Licensed under either of 87 | 88 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 89 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 90 | 91 | at your option. 92 | -------------------------------------------------------------------------------- /examples/arp.rs: -------------------------------------------------------------------------------- 1 | //! Build an example ARP message 2 | 3 | fn main() -> () { 4 | use catnip::*; 5 | 6 | let msg = ArpPayload::new( 7 | MacAddr::new([1, 2, 3, 4, 5, 6]), 8 | IpV4Addr::new([7, 8, 9, 10]), 9 | MacAddr::new([11, 12, 13, 14, 15, 16]), 10 | IpV4Addr::new([17, 18, 19, 20]), 11 | ArpOperation::Request, 12 | ); 13 | 14 | // Serialize 15 | let bytes: [u8; ArpPayload::BYTE_LEN] = msg.to_be_bytes(); 16 | 17 | // Deserialize 18 | let msg_parsed = ArpPayload::read_bytes(&bytes); 19 | 20 | assert_eq!(msg, msg_parsed); 21 | } -------------------------------------------------------------------------------- /examples/dhcp.rs: -------------------------------------------------------------------------------- 1 | 2 | fn main() -> () { 3 | use catnip::*; 4 | 5 | let dhcp_inform = DhcpFixedPayload::new_inform( 6 | IpV4Addr::new([1, 2, 3, 4]), 7 | MacAddr::new([5, 6, 7, 8, 9, 10]), 8 | 12345 9 | ); 10 | 11 | // Serialize 12 | let bytes = dhcp_inform.to_be_bytes(); 13 | // Deserialize 14 | let msg_parsed = DhcpFixedPayload::read_bytes(&bytes); 15 | 16 | assert_eq!(msg_parsed, dhcp_inform); 17 | } -------------------------------------------------------------------------------- /examples/udp.rs: -------------------------------------------------------------------------------- 1 | //! Build a UDP/IP Ethernet packet and get its representation as network bytes 2 | 3 | 4 | 5 | fn main() -> () { 6 | use catnip::*; 7 | 8 | // Some made-up data with two 32-bit words' worth of bytes 9 | let data: ByteArray<8> = ByteArray([0, 1, 2, 3, 4, 5, 6, 7]); 10 | 11 | // Arbitrary addresses 12 | let src_ipaddr: IpV4Addr = IpV4Addr::new([10, 0, 0, 120]); 13 | let dst_ipaddr: IpV4Addr = IpV4Addr::new([10, 0, 0, 121]); 14 | 15 | let frame = EthernetFrame::>>> { 16 | header: EthernetHeader { 17 | dst_macaddr: MacAddr::BROADCAST, 18 | src_macaddr: MacAddr::new([0x02, 0xAF, 0xFF, 0x1A, 0xE5, 0x3C]), 19 | ethertype: EtherType::IpV4, 20 | }, 21 | data: IpV4Frame::>> { 22 | header: IpV4Header { 23 | version_and_header_length: VersionAndHeaderLength::new().with_version(4).with_header_length((IpV4Header::BYTE_LEN / 4) as u8), 24 | dscp: DSCP::Standard, 25 | total_length: IpV4Frame::>>::BYTE_LEN as u16, 26 | identification: 0, 27 | fragmentation: Fragmentation::default(), 28 | time_to_live: 10, 29 | protocol: Protocol::Udp, 30 | checksum: 0, 31 | src_ipaddr: src_ipaddr, 32 | dst_ipaddr: dst_ipaddr, 33 | }, 34 | data: UdpFrame::> { 35 | header: UdpHeader { 36 | src_port: 8123, 37 | dst_port: 8125, 38 | length: UdpFrame::>::BYTE_LEN as u16, 39 | checksum: 0, 40 | }, 41 | data: data, 42 | }, 43 | }, 44 | checksum: 0_u32, 45 | }; 46 | 47 | let bytes = frame.to_be_bytes(); 48 | let frame_parsed = EthernetFrame::>>>::read_bytes(&bytes); 49 | 50 | assert_eq!(frame_parsed, frame); 51 | } 52 | 53 | #[test] 54 | fn test_packet() -> () { 55 | main(); 56 | } -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /semver-check.sh: -------------------------------------------------------------------------------- 1 | # Check whether this branch's changes need to be a major, minor, or patch 2 | 3 | branch_name=$(git symbolic-ref --short HEAD) 4 | 5 | git checkout main 6 | cargo +nightly rustdoc -- -Zunstable-options --output-format json 7 | mkdir tmp 8 | cp ./target/doc/catnip.json ./tmp/catnip-main.json 9 | 10 | git checkout $branch_name 11 | cargo +nightly rustdoc -- -Zunstable-options --output-format json 12 | cp ./target/doc/catnip.json ./tmp 13 | 14 | cargo semver-checks diff-files --current ./tmp/catnip.json --baseline ./tmp/catnip-main.json 15 | 16 | rm -rf ./tmp -------------------------------------------------------------------------------- /src/arp.rs: -------------------------------------------------------------------------------- 1 | //! Address Resolution Protocol implementation for IPV4. 2 | //! 3 | //! ARP is not a distinct network abstraction layer, but is still required for most networks to function 4 | //! because socket abstractions frequently require an ARP request and response to be completed before sending data 5 | //! even if the router is actually going to be handling the association between MAC addresses and IP addresses, 6 | //! so resolving the target's MAC address is not explicitly necessary. 7 | //! 8 | //! This is a noisy process, but on a statically-addressed network, it will ideally only occur once 9 | //! during network initialization or if a host resets its network drivers and needs to re-connect. 10 | //! In practice, most systems send out ARP requests periodically, with the period varying from seconds to hours. 11 | //! 12 | //! This process is not useful on a statically-addressed network, but on a mixed statically-and-dynamically-addressed network, it can help 13 | //! in the case where the target device does exist on the network, but has not yet sent a packet and does not have an entry in the 14 | //! router/switch's MAC address table. In that case, the broadcasted ARP request will still reach that device and produce a response, 15 | //! which will be noted by the router/switch and allow its MAC address table entry to be populated. 16 | //! 17 | //! It can also be useful for networks with not-smart network switches where the hosts have to self-assemble the addressing space, 18 | //! because ARP allows each host on the network to poll the others to check if an address is already taken before assigning 19 | //! that address to itself. The success of that method requires that all devices on the network be configured to respond to ARP requests, 20 | //! or to listen for conflicts and resolve them proactively, which is not necessarily the case. 21 | //! 22 | //! In any case, most network stacks that we might interact with seem to refuse to function without it. 23 | //! 24 | //! ```rust 25 | //! use catnip::*; 26 | //! 27 | //! let msg = ArpPayload::new( 28 | //! MacAddr::new([1, 2, 3, 4, 5, 6]), 29 | //! IpV4Addr::new([7, 8, 9, 10]), 30 | //! MacAddr::new([11, 12, 13, 14, 15, 16]), 31 | //! IpV4Addr::new([17, 18, 19, 20]), 32 | //! ArpOperation::Request, 33 | //! ); 34 | //! 35 | //! // Serialize 36 | //! let bytes: [u8; ArpPayload::BYTE_LEN] = msg.to_be_bytes(); 37 | //! 38 | //! // Deserialize 39 | //! let msg_parsed = ArpPayload::read_bytes(&bytes); 40 | //! 41 | //! assert_eq!(msg, msg_parsed); 42 | //! ``` 43 | 44 | use crate::*; 45 | 46 | use ufmt::derive::uDebug; 47 | use byte_struct::*; 48 | use static_assertions::const_assert; 49 | 50 | const_assert!(ArpPayload::BYTE_LEN == 46); // Make sure the ARP frame is at least sized for the minimum ethernet payload 51 | 52 | /// An ARP request or response with IPV4 addresses and standard MAC addresses. 53 | /// Assumes 6-byte standard MAC addresses and 4-byte IPV4 addresses; this function can't be as general as the parser. 54 | /// because we need to know the size of the output at compile time. 55 | /// See . 56 | #[derive(ByteStruct, Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)] 57 | #[byte_struct_be] 58 | pub struct ArpPayload { 59 | /// Hardware type (1 for ethernet) 60 | pub htype: u16, 61 | /// Protocol type (same as ethertype from ethernet header) 62 | pub ptype: ProtocolType, 63 | /// Hardware address length (6 for standard MAC) 64 | pub hlen: u8, 65 | /// Protocol address length (4 for IPV4) 66 | pub plen: u8, 67 | /// ARP operation type 68 | pub operation: ArpOperation, 69 | /// Source MAC address 70 | pub src_mac: MacAddr, 71 | /// Source IP address 72 | pub src_ipaddr: IpV4Addr, 73 | /// Destination MAC address 74 | pub dst_mac: MacAddr, 75 | /// Destination IP address 76 | pub dst_ipaddr: IpV4Addr, 77 | /// Pad to minimum frame size 78 | _pad0: u128, 79 | _pad1: u16 80 | } 81 | 82 | impl ArpPayload { 83 | /// Create a new ARP payload for IPV4 on ethernet 84 | pub fn new( 85 | src_mac: MacAddr, 86 | src_ipaddr: IpV4Addr, 87 | dst_mac: MacAddr, 88 | dst_ipaddr: IpV4Addr, 89 | operation: ArpOperation, 90 | ) -> Self { 91 | ArpPayload { 92 | htype: 1, // Always on ethernet 93 | ptype: ProtocolType::IpV4, // Always resolving an IPV4 address 94 | hlen: 6, 95 | plen: 4, 96 | operation: operation, 97 | src_mac: src_mac, 98 | src_ipaddr: src_ipaddr, 99 | dst_mac: dst_mac, 100 | dst_ipaddr: dst_ipaddr, 101 | _pad0: 0, 102 | _pad1: 0 103 | } 104 | } 105 | 106 | /// Convert to big-endian byte array 107 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 108 | let mut bytes = [0_u8; Self::BYTE_LEN]; 109 | self.write_bytes(&mut bytes); 110 | bytes 111 | } 112 | } 113 | 114 | /// ARP request or response flag values 115 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)] 116 | #[repr(u16)] 117 | pub enum ArpOperation { 118 | /// This is a request to confirm target IP address and acquire associated MAC address 119 | Request = 1, 120 | /// This is a response to confirm our IP address and provide associated MAC address 121 | Response = 2, 122 | /// Invalid operation 123 | Unimplemented, 124 | } 125 | 126 | impl From for ArpOperation { 127 | fn from(value: u16) -> Self { 128 | match value { 129 | x if x == ArpOperation::Request as u16 => ArpOperation::Request, 130 | x if x == ArpOperation::Response as u16 => ArpOperation::Response, 131 | _ => ArpOperation::Unimplemented, 132 | } 133 | } 134 | } 135 | 136 | impl ByteStructLen for ArpOperation { 137 | const BYTE_LEN: usize = 2; 138 | } 139 | 140 | impl ByteStruct for ArpOperation { 141 | fn read_bytes(bytes: &[u8]) -> Self { 142 | let mut bytes_read = [0_u8; 2]; 143 | bytes_read.copy_from_slice(&bytes[0..=1]); 144 | return ArpOperation::from(u16::from_be_bytes(bytes_read)); 145 | } 146 | 147 | fn write_bytes(&self, bytes: &mut [u8]) { 148 | let bytes_to_write = self.to_be_bytes(); 149 | bytes[0] = bytes_to_write[0]; 150 | bytes[1] = bytes_to_write[1]; 151 | } 152 | } 153 | 154 | impl ArpOperation { 155 | /// Convert to big-endian byte array 156 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 157 | (*self as u16).to_be_bytes() 158 | } 159 | } 160 | 161 | /// Protocol Type flags are the same as EtherType but must be reimplemented to avoid run-time recursion. 162 | /// See . 163 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)] 164 | #[repr(u16)] 165 | pub enum ProtocolType { 166 | /// Internet protocol version 4 167 | IpV4 = 0x0800, 168 | /// Address resolution protocol 169 | Arp = 0x0806, 170 | /// Tagged virtual LAN - if this tag is encountered, then this is not the real ethertype field, and we're reading an 802.1Q Vlan tag instead 171 | /// This crate does not support tagged Vlan, which is a trust-based and inefficient system. Untagged Vlan should be used instead. 172 | Vlan = 0x8100, 173 | /// Internet protocol version 6 174 | IpV6 = 0x86DD, 175 | /// EtherCat 176 | EtherCat = 0x88A4, 177 | /// Precision Time Protocol 178 | Ptp = 0x88A7, 179 | /// Catch-all for uncommon types not handled here 180 | Unimplemented = 0x0, 181 | } 182 | 183 | impl From for ProtocolType { 184 | fn from(value: u16) -> Self { 185 | match value { 186 | x if x == ProtocolType::Arp as u16 => ProtocolType::Arp, 187 | x if x == ProtocolType::EtherCat as u16 => ProtocolType::EtherCat, 188 | x if x == ProtocolType::IpV4 as u16 => ProtocolType::IpV4, 189 | x if x == ProtocolType::IpV6 as u16 => ProtocolType::IpV6, 190 | x if x == ProtocolType::Ptp as u16 => ProtocolType::Ptp, 191 | x if x == ProtocolType::Vlan as u16 => ProtocolType::Vlan, 192 | _ => ProtocolType::Unimplemented, 193 | } 194 | } 195 | } 196 | 197 | impl ByteStructLen for ProtocolType { 198 | const BYTE_LEN: usize = 2; 199 | } 200 | 201 | impl ByteStruct for ProtocolType { 202 | fn read_bytes(bytes: &[u8]) -> Self { 203 | let mut bytes_read = [0_u8; 2]; 204 | bytes_read.copy_from_slice(&bytes[0..=1]); 205 | return ProtocolType::from(u16::from_be_bytes(bytes_read)); 206 | } 207 | 208 | fn write_bytes(&self, bytes: &mut [u8]) { 209 | let bytes_to_write = (*self as u16).to_be_bytes(); 210 | bytes[0] = bytes_to_write[0]; 211 | bytes[1] = bytes_to_write[1]; 212 | } 213 | } 214 | 215 | impl ProtocolType { 216 | /// Pack into big-endian (network) byte array 217 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 218 | (*self as u16).to_be_bytes() 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/dhcp.rs: -------------------------------------------------------------------------------- 1 | //! Dynamic Host Configuration Protocol for IPV4. 2 | //! 3 | //! Client side of the call-response structure used by a router to assign IP addresses to devices on a local network. 4 | //! 5 | //! Partial implementation per IETF-RFC-2131; see https://datatracker.ietf.org/doc/html/rfc2131#page-22 6 | //! 7 | //! This is intended to provide just enough functionality to accept a statically-assigned address on 8 | //! networks that require confirmation of static addresses with an indefinite lease duration via DHCP. 9 | //! 10 | //! In this case, the server refers to the router or similar hardware orchestrating the address space, 11 | //! while the client refers to the endpoints requesting addresses. 12 | //! 13 | //! ```rust 14 | //! use catnip::*; 15 | //! 16 | //! let dhcp_inform = DhcpFixedPayload::new_inform( 17 | //! IpV4Addr::new([1, 2, 3, 4]), 18 | //! MacAddr::new([5, 6, 7, 8, 9, 10]), 19 | //! 12345 // Arbitrary transaction ID chosen pseudorandomly by client (us) 20 | //! ); 21 | //! 22 | //! // Serialize 23 | //! let mut bytes = dhcp_inform.to_be_bytes(); 24 | //! // Deserialize 25 | //! let msg_parsed = DhcpFixedPayload::read_bytes(&bytes); 26 | //! 27 | //! assert_eq!(msg_parsed, dhcp_inform); 28 | //! ``` 29 | 30 | use crate::*; 31 | 32 | /// UDP port on server 33 | pub const DHCP_SERVER_PORT: u16 = 67; 34 | 35 | /// UDP port on client 36 | pub const DHCP_CLIENT_PORT: u16 = 68; 37 | 38 | /// "Magic Cookie" placed at the end of the fixed portion of the DHCP payload 39 | const DHCP_COOKIE: u32 = 0x63_82_53_63; 40 | /// A full word containing 255 in the options segment indicates end of message 41 | const DHCP_END: u32 = 0xff; 42 | 43 | use byte_struct::*; 44 | use ufmt::derive::uDebug; 45 | 46 | /// The fixed-length part of the DHCP payload. 47 | /// The options section can vary in length, and is handled separately. 48 | /// For "Inform" message kind, this is the entire message. 49 | #[derive(ByteStruct, uDebug, Debug, Clone, Copy, PartialEq, Eq)] 50 | #[byte_struct_be] 51 | pub struct DhcpFixedPayload { 52 | /// Message op code / message type. 1 = BOOTREQUEST, 2 = BOOTREPLY 53 | op: DhcpOperation, 54 | /// Hardware type always 1 for ethernet 55 | htype: u8, 56 | /// Hardware address length always 6 bytes for standard mac address 57 | hlen: u8, 58 | /// Legacy field, always 0 59 | hops: u8, 60 | /// Transaction ID; assigned by router; must be kept the same through a transaction 61 | xid: u32, 62 | /// Seconds elapsed since client started transaction 63 | secs: u16, 64 | /// Broadcast flag; 1 for broadcast, 0 for unicast 65 | flags: u16, 66 | /// Client IP Address 67 | ciaddr: IpV4Addr, 68 | /// Your IP Address 69 | yiaddr: IpV4Addr, 70 | /// Server IP Address 71 | siaddr: IpV4Addr, 72 | /// Gateway IP Address 73 | giaddr: IpV4Addr, 74 | /// Client (your) hardware address. Actual field is 16 bytes; we only use 6 for standard MAC address. 75 | chaddr: MacAddr, 76 | /// Explicit padding of the remaining 10 bytes of chaddr 77 | _pad0: [u16; 5], 78 | /// Padding of BOOTP legacy fields and server's irrelevant stringified name 79 | _pad1: [u128; 12], 80 | /// "Magic cookie" identifying this as a DHCP message. 81 | /// Must always have the value of 0x63_82_53_63 (in dhcp::COOKIE) 82 | cookie: u32, 83 | /// The message kind should always be included and should be the first options field 84 | kind_option: DhcpMessageKindOption, 85 | } 86 | 87 | impl DhcpFixedPayload { 88 | /// Convenience function to remove boilerplate for predetermined fields. 89 | pub fn new( 90 | end_of_message: bool, 91 | op: DhcpOperation, 92 | kind: DhcpMessageKind, 93 | transaction_id: u32, 94 | broadcast: bool, 95 | ciaddr: IpV4Addr, 96 | yiaddr: IpV4Addr, 97 | siaddr: IpV4Addr, 98 | chaddr: MacAddr, 99 | ) -> Self { 100 | DhcpFixedPayload { 101 | op: op, 102 | htype: 1_u8, // Always 1 for ethernet 103 | hlen: 6_u8, // Always 6 byte standard mac address 104 | hops: 0, 105 | xid: transaction_id, 106 | secs: 0, 107 | flags: (broadcast as u16) * 32768, 108 | ciaddr: ciaddr, 109 | yiaddr: yiaddr, 110 | siaddr: siaddr, 111 | giaddr: IpV4Addr::ANY, 112 | chaddr: chaddr, 113 | _pad0: [0_u16; 5], 114 | _pad1: [0_u128; 12], 115 | cookie: DHCP_COOKIE, 116 | kind_option: DhcpMessageKindOption::new(kind, end_of_message), 117 | } 118 | } 119 | 120 | /// Build a DHCP INFORM message to broadcast to the network indicating that we are 121 | /// taking a pre-assigned IP address which may have already be assigned statically 122 | /// in the configuration of the router. This message should also be accompanied by 123 | /// an ARP "announce" message to broadcast the presence of the machine to others on 124 | /// the network that may or may not receive a forwarded copy of the DHCP INFORM. 125 | pub fn new_inform(ipaddr: IpV4Addr, macaddr: MacAddr, transaction_id: u32) -> Self { 126 | Self::new( 127 | true, 128 | DhcpOperation::Request, 129 | DhcpMessageKind::Inform, 130 | transaction_id, 131 | true, 132 | ipaddr, 133 | IpV4Addr::ANY, 134 | IpV4Addr::ANY, 135 | macaddr, 136 | ) 137 | } 138 | 139 | /// Pack into big-endian (network) byte array 140 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 141 | let mut header_bytes = [0_u8; Self::BYTE_LEN]; 142 | self.write_bytes(&mut header_bytes); 143 | 144 | header_bytes 145 | } 146 | } 147 | 148 | /// The options field for message kind is technically part of the 149 | /// variable-length portion, but is always required and always the first option 150 | /// so it's really part of the fixed-length portion. 151 | #[derive(ByteStruct, uDebug, Debug, Clone, Copy, PartialEq, Eq)] 152 | #[byte_struct_be] 153 | pub struct DhcpMessageKindOption { 154 | /// Type of option field 155 | pub kind: DhcpOptionKind, 156 | /// Length (how many bytes of data is the actual option?) 157 | length: u8, 158 | /// The actual message kind 159 | value: DhcpMessageKind, 160 | /// Pad to word boundary or indicate end of message 161 | _pad: u8, 162 | } 163 | 164 | impl DhcpMessageKindOption { 165 | /// For convenience, since most values are predetermined 166 | pub fn new(kind: DhcpMessageKind, eom: bool) -> Self { 167 | DhcpMessageKindOption { 168 | kind: DhcpOptionKind::DhcpMessageType, 169 | length: 1, 170 | value: kind, 171 | _pad: match eom { 172 | true => 255, 173 | false => 0, 174 | }, 175 | } 176 | } 177 | } 178 | 179 | enum_with_unknown! { 180 | /// Message op code / message type. 1 = BOOTREQUEST, 2 = BOOTREPLY 181 | /// Legacy operation type field from BOOTP. 182 | /// Still has to match and change value depending on message type even though 183 | /// there is only one valid combination of message type and operation. 184 | pub enum DhcpOperation(u8) { 185 | /// Anything coming from the client 186 | Request = 1, 187 | /// Anything coming from the server 188 | Reply = 2 189 | } 190 | } 191 | 192 | impl ByteStructLen for DhcpOperation { 193 | const BYTE_LEN: usize = 1; 194 | } 195 | 196 | impl ByteStruct for DhcpOperation { 197 | fn read_bytes(bytes: &[u8]) -> Self { 198 | Self::from(bytes[0]) 199 | } 200 | 201 | fn write_bytes(&self, bytes: &mut [u8]) { 202 | bytes[0] = u8::from(*self); 203 | } 204 | } 205 | 206 | enum_with_unknown! { 207 | /// Contents of option field kind 53 208 | #[allow(missing_docs)] 209 | pub enum DhcpMessageKind(u8) { 210 | /// Client broadcast to locate available servers. 211 | Discover = 1, 212 | /// Server to client in response to DHCPDISCOVER with offer of configuration parameters. 213 | Offer = 2, 214 | /// Client message to servers either (a) requesting 215 | /// offered parameters from one server and implicitly 216 | /// declining offers from all others, (b) confirming 217 | /// correctness of previously allocated address after, 218 | /// e.g., system reboot, or (c) extending the lease on a 219 | /// particular network address. 220 | Request = 3, 221 | /// Client to server indicating network address is already in use. 222 | Decline = 4, 223 | /// Server to client with configuration parameters, including committed network address. 224 | Ack = 5, // Acknowledge 225 | /// Server to client indicating client's notion of network address is incorrect 226 | /// (e.g., client has moved to new subnet) or client's lease as expired 227 | Nak = 6, // Negative-acknowledge 228 | /// Client to server relinquishing network address and cancelling remaining lease. 229 | Release = 7, 230 | /// Client to server, asking only for local configuration parameters. 231 | /// Client already has externally configured network address. 232 | Inform = 8, 233 | ForceRenew = 9, 234 | LeaseQuery = 10, 235 | LeaseUnassigned = 11, 236 | LeaseUnknown = 12, 237 | LeaseActive = 13, 238 | BulkLeaseQuery = 14, 239 | LeaseQueryDone = 15, 240 | ActiveLeaseQuery = 16, 241 | LeaseQueryStatus = 17, 242 | Tls = 18 243 | } 244 | } 245 | 246 | impl ByteStructLen for DhcpMessageKind { 247 | const BYTE_LEN: usize = 1; 248 | } 249 | 250 | impl ByteStruct for DhcpMessageKind { 251 | fn read_bytes(bytes: &[u8]) -> Self { 252 | Self::from(bytes[0]) 253 | } 254 | 255 | fn write_bytes(&self, bytes: &mut [u8]) { 256 | bytes[0] = u8::from(*self); 257 | } 258 | } 259 | 260 | enum_with_unknown! { 261 | /// Option type codes for parsing options section. 262 | /// Most of these are useless. 263 | #[allow(missing_docs)] 264 | pub enum DhcpOptionKind(u8) { 265 | Pad = 0, 266 | SubnetMask = 1, 267 | TimeOffset = 2, 268 | Router = 3, 269 | TimeServer = 4, 270 | NameServer = 5, 271 | DomainNameServers = 6, 272 | LogServer = 7, 273 | CookieServer = 8, 274 | LPRServer = 9, 275 | ImpressServer = 10, 276 | ResourceLocationServer = 11, 277 | HostName = 12, 278 | BootFileSize = 13, 279 | MeritDumpFileSize = 14, 280 | DomainName = 15, 281 | SwapServer = 16, 282 | RootPath = 17, 283 | ExtensionsPath = 18, 284 | IPForwardEnable = 19, 285 | SourceRoutingEnable = 20, 286 | PolicyFilter = 21, 287 | MaximumDatagramSize = 22, 288 | DefaultIpTtl = 23, 289 | PathMtuTimeout = 24, 290 | PathMtuPlateau = 25, 291 | InterfaceMtu = 26, 292 | AllSubnetsLocal = 27, 293 | BroadcastAddress = 28, 294 | PerformMaskDiscovery = 29, 295 | MaskSupplier = 30, 296 | PerformRouterDiscovery = 31, 297 | RouterSolicitationAddress = 32, 298 | StaticRoute = 33, 299 | TrailerEncapsulation = 34, 300 | ArpCacheTimeout = 35, 301 | EthernetEncapsulation = 36, 302 | TcpDefaultTtl = 37, 303 | TcpKeepAliveInterval = 38, 304 | TcpKeepAliveGarbage = 39, 305 | NetworkInfoServiceDomain = 40, 306 | NetworkInfoSevers = 41, 307 | NtpServers = 42, 308 | VendorInfo = 43, 309 | NetBiosNameServer = 44, 310 | NetBiosDistributionServer = 45, 311 | NetBiosNodeType = 46, 312 | NetBiosScope = 47, 313 | XWindowFontServer = 48, 314 | XWindowDisplayMgr = 49, 315 | 316 | // Extensions (these are mostly the useful ones) 317 | RequestedIpAddress = 50, 318 | IpAddressLeaseTime = 51, 319 | OptionOverload = 52, 320 | /// This option's contents indicate how the rest of the message should be parsed 321 | DhcpMessageType = 53, 322 | ServerIdentifier = 54, 323 | ParameterRequestList = 55, 324 | Message = 56, 325 | MaxDhcpMessageSize = 57, 326 | /// Time in seconds until start of renewal (half of lease duration) 327 | RenewalTime = 58, 328 | RebindingTime = 59, 329 | VendorClassId = 60, 330 | ClientId = 61, 331 | TftpServerName = 62, 332 | BootFileName = 63, 333 | 334 | // More application stuff 335 | NisPlusDomain = 64, 336 | NisPlusServers = 65, 337 | // Where are 66-67? 338 | MobileIpHomeAgent = 68, 339 | SmtpServer = 69, 340 | Pop3Server = 70, 341 | NntpServer = 71, 342 | DefaultWwwServer = 72, 343 | DefaultFingerServer = 73, 344 | DefaultIrcServer = 74, 345 | StreetTalkServer = 75, 346 | StreetTalkDirectoryServer = 76, 347 | 348 | // More extensions 349 | RelayAgentInfo = 82, 350 | NdsServers = 85, 351 | NdsContext = 86, 352 | TimeZonePosix = 100, 353 | TimeZoneTz = 101, 354 | DhcpCaptivePortal = 114, 355 | DomainSearch = 119, 356 | ClasslessStaticRoute = 121, 357 | ConfigFile = 209, 358 | PathPrefix = 210, 359 | RebootTime = 211, 360 | 361 | End = 255, 362 | } 363 | } 364 | 365 | impl ByteStructLen for DhcpOptionKind { 366 | const BYTE_LEN: usize = 1; 367 | } 368 | 369 | impl ByteStruct for DhcpOptionKind { 370 | fn read_bytes(bytes: &[u8]) -> Self { 371 | Self::from(bytes[0]) 372 | } 373 | 374 | fn write_bytes(&self, bytes: &mut [u8]) { 375 | bytes[0] = u8::from(*self); 376 | } 377 | } 378 | 379 | #[cfg(test)] 380 | mod test { 381 | use super::*; 382 | // use crate::*; 383 | 384 | #[test] 385 | fn test_serialization_loop() { 386 | let dhcp_inform = DhcpFixedPayload::new_inform( 387 | IpV4Addr::new([1, 2, 3, 4]), 388 | MacAddr::new([5, 6, 7, 8, 9, 10]), 389 | 12345, 390 | ); 391 | 392 | let mut bytes = [0_u8; DhcpFixedPayload::BYTE_LEN]; 393 | dhcp_inform.write_bytes(&mut bytes); 394 | 395 | let msg_parsed = DhcpFixedPayload::read_bytes(&bytes); 396 | 397 | assert_eq!(msg_parsed, dhcp_inform); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/enet.rs: -------------------------------------------------------------------------------- 1 | //! Link layer: Ethernet II protocol. 2 | //! See . 3 | 4 | use crate::MacAddr; 5 | 6 | use byte_struct::*; 7 | use ufmt::derive::uDebug; 8 | use static_assertions::const_assert; 9 | 10 | // In general, this could be 18 bytes for a 802.1Q tagged vlan, 11 | // but that is not supported here because tagged vlan is spammy and unsecure. 12 | const_assert!(EthernetHeader::BYTE_LEN == 14); 13 | 14 | /// Header for Ethernet II frame 15 | #[derive(ByteStruct, Clone, Copy, uDebug, Debug, PartialEq, Eq)] 16 | #[byte_struct_be] 17 | pub struct EthernetHeader { 18 | /// Destination MAC address 19 | pub dst_macaddr: MacAddr, 20 | /// Source MAC address 21 | pub src_macaddr: MacAddr, 22 | /// Type of content (IpV4, IpV6, Arp, Ptp, etc) 23 | pub ethertype: EtherType, 24 | } 25 | 26 | impl EthernetHeader { 27 | /// Pack into big-endian (network) byte array 28 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 29 | let mut bytes = [0_u8; Self::BYTE_LEN]; 30 | self.write_bytes(&mut bytes); 31 | bytes 32 | } 33 | } 34 | 35 | /// Ethernet frame around arbitrary data 36 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq)] 37 | pub struct EthernetFrame 38 | where 39 | T: ByteStruct, 40 | { 41 | /// Ethernet header 42 | pub header: EthernetHeader, 43 | /// Data payload (probably and IP frame or Arp message) 44 | pub data: T, 45 | /// CRC checksum. Must be present, but zeroed-out s.t. it can be calculated by hardware. 46 | pub checksum: u32, 47 | } 48 | 49 | impl ByteStructLen for EthernetFrame 50 | where 51 | T: ByteStruct, 52 | { 53 | const BYTE_LEN: usize = EthernetHeader::BYTE_LEN + T::BYTE_LEN + 4; 54 | } 55 | 56 | impl ByteStruct for EthernetFrame 57 | where 58 | T: ByteStruct, 59 | { 60 | fn read_bytes(bytes: &[u8]) -> Self { 61 | let mut checksum_bytes = [0_u8; 4]; 62 | checksum_bytes.copy_from_slice(&bytes[Self::BYTE_LEN - 4..Self::BYTE_LEN]); 63 | EthernetFrame:: { 64 | header: EthernetHeader::read_bytes(&bytes[0..EthernetHeader::BYTE_LEN]), 65 | data: T::read_bytes(&bytes[EthernetHeader::BYTE_LEN..Self::BYTE_LEN - 4]), 66 | checksum: u32::from_be_bytes(checksum_bytes), 67 | } 68 | } 69 | 70 | fn write_bytes(&self, bytes: &mut [u8]) { 71 | self.header 72 | .write_bytes(&mut bytes[0..EthernetHeader::BYTE_LEN]); 73 | self.data 74 | .write_bytes(&mut bytes[EthernetHeader::BYTE_LEN..Self::BYTE_LEN - 4]); 75 | let checksum_bytes = self.checksum.to_be_bytes(); 76 | for i in 0..4 { 77 | bytes[Self::BYTE_LEN - 4 + i] = checksum_bytes[i]; 78 | } 79 | } 80 | } 81 | 82 | impl EthernetFrame 83 | where 84 | T: ByteStruct, 85 | { 86 | /// Pack into big-endian (network) byte array 87 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 88 | let mut bytes = [0_u8; Self::BYTE_LEN]; 89 | self.write_bytes(&mut bytes); 90 | bytes 91 | } 92 | } 93 | 94 | /// EtherType tag values (incomplete list - there are many more not implemented here). 95 | /// See . 96 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)] 97 | #[repr(u16)] 98 | pub enum EtherType { 99 | /// Internet protocol version 4 100 | IpV4 = 0x0800, 101 | /// Address resolution protocol 102 | Arp = 0x0806, 103 | /// Tagged virtual LAN - if this tag is encountered, then this is not the real ethertype field, and we're reading an 802.1Q Vlan tag instead 104 | /// This crate does not support tagged Vlan, which is a trust-based and inefficient system. Untagged Vlan should be used instead. 105 | Vlan = 0x8100, 106 | /// Internet protocol version 6 107 | IpV6 = 0x86DD, 108 | /// EtherCat 109 | EtherCat = 0x88A4, 110 | /// Precision Time Protocol 111 | Ptp = 0x88A7, 112 | /// Catch-all for uncommon types not handled here 113 | Unimplemented = 0x0, 114 | } 115 | 116 | impl From for EtherType { 117 | fn from(value: u16) -> Self { 118 | match value { 119 | x if x == EtherType::Arp as u16 => EtherType::Arp, 120 | x if x == EtherType::EtherCat as u16 => EtherType::EtherCat, 121 | x if x == EtherType::IpV4 as u16 => EtherType::IpV4, 122 | x if x == EtherType::IpV6 as u16 => EtherType::IpV6, 123 | x if x == EtherType::Ptp as u16 => EtherType::Ptp, 124 | x if x == EtherType::Vlan as u16 => EtherType::Vlan, 125 | _ => EtherType::Unimplemented, 126 | } 127 | } 128 | } 129 | 130 | impl ByteStructLen for EtherType { 131 | const BYTE_LEN: usize = 2; 132 | } 133 | 134 | impl ByteStruct for EtherType { 135 | fn read_bytes(bytes: &[u8]) -> Self { 136 | let mut bytes_read = [0_u8; 2]; 137 | bytes_read.copy_from_slice(&bytes[0..=1]); 138 | return EtherType::from(u16::from_be_bytes(bytes_read)); 139 | } 140 | 141 | fn write_bytes(&self, bytes: &mut [u8]) { 142 | let bytes_to_write = (*self as u16).to_be_bytes(); 143 | bytes[0] = bytes_to_write[0]; 144 | bytes[1] = bytes_to_write[1]; 145 | } 146 | } 147 | 148 | impl EtherType { 149 | /// Pack into big-endian (network) byte array 150 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 151 | (*self as u16).to_be_bytes() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/ip.rs: -------------------------------------------------------------------------------- 1 | //! Internet layer: Internet Protocol message header construction 2 | 3 | use crate::{IpV4Addr, Protocol, DSCP}; 4 | 5 | use byte_struct::*; 6 | use modular_bitfield::prelude::*; 7 | use static_assertions::const_assert; 8 | use ufmt::derive::uDebug; 9 | 10 | const_assert!(IpV4Header::BYTE_LEN == 20); 11 | 12 | /// IPV4 header per IETF-RFC-791. 13 | /// See . 14 | #[derive(ByteStruct, Clone, Copy, uDebug, Debug, PartialEq, Eq)] 15 | #[byte_struct_be] 16 | pub struct IpV4Header { 17 | /// Combined version and header length info in a single byte 18 | pub version_and_header_length: VersionAndHeaderLength, 19 | /// Type of Service / Differentiated-Service 20 | pub dscp: DSCP, 21 | /// Total length including header and data 22 | pub total_length: u16, 23 | /// Mostly-legacy id field 24 | pub identification: u16, 25 | /// Mostly-legacy packet fragmentation info 26 | pub fragmentation: Fragmentation, 27 | /// TTL counter 28 | pub time_to_live: u8, 29 | /// Transport-layer protocol 30 | pub protocol: Protocol, 31 | /// CRC checksum 32 | pub checksum: u16, 33 | /// Source IP address 34 | pub src_ipaddr: IpV4Addr, 35 | /// Destination IP address 36 | pub dst_ipaddr: IpV4Addr, 37 | } 38 | 39 | impl IpV4Header { 40 | /// Pack into big-endian (network) byte array 41 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 42 | let mut bytes = [0_u8; Self::BYTE_LEN]; 43 | self.write_bytes(&mut bytes); 44 | bytes 45 | } 46 | } 47 | 48 | /// IPV4 frame with header and data. 49 | /// Data should be sized in a multiple of 4 bytes. 50 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq)] 51 | pub struct IpV4Frame 52 | where 53 | T: ByteStruct, 54 | { 55 | /// IP header 56 | pub header: IpV4Header, 57 | /// Data such as a UDP header; should be some multiple of 4 bytes (32-bit words) 58 | pub data: T, 59 | } 60 | 61 | impl ByteStructLen for IpV4Frame 62 | where 63 | T: ByteStruct, 64 | { 65 | const BYTE_LEN: usize = IpV4Header::BYTE_LEN + T::BYTE_LEN; 66 | } 67 | 68 | impl ByteStruct for IpV4Frame 69 | where 70 | T: ByteStruct, 71 | { 72 | fn read_bytes(bytes: &[u8]) -> Self { 73 | IpV4Frame:: { 74 | header: IpV4Header::read_bytes(&bytes[0..IpV4Header::BYTE_LEN]), 75 | data: T::read_bytes(&bytes[IpV4Header::BYTE_LEN..]), 76 | } 77 | } 78 | 79 | fn write_bytes(&self, bytes: &mut [u8]) { 80 | self.header.write_bytes(&mut bytes[0..IpV4Header::BYTE_LEN]); 81 | self.data.write_bytes(&mut bytes[IpV4Header::BYTE_LEN..]); 82 | } 83 | } 84 | 85 | impl IpV4Frame 86 | where 87 | T: ByteStruct, 88 | { 89 | /// Pack into big-endian (network) byte array 90 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 91 | let mut bytes = [0_u8; Self::BYTE_LEN]; 92 | self.write_bytes(&mut bytes); 93 | bytes 94 | } 95 | } 96 | 97 | /// Fragmentation flags and offset info 98 | #[bitfield(bits = 16)] 99 | #[derive(Clone, Copy, uDebug, Debug, Default, PartialEq, Eq)] 100 | pub struct Fragmentation { 101 | unused: B1, 102 | /// Flag for routers to drop packets instead of fragmenting 103 | pub do_not_fragment: B1, 104 | /// Flag that there are more fragments coming 105 | pub more_fragments: B1, 106 | /// Where we are in a set of fragments 107 | pub offset: B13, 108 | } 109 | 110 | impl ByteStructLen for Fragmentation { 111 | const BYTE_LEN: usize = 2; 112 | } 113 | 114 | impl ByteStruct for Fragmentation { 115 | fn read_bytes(bytes: &[u8]) -> Self { 116 | // All bit patterns are valid, so this will never error 117 | let mut bytes_to_read = [0_u8; Fragmentation::BYTE_LEN]; 118 | bytes_to_read.copy_from_slice(&bytes[0..=1]); 119 | Fragmentation::from_bytes(bytes_to_read) 120 | } 121 | 122 | fn write_bytes(&self, bytes: &mut [u8]) { 123 | let bytes_to_write = self.into_bytes(); 124 | bytes[0] = bytes_to_write[0]; 125 | bytes[1] = bytes_to_write[1]; 126 | } 127 | } 128 | 129 | /// Combined IP version and header length in a single byte. 130 | #[bitfield(bits = 8)] 131 | #[derive(Clone, Copy, uDebug, Debug, Default, PartialEq, Eq)] 132 | pub struct VersionAndHeaderLength { 133 | /// Length of IP header in 32-bit words (usually 5 words, or 20 bytes) 134 | pub header_length: B4, 135 | /// IP version number 136 | pub version: B4, 137 | } 138 | 139 | impl ByteStructLen for VersionAndHeaderLength { 140 | const BYTE_LEN: usize = 1; 141 | } 142 | 143 | impl ByteStruct for VersionAndHeaderLength { 144 | fn read_bytes(bytes: &[u8]) -> Self { 145 | // All bit patterns are valid, so this will never error 146 | VersionAndHeaderLength::from_bytes([bytes[0]]) 147 | } 148 | 149 | fn write_bytes(&self, bytes: &mut [u8]) { 150 | bytes[0] = self.into_bytes()[0]; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/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::>>> { 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::>> { 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::>>::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::> { 40 | //! header: UdpHeader { 41 | //! src_port: 8123, 42 | //! dst_port: 8125, 43 | //! length: UdpFrame::>::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::>>>::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")] 70 | use panic_never as _; 71 | 72 | pub use byte_struct::{ByteStruct, ByteStructLen}; 73 | pub use modular_bitfield; 74 | pub use ufmt::{derive::uDebug, uDebug, uDisplay, uWrite}; 75 | 76 | pub mod enet; // Link Layer 77 | pub mod ip; // Internet layer 78 | pub mod udp; // Transport layer 79 | 80 | pub mod arp; // Address Resolution Protocol - not a distinct layer (between link and transport), but required for IP and UDP to function on most networks. 81 | pub mod dhcp; // Dynamic Host Configuration Protocol - for negotiating an IP address from a router/switch. Uses UDP. 82 | 83 | pub use arp::*; 84 | pub use dhcp::*; 85 | pub use enet::*; 86 | pub use ip::*; 87 | pub 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, ...] 92 | pub type MacAddr = ByteArray<6>; 93 | 94 | impl 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 108 | pub type IpV4Addr = ByteArray<4>; 109 | 110 | impl 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 . 129 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)] 130 | #[repr(u8)] 131 | pub enum Protocol { 132 | /// Transmission Control Protocol 133 | Tcp = 0x06, 134 | /// User Datagram Protocol 135 | Udp = 0x11, 136 | /// Unimplemented 137 | Unimplemented, 138 | } 139 | 140 | impl ByteStructLen for Protocol { 141 | const BYTE_LEN: usize = 1; 142 | } 143 | 144 | impl 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 | 158 | impl 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 . 166 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq, PartialOrd, Ord)] 167 | #[repr(u8)] 168 | pub 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 | 177 | impl ByteStructLen for DSCP { 178 | const BYTE_LEN: usize = 1; 179 | } 180 | 181 | impl 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 | 195 | impl 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)] 204 | pub struct ByteArray(pub [u8; N]); 205 | 206 | impl ByteStructLen for ByteArray { 207 | const BYTE_LEN: usize = N; 208 | } 209 | 210 | impl ByteStruct for ByteArray { 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 | 224 | impl ByteArray { 225 | /// Convert to big-endian byte array 226 | pub fn to_be_bytes(&self) -> [u8; N] { 227 | self.0 228 | } 229 | } 230 | 231 | impl uDebug for ByteArray<4> { 232 | fn fmt(&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 | 240 | impl uDebug for ByteArray<6> { 241 | fn fmt(&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] 255 | macro_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 . 299 | /// This function is provided for convenience and is not used directly. 300 | pub 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 311 | pub 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. 336 | pub 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)] 354 | mod 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::>>::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 | } 385 | -------------------------------------------------------------------------------- /src/udp.rs: -------------------------------------------------------------------------------- 1 | //! Transport layer: User Datagram Protocol 2 | 3 | use crate::ip::{IpV4Frame, IpV4Header}; 4 | use crate::{calc_ip_checksum_finalize, calc_ip_checksum_incomplete}; 5 | use byte_struct::*; 6 | pub use ufmt::derive::uDebug; 7 | 8 | /// UDP datagram header structure for IPV4. 9 | #[derive(ByteStruct, Clone, Copy, uDebug, Debug, PartialEq, Eq)] 10 | #[byte_struct_be] 11 | pub struct UdpHeader { 12 | /// Source port 13 | pub src_port: u16, 14 | /// Destination port 15 | pub dst_port: u16, 16 | /// Total frame length including header and data, in bytes 17 | pub length: u16, 18 | /// IP-style checksum (optional for UDP, and usually supplied by hardware). 19 | /// Calculated from a "pseudo-header" that is not the actual header. 20 | pub checksum: u16, 21 | } 22 | 23 | impl UdpHeader { 24 | /// Get length of byte representation 25 | fn len(&self) -> usize { 26 | Self::BYTE_LEN 27 | } 28 | 29 | /// Pack into big-endian (network) byte array 30 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 31 | let mut bytes = [0_u8; Self::BYTE_LEN]; 32 | self.write_bytes(&mut bytes); 33 | 34 | bytes 35 | } 36 | } 37 | 38 | /// IPV4 message frame for UDP protocol. 39 | #[derive(Clone, Copy, uDebug, Debug, PartialEq, Eq)] 40 | pub struct UdpFrame { 41 | /// UDP packet header 42 | pub header: UdpHeader, 43 | /// Data to transmit; bytes must be in some multiple of 4 (32 bit words) 44 | pub data: T, 45 | } 46 | 47 | impl UdpFrame { 48 | /// Pack into big-endian (network) byte array 49 | pub fn to_be_bytes(&self) -> [u8; Self::BYTE_LEN] { 50 | let mut bytes = [0_u8; Self::BYTE_LEN]; 51 | self.write_bytes(&mut bytes); 52 | 53 | bytes 54 | } 55 | } 56 | 57 | impl ByteStructLen for UdpFrame 58 | where 59 | T: ByteStruct, 60 | { 61 | const BYTE_LEN: usize = IpV4Header::BYTE_LEN + UdpHeader::BYTE_LEN + T::BYTE_LEN; 62 | } 63 | 64 | impl ByteStruct for UdpFrame 65 | where 66 | T: ByteStruct, 67 | { 68 | fn read_bytes(bytes: &[u8]) -> Self { 69 | UdpFrame:: { 70 | header: UdpHeader::read_bytes(&bytes[0..UdpHeader::BYTE_LEN]), 71 | data: T::read_bytes(&bytes[UdpHeader::BYTE_LEN..Self::BYTE_LEN]), 72 | } 73 | } 74 | 75 | fn write_bytes(&self, bytes: &mut [u8]) { 76 | self.header.write_bytes(&mut bytes[0..UdpHeader::BYTE_LEN]); 77 | self.data 78 | .write_bytes(&mut bytes[UdpHeader::BYTE_LEN..Self::BYTE_LEN]); 79 | } 80 | } 81 | 82 | /// UDP checksum calculation with pseudo-header that includes some info from IP header 83 | /// This is not the most efficient possible way to do this; in general, checksum calculation 84 | /// should be processor-offloaded and should not be run in software except for troubleshooting. 85 | pub fn calc_udp_checksum(ipframe: &IpV4Frame>) -> u16 86 | where 87 | [(); UdpFrame::::BYTE_LEN]:, 88 | { 89 | // Build the weirdly-formatted part 90 | let udp_len = ipframe.data.header.length; 91 | let udp_length_bytes = udp_len.to_be_bytes(); 92 | // let ip_pseudoheader: [u8; 4] = [0, ipframe.header.protocol.to_be_bytes()[0], udp_length_bytes[0], udp_length_bytes[1]]; 93 | let ip_pseudoheader: [u8; 4] = [ 94 | 0, 95 | (ipframe.header.protocol as u8).to_be(), 96 | udp_length_bytes[0], 97 | udp_length_bytes[1], 98 | ]; 99 | // Sum over components 100 | let mut sum: u32 = 0; 101 | sum += calc_ip_checksum_incomplete(&ipframe.header.src_ipaddr.0); // IP addresses 102 | sum += calc_ip_checksum_incomplete(&ipframe.header.dst_ipaddr.0); 103 | sum += calc_ip_checksum_incomplete(&ip_pseudoheader); // The weirdly formatted IP header part 104 | let index = UdpFrame::::BYTE_LEN.min(udp_len as usize); // If we don't clip here, we can consume uninitialized junk 105 | sum += calc_ip_checksum_incomplete(&ipframe.data.to_be_bytes()[..index]); 106 | 107 | // Fold the accumulator into a u16 108 | let checksum: u16 = calc_ip_checksum_finalize(sum); 109 | 110 | checksum 111 | } 112 | -------------------------------------------------------------------------------- /test/test_no_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_no_std" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | catnip = {path="../../", features=["panic_never"]} 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | opt-level = "s" 14 | lto = true # Use full link-time optimization to reduce file size and eliminate panic branches 15 | debug = true # Include debugging flags 16 | debug-assertions = true 17 | overflow-checks = true 18 | 19 | [profile.release] 20 | panic = "abort" 21 | opt-level = "s" # Compiler optimization for minimum file size 22 | lto = true # Use full link-time optimization to reduce file size and eliminate panic branches 23 | debug = false 24 | debug-assertions = false 25 | overflow-checks = false 26 | -------------------------------------------------------------------------------- /test/test_no_std/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Building this module successfully guarantees that the catnip library is no-std compatible 2 | //! and that it produces no panic branches (panic-never compatible). 3 | //! However, not all tests can be run this way, because panic_never precludes the use of run-time assertions. 4 | 5 | #![no_std] 6 | #![no_main] 7 | 8 | use catnip::*; 9 | 10 | #[no_mangle] 11 | pub fn _start() -> ! { 12 | 13 | test_arp(); 14 | test_enet_ip_udp(); 15 | 16 | loop {} 17 | } 18 | 19 | 20 | fn test_arp() -> () { 21 | // Build an ARP message and make sure the parser returns the same values from the input 22 | let msg = ArpPayload::new( 23 | MacAddr::new([7_u8; 6]), 24 | IpV4Addr::new([8_u8; 4]), 25 | MacAddr::new([9_u8; 6]), 26 | IpV4Addr::new([10_u8; 4]), 27 | ArpOperation::Request, 28 | ); 29 | // Serialize 30 | let bytes: [u8; ArpPayload::BYTE_LEN] = msg.to_be_bytes(); 31 | // Deserialize 32 | let _msg_parsed = ArpPayload::read_bytes(&bytes); 33 | } 34 | 35 | fn generate_sample_frame() -> EthernetFrame::>>> { 36 | // Some made-up addresses 37 | // MAC address in locally-administered address range 38 | // IP addresses in local network range 39 | // Ports are arbitrary 40 | let src_macaddr: MacAddr = MacAddr::new([0x02, 0xAF, 0xFF, 0x1A, 0xE5, 0x3C]); 41 | let dst_macaddr: MacAddr = MacAddr::ANY; 42 | let src_port: u16 = 8123; 43 | let dst_port: u16 = 8125; 44 | let src_ipaddr: IpV4Addr = IpV4Addr::new([10, 0, 0, 1]); 45 | let dst_ipaddr: IpV4Addr = IpV4Addr::new([10, 0, 0, 2]); 46 | 47 | // Some made-up data with two 32-bit words' worth of bytes 48 | let data: ByteArray<8> = ByteArray([0, 1, 2, 3, 4, 5, 6, 7]); 49 | 50 | let frame = EthernetFrame::>>> { 51 | header: EthernetHeader { 52 | dst_macaddr: dst_macaddr, 53 | src_macaddr: src_macaddr, 54 | ethertype: EtherType::IpV4, 55 | }, 56 | data: IpV4Frame::>> { 57 | header: IpV4Header { 58 | version_and_header_length: VersionAndHeaderLength::new().with_version(4).with_header_length((IpV4Header::BYTE_LEN / 4) as u8), 59 | dscp: DSCP::Standard, 60 | total_length: IpV4Frame::>>::BYTE_LEN as u16, 61 | identification: 0, 62 | fragmentation: Fragmentation::default(), 63 | time_to_live: 10, 64 | protocol: Protocol::Udp, 65 | checksum: 0, 66 | src_ipaddr: src_ipaddr, 67 | dst_ipaddr: dst_ipaddr, 68 | }, 69 | data: UdpFrame::> { 70 | header: UdpHeader { 71 | src_port: src_port, 72 | dst_port: dst_port, 73 | length: UdpFrame::>::BYTE_LEN as u16, 74 | checksum: 0, 75 | }, 76 | data: data, 77 | }, 78 | }, 79 | checksum: 0_u32, 80 | }; 81 | 82 | frame 83 | } 84 | 85 | fn test_enet_ip_udp() -> () { 86 | let frame = generate_sample_frame(); 87 | 88 | let bytes = frame.to_be_bytes(); 89 | let _frame_parsed = EthernetFrame::>>>::read_bytes(&bytes); 90 | 91 | } 92 | --------------------------------------------------------------------------------