cidr 0.3.2

IP network and IP host within network types
Documentation
use core::net::{
	AddrParseError,
	IpAddr,
	Ipv4Addr,
};

/// Parse "loose" IPv4 address similar to POSIX `inet_addr`
///
/// This also accepts inputs that are not 4 decimal represented octects separated by dots.
///
/// * Allows less than 4 numbers separated by dots; the last number is
///   interpreted as an 32-bit (no dot), 24-bit (1 dot), 16-bit (2 dots) number.
///   Other numbers still must be octets (i.e. 8 bit)
/// * Allows hexadecimal ("0x" or "0X" prefix) and octal ("0" prefix) numbers
///   in all places.
///
/// See <https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_addr.html>
pub fn inet_addr(s: &str) -> Option<Ipv4Addr> {
	let mut slots = [0u32; 4];
	let mut last_slot = 0;
	for (ndx, part) in s.split('.').enumerate() {
		if ndx >= 4 {
			// too many '.'
			return None;
		}
		let slot = if part.starts_with("0x") || part.starts_with("0X") {
			let part = &part[2..];
			if part.starts_with('+') {
				// we don't want to support "0x+..."
				return None;
			}
			u32::from_str_radix(part, 16)
		} else if part.starts_with('0') {
			u32::from_str_radix(part, 8)
		} else {
			part.parse::<u32>()
		};
		slots[ndx] = slot.ok()?;
		last_slot = ndx;
	}
	debug_assert!(last_slot <= 3, "can't have more than 4");

	let num = if last_slot == 0 {
		slots[0]
	} else {
		let mut base: u32 = 0;
		for (ndx, &slot) in slots.iter().enumerate().take(last_slot) {
			if slot >= 256 {
				// leading parts must be octects
				return None;
			}
			// shift by 24, 16 or 8
			base |= slots[ndx] << ((3 - ndx) * 8);
		}
		// last 3 => have 4 => 1 byte, last 2 => have 3 => 2 bytes, last 1 => have 2 => 3 bytes
		let last_slot_bit_limit = (4 - last_slot) * 8;
		if slots[last_slot] >= 1 << last_slot_bit_limit {
			// last part too big
			return None;
		}
		// never shift last slot
		base | slots[last_slot]
	};

	Some(Ipv4Addr::from(num))
}

/// Parse IPv4 address; fall back to `inet_addr` if normal parser fails
pub fn parse_loose_ipv4(s: &str) -> Result<Ipv4Addr, AddrParseError> {
	match s.parse() {
		Ok(a) => Ok(a),
		Err(e) => {
			if let Some(a) = inet_addr(s) {
				Ok(a)
			} else {
				Err(e)
			}
		},
	}
}

/// Parse IP address; fall back to `inet_addr` if normal parser fails
pub fn parse_loose_ip(s: &str) -> Result<IpAddr, AddrParseError> {
	match s.parse() {
		Ok(a) => Ok(a),
		Err(e) => {
			if let Some(a) = inet_addr(s) {
				Ok(IpAddr::V4(a))
			} else {
				Err(e)
			}
		},
	}
}

#[cfg(test)]
mod tests {
	use core::net::{
		AddrParseError,
		IpAddr,
		Ipv4Addr,
	};

	fn run(s: &str, expect: Ipv4Addr) {
		assert_eq!(super::inet_addr(s), Some(expect));
		assert_eq!(super::parse_loose_ipv4(s), Ok::<_, AddrParseError>(expect));
		assert_eq!(
			super::parse_loose_ip(s),
			Ok::<_, AddrParseError>(IpAddr::V4(expect))
		);
	}

	#[test]
	fn test_loose_ipv4() {
		run("10.0.1.2", Ipv4Addr::from([10, 0, 1, 2]));
		run("10.0.258", Ipv4Addr::from([10, 0, 1, 2]));
		run("0xA000102", Ipv4Addr::from([10, 0, 1, 2]));
		run("0XA000102", Ipv4Addr::from([10, 0, 1, 2]));
		run("012.0x102", Ipv4Addr::from([10, 0, 1, 2]));

		run("127.0.0.1", Ipv4Addr::from([127, 0, 0, 1]));
		run("127.0.1", Ipv4Addr::from([127, 0, 0, 1]));
		run("127.1", Ipv4Addr::from([127, 0, 0, 1]));

		run("0", Ipv4Addr::from([0, 0, 0, 0]));
		run("1", Ipv4Addr::from([0, 0, 0, 1]));
	}
}