Skip to main content

godspeed/
addr.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Addresses. Pure parsing - no service, no SDK, and no formatter.
3//!
4//! Separate from [`crate::net`] for one reason: this is the only part of the network surface a
5//! caller feeds UNTRUSTED TEXT to, it is the classic place to be off by one, and it has no
6//! dependencies - so it can be unit-tested on the host, where a wrong answer is a failing test
7//! rather than a machine quietly talking to the wrong address.
8//!
9//! That split is the pattern `kernel/src/clock.rs` documents: pure logic lives where it can be
10//! tested, and the part that talks to a service is exercised on the target.
11
12/// An IPv4 address.
13#[derive(Copy, Clone, PartialEq, Eq, Debug)]
14pub struct Ipv4(pub [u8; 4]);
15
16impl Ipv4 {
17    /// Parse dotted-quad. Returns `None` rather than guessing at anything malformed.
18    pub fn parse(s: &str) -> Option<Ipv4> {
19        let mut out = [0u8; 4];
20        let mut parts = 0usize;
21        for field in s.split('.') {
22            if parts == 4 || field.is_empty() || field.len() > 3 {
23                return None;
24            }
25            let mut v: u16 = 0;
26            for c in field.bytes() {
27                if !c.is_ascii_digit() {
28                    return None;
29                }
30                v = v * 10 + (c - b'0') as u16;
31            }
32            if v > 255 {
33                return None;
34            }
35            out[parts] = v as u8;
36            parts += 1;
37        }
38        if parts == 4 { Some(Ipv4(out)) } else { None }
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn parses_ordinary_addresses() {
48        assert_eq!(Ipv4::parse("10.0.2.2"), Some(Ipv4([10, 0, 2, 2])));
49        assert_eq!(Ipv4::parse("0.0.0.0"), Some(Ipv4([0, 0, 0, 0])));
50        assert_eq!(Ipv4::parse("255.255.255.255"), Some(Ipv4([255, 255, 255, 255])));
51        assert_eq!(Ipv4::parse("192.168.4.43"), Some(Ipv4([192, 168, 4, 43])));
52    }
53
54    /// The cases that matter, because each one is an address a careless parser would ACCEPT and
55    /// then send traffic to. A wrong answer here is not a crash, it is the machine talking to
56    /// somebody else.
57    #[test]
58    fn refuses_what_is_not_an_address() {
59        assert_eq!(Ipv4::parse("256.0.0.1"), None, "an octet past 255");
60        assert_eq!(Ipv4::parse("1.2.3"), None, "three octets");
61        assert_eq!(Ipv4::parse("1.2.3.4.5"), None, "five octets");
62        assert_eq!(Ipv4::parse(""), None);
63        assert_eq!(Ipv4::parse("..."), None, "four empty fields");
64        assert_eq!(Ipv4::parse("1.2.3."), None, "a trailing dot leaves an empty field");
65        assert_eq!(Ipv4::parse(".1.2.3"), None, "and so does a leading one");
66        assert_eq!(Ipv4::parse("1.2.3.x"), None, "a non-digit");
67        assert_eq!(Ipv4::parse("1.2.3.-1"), None, "a sign is not a digit");
68        assert_eq!(Ipv4::parse("1.2.3.4 "), None, "trailing space is not trimmed for us");
69        assert_eq!(Ipv4::parse("1.2.3.0004"), None, "more digits than an octet can hold");
70    }
71
72    /// `0999` is under 255 only if you let it overflow into four digits. The length guard is what
73    /// stops that, and this pins it, because removing the guard still passes every test above.
74    #[test]
75    fn octet_length_is_bounded_before_the_value_is() {
76        assert_eq!(Ipv4::parse("1.2.3.0255"), None);
77        assert_eq!(Ipv4::parse("1.2.3.255"), Some(Ipv4([1, 2, 3, 255])));
78    }
79}