use crate::errors::Error;
use crate::ip4::cidr::CIDR;
use core::ops::Deref;
const PRIVATE_IP4_SUBNETS: [CIDR; 12] = [
CIDR::new([0, 0, 0, 0], 8),
CIDR::new([10, 0, 0, 0], 8),
CIDR::new([100, 64, 0, 0], 10),
CIDR::new([127, 0, 0, 0], 8),
CIDR::new([169, 254, 0, 0], 16),
CIDR::new([172, 16, 0, 0], 12),
CIDR::new([192, 0, 2, 0], 24),
CIDR::new([192, 168, 0, 0], 16),
CIDR::new([198, 18, 0, 0], 15),
CIDR::new([198, 51, 100, 0], 24),
CIDR::new([203, 0, 113, 0], 24),
CIDR::new([255, 255, 255, 255], 32),
];
#[derive(Debug, PartialEq, Eq)]
pub struct IPv4 {
octets: [u8; 4],
}
impl IPv4 {
#[inline]
pub const fn new(octets: [u8; 4]) -> IPv4 {
IPv4 { octets }
}
#[inline]
pub const fn as_octets(&self) -> [u8; 4] {
self.octets
}
#[inline]
pub const fn as_bits(&self) -> u32 {
(self.octets[0] as u32) << 24
| (self.octets[1] as u32) << 16
| (self.octets[2] as u32) << 8
| (self.octets[3] as u32)
}
#[inline]
pub const fn is_unicast(&self) -> bool {
self.octets[0] < 224
}
pub fn is_public(&self) -> bool {
for pip in &PRIVATE_IP4_SUBNETS {
if self.as_bits() & pip.as_bitmask() == IPv4::new(pip.as_prefix()).as_bits() {
return false;
}
}
true
}
pub fn parse(input: &[u8]) -> Result<IPv4, Error> {
if input.len() < 7 {
return Err(Error::InputTooShort);
}
if input.len() > 15 {
return Err(Error::InputTooLong);
}
let mut sections = 0;
let mut last_char_was_dot = true;
let mut octets = [0, 0, 0, 0];
let mut accumulator: u8 = 0;
for c in input {
match c {
b'0'..=b'9' => {
if accumulator == 0 && !last_char_was_dot {
return Err(Error::LeadingZero);
}
accumulator = if let Some(a) = accumulator.checked_mul(10) {
a
} else {
return Err(Error::OctetOverflow);
};
accumulator = if let Some(a) = accumulator.checked_add(c - b'0') {
a
} else {
return Err(Error::OctetOverflow);
};
last_char_was_dot = false;
}
b'.' => {
if last_char_was_dot {
return Err(Error::MissingOctet);
}
octets[sections] = accumulator;
sections += 1;
accumulator = 0;
if sections > 3 {
return Err(Error::TooManyOctets);
}
last_char_was_dot = true;
}
_ => return Err(Error::IllegalCharacter),
}
}
octets[sections] = accumulator;
if sections < 3 {
Err(Error::InsufficientOctets)
} else if last_char_was_dot {
Err(Error::MissingOctet)
} else {
Ok(IPv4 { octets })
}
}
}
impl Deref for IPv4 {
type Target = [u8; 4];
fn deref(&self) -> &Self::Target {
&self.octets
}
}
#[cfg(test)]
mod tests {
use crate::errors::Error;
#[test]
fn parse_valid_ips() {
assert_eq!(
super::IPv4::parse("1.1.1.1".as_bytes()),
Ok(super::IPv4::new([1, 1, 1, 1]))
);
assert_eq!(
super::IPv4::parse("100.200.10.0".as_bytes()),
Ok(super::IPv4::new([100, 200, 10, 0]))
);
assert_eq!(
super::IPv4::parse("255.255.255.255".as_bytes()),
Ok(super::IPv4::new([255, 255, 255, 255]))
);
assert_eq!(
super::IPv4::parse("0.0.0.0".as_bytes()),
Ok(super::IPv4::new([0, 0, 0, 0]))
);
}
#[test]
fn reject_invalid_ips() {
assert_eq!(
super::IPv4::parse("1.1.1.1.".as_bytes()),
Err(Error::TooManyOctets)
);
assert_eq!(
super::IPv4::parse("1.1.1.1.1".as_bytes()),
Err(Error::TooManyOctets)
);
assert_eq!(
super::IPv4::parse("1.1.1.".as_bytes()),
Err(Error::InputTooShort)
);
assert_eq!(
super::IPv4::parse("1.1.1".as_bytes()),
Err(Error::InputTooShort)
);
assert_eq!(
super::IPv4::parse("100.100.1".as_bytes()),
Err(Error::InsufficientOctets)
);
assert_eq!(
super::IPv4::parse("100.100.1.".as_bytes()),
Err(Error::MissingOctet)
);
assert_eq!(
super::IPv4::parse("255.255.255.256".as_bytes()),
Err(Error::OctetOverflow)
);
assert_eq!(
super::IPv4::parse("256.255.255.255".as_bytes()),
Err(Error::OctetOverflow)
);
assert_eq!(
super::IPv4::parse("1.10.100.1000".as_bytes()),
Err(Error::OctetOverflow)
);
assert_eq!(
super::IPv4::parse("1000.100.10.1".as_bytes()),
Err(Error::OctetOverflow)
);
assert_eq!(
super::IPv4::parse("af.fe.ff.ac".as_bytes()),
Err(Error::IllegalCharacter)
);
assert_eq!(
super::IPv4::parse("00.0.0.0".as_bytes()),
Err(Error::LeadingZero)
);
assert_eq!(
super::IPv4::parse("1.01.2.3".as_bytes()),
Err(Error::LeadingZero)
);
assert_eq!(
super::IPv4::parse("1:1:1:1".as_bytes()),
Err(Error::IllegalCharacter)
);
assert_eq!(
super::IPv4::parse("1.1.2_.3".as_bytes()),
Err(Error::IllegalCharacter)
);
}
#[test]
fn public_ip() {
assert_eq!(
super::IPv4::parse("1.1.1.1".as_bytes())
.unwrap()
.is_public(),
true
);
assert_eq!(
super::IPv4::parse("10.10.100.254".as_bytes())
.unwrap()
.is_public(),
false
);
assert_eq!(
super::IPv4::parse("10.10.100.254".as_bytes())
.unwrap()
.is_public(),
false
);
assert_eq!(
super::IPv4::parse("172.10.100.254".as_bytes())
.unwrap()
.is_public(),
true
);
assert_eq!(
super::IPv4::parse("172.20.100.254".as_bytes())
.unwrap()
.is_public(),
false
);
}
}