use crate::model::network_address::Address;
use crate::model::network_address::Address::{IPv4, IPv6, Unknown};
use crate::model::network_address::{NetworkAddress, ValidIpAddress};
pub type AddressType = Address<NetworkAddress>;
pub fn parse_address(address: &str) -> Address<NetworkAddress> {
parse_address_with(
address.to_string(),
NetworkAddress::parse,
extract_ipv4_embedded_in_ipv6,
)
}
pub fn remove_host(network_address: &str) -> String {
let parts: Vec<&str> = network_address.split('/').collect();
if parts.len() == 2 {
if parts[1].chars().all(|c| c.is_numeric()) {
return network_address.to_string();
}
return parts[1].to_string();
}
if parts.len() == 3 {
return format!("{}/{}", parts[1], parts[2]);
}
network_address.to_string()
}
fn extract_ipv4_embedded_in_ipv6(addr: String) -> String {
let addr = remove_host(&addr);
let extract_last_four_octets = |address: &str| {
address
.split(':')
.next_back()
.unwrap_or_default()
.to_string()
};
if addr.starts_with("::ffff:") {
extract_last_four_octets(&addr)
} else {
addr
}
}
fn parse_address_with<E, N, F, G>(address: String, parser: F, preprocess: G) -> Address<N>
where
F: FnOnce(String) -> Result<N, E>,
G: FnOnce(String) -> String,
N: ValidIpAddress,
{
let preprocessed = preprocess(address);
let net = parser(preprocessed);
net.map(|address| {
if address.is_ipv6() {
IPv6(address)
} else if address.is_ipv4() {
IPv4(address)
} else {
Unknown
}
})
.unwrap_or(Unknown)
}
#[allow(non_snake_case)]
#[cfg(test)]
mod address_parser_tests {
extern crate test_case;
use crate::model::network_address::ValidIpAddress;
use crate::IpFilterError;
use super::*;
mod host_remover_tests {
use super::remove_host;
#[test]
fn given_address_with_mask_and_no_prefix__when_removing_prefix__then_returns_same_address()
{
let address = "192.168.0.1/24";
let address_wo_prefix = remove_host(address);
assert_eq!(address, address_wo_prefix)
}
#[test]
fn given_address_with_prefix_and_no_mask__when_removing_prefix__then_removes_host() {
let address = "host/192.168.0.1";
let address_wo_prefix = remove_host(address);
assert_eq!("192.168.0.1", address_wo_prefix)
}
#[test]
fn given_address_with_prefix_and_mask__when_removing_prefix__then_removes_host() {
let address = "host/192.168.0.1/24";
let address_wo_prefix = remove_host(address);
assert_eq!("192.168.0.1/24", address_wo_prefix)
}
}
mod ipv4_tests {
use test_case::test_case;
use crate::model::network_address::NetworkAddress;
use super::*;
#[test_case("192"; "ipv4 missing three octets")]
#[test_case("..."; "ipv4 all octets empty")]
#[test_case("0..."; "ipv4 three octets empty")]
#[test_case("1.2.."; "ipv4 two octets empty")]
#[test_case("192.0.0.-1"; "ipv4 negative octet")]
#[test_case("192.0.0.256"; "ipv4 octet greater than 255")]
fn given_invalid_IPv4_string__when_parsing__then_invalid_ip_is_returned(invalid_ip: &str) {
assert_eq!(Unknown, parse_address(invalid_ip));
}
#[test_case("192.0.0.1", "194.0.0.1"; "ipv4 without port")]
#[test_case("192.0.0.1:80", "193.0.0.1:80"; "ipv4 socket")]
#[test_case("host/192.0.0.1", "host/193.0.0.1"; "without port with host/ prefix")]
#[test_case("host/192.0.0.1:8080", "host/193.0.0.2:8080"; "ipv4 socket with host/ prefix")]
fn given_valid_ipv4_address__then_address_is_preserved(valid_addr: &str, other_addr: &str) {
let addr = parse_address(valid_addr);
let same_addr = parse_address(valid_addr);
let other_addr = parse_address(other_addr);
assert_ne!(other_addr, addr);
assert_eq!(same_addr, addr);
}
#[test_case("192.0.0.1"; "ipv4 without port")]
#[test_case("192.0.0.1:8080"; "ipv4 socket")]
#[test_case("host/192.0.0.1"; "ipv4 without port with host/ prefix")]
#[test_case("host/192.0.0.1:8080"; "ipv4 socket host/ prefix")]
fn given_valid_IPv4_string__when_parsing__then_ipv4_is_returned(addr: &str) {
let valid_ipv4 = String::from(addr);
let parsed_address = parse_address(&valid_ipv4);
assert_is_ipv4(parsed_address)
}
fn assert_is_ipv4(parsed_address: Address<NetworkAddress>) {
match parsed_address {
IPv4(_) => {}
_ => panic!(),
}
}
}
mod ipv6_tests {
use test_case::test_case;
use crate::model::network_address::NetworkAddress;
use super::*;
#[test_case("2001"; "ipv6 missing three quartets")]
#[test_case("::::::"; "ipv6 all quartets empty")]
#[test_case("2001..."; "ipv6 three quartets empty")]
#[test_case("2001.db8.."; "ipv6 two quartets empty")]
#[test_case("2001:db8:0:0:0:0:A:-1"; "ipv6 negative quartet")]
#[test_case("2001:db8:0:0:0:0:A:fffff"; "ipv6 quartet greater than 255")]
#[test_case("[2001:db8:0:0:0:0:A:0]:"; "ipv6 missing port")]
fn given_invalid_ipv6_string__when_parsing__then_invalid_ip_is_returned(invalid_ip: &str) {
assert_eq!(Unknown, parse_address(invalid_ip));
}
#[test_case("2001:db8:0:0:0:0:A:0", "2001:db8:0:0:0:0:A:B"; "ipv6 without port")]
#[test_case("[2001:db8:0:0:0:0:A:0]:8080", "2001:db8:0:0:0:0:A:B"; "ipv6 socket")]
#[test_case("host/2001:db8:0:0:0:0:A:0", "2001:db8:0:0:0:0:A:B"; "ipv6 without port with host/ prefix")]
#[test_case("host/[2001:db8:0:0:0:0:A:0]:8080", "2001:db8:0:0:0:0:A:B"; "ipv6 socket host/ prefix")]
#[test_case("::", "::1"; "abridged ipv6 loopback")]
#[test_case("::/0", "::1/10"; "abridged ipv6 loopback with mask")]
fn given_valid_ipv6_address__then_address_is_preserved(valid_addr: &str, other_addr: &str) {
let addr = parse_address(valid_addr);
let same_addr = parse_address(valid_addr);
let other_addr = parse_address(other_addr);
assert_ne!(other_addr, addr);
assert_eq!(same_addr, addr);
}
#[test_case("2001:db8:0:0:0:0:A:0"; "ipv6 without port")]
#[test_case("[2001:db8:0:0:0:0:A:0]:8080"; "ipv6 socket")]
#[test_case("host/2001:db8:0:0:0:0:A:0"; "ipv6 without port with host/ prefix")]
#[test_case("host/[2001:db8:0:0:0:0:A:0]:8080"; "ipv6 socket host/ prefix")]
#[test_case("::"; "abridged ipv6 loopback")]
#[test_case("::/0"; "abridged ipv6 loopback with mask")]
fn given_valid_IPv6_string__when_parsing__then_ipv6_is_returned(addr: &str) {
let valid_ipv6 = String::from(addr);
let parsed_address = parse_address(&valid_ipv6);
assert_is_ipv6(parsed_address)
}
fn assert_is_ipv6(parsed_address: Address<NetworkAddress>) {
match parsed_address {
IPv6(_) => {}
_ => panic!(),
}
}
#[test]
fn abtirdeg() {
let abridged = "::/0";
let addr = parse_address(abridged);
match addr {
IPv4(_) => unreachable!(),
IPv6(_) => {}
Unknown => unreachable!(),
}
}
}
struct FakeAddress;
impl ValidIpAddress for FakeAddress {
fn is_ipv6(&self) -> bool {
false
}
fn is_ipv4(&self) -> bool {
false
}
}
#[test]
fn given_address_str__when_parsed_address_is_not_ipv4_nor_ipv6__then_parse_address_returns_unknown(
) {
let parser = |_s: String| Result::Ok::<FakeAddress, IpFilterError>(FakeAddress);
let addr = parse_address_with("".parse().unwrap(), parser, |str| str);
match addr {
Unknown => {}
_ => panic!(),
}
}
mod cidr_tests {
use crate::model::address_parser::parse_address;
#[test]
fn given_31_bit_mask__then_contains_two_addresses() {
let subnet = parse_address(&String::from("192.168.0.0/31"));
let addr1 = parse_address(&String::from("192.168.0.0"));
let addr2 = parse_address(&String::from("192.168.0.1"));
let addr3 = parse_address(&String::from("192.168.0.2"));
assert!(subnet.contains(&addr1));
assert!(subnet.contains(&addr2));
assert!(!subnet.contains(&addr3));
}
#[test]
fn given_ipv4_address_with_octet_missing__when_parsing__then_get_subnet_with_24_bit_mask() {
let subnet = parse_address(&String::from("192.168.0"));
let addr1 = parse_address(&String::from("192.168.0.0"));
let addr2 = parse_address(&String::from("192.168.0.255"));
assert!(subnet.contains(&addr1));
assert!(subnet.contains(&addr2));
}
#[test]
fn given_ipv6_address_with_octet_missing__when_parsing__then_get_subnet_with_24_bit_mask() {
let subnet = parse_address(&String::from("2001:db8:0:0:0:0:A"));
let addr1 = parse_address(&String::from("2001:db8:0:0:0:0:A:0"));
let addr2 = parse_address(&String::from("2001:db8:0:0:0:0:A:FFFF"));
assert!(subnet.contains(&addr1));
assert!(subnet.contains(&addr2));
}
}
mod ipnet_test {
use std::net::IpAddr;
use ipnet::IpNet;
#[test]
fn given_valid_ipv4__then_parsing_to_ipnet_success_and_is_ipv4() {
let parsed_ip = "192.168.0.0".parse::<IpAddr>();
assert!(parsed_ip.is_ok());
assert!(parsed_ip.unwrap().is_ipv4());
}
#[test]
fn given_valid_ipv4_cidr_with_32bit_mask__then_contains_that_ip_and_no_other() {
let net: IpNet = "10.1.1.0/32".parse().unwrap();
let same_addr1: IpAddr = "10.1.1.0".parse().unwrap();
let sibling_addr1: IpAddr = "10.1.1.255".parse().unwrap();
let sibling_addr2: IpAddr = "10.1.0.1".parse().unwrap();
let sibling_addr3: IpAddr = "10.1.0.255".parse().unwrap();
assert!(net.contains(&same_addr1));
assert!(!net.contains(&sibling_addr3));
assert!(!net.contains(&sibling_addr1));
assert!(!net.contains(&sibling_addr2));
}
#[test]
fn given_valid_ipv4__then_can_convert_to_ipv4_cidr_with_32bit_mask() {
let addr: IpAddr = "10.1.1.0".parse().unwrap();
let net: IpNet = IpNet::from(addr);
let same_addr1: IpAddr = "10.1.1.0".parse().unwrap();
let sibling_addr1: IpAddr = "10.1.1.255".parse().unwrap();
let sibling_addr2: IpAddr = "10.1.0.1".parse().unwrap();
let sibling_addr3: IpAddr = "10.1.0.255".parse().unwrap();
assert!(net.contains(&same_addr1));
assert!(!net.contains(&sibling_addr3));
assert!(!net.contains(&sibling_addr1));
assert!(!net.contains(&sibling_addr2));
}
#[test]
fn abridged_loopback_ipv6_decoded_successfully() {
let abridged: IpAddr = "::1".parse().unwrap();
let unabridged: IpAddr = "0:0:0:0:0:0:0:1".parse().unwrap();
assert!(abridged.is_ipv6());
assert_eq!(unabridged, abridged)
}
#[test]
fn abridged_unspecified_ipv6_decoded_successfully() {
let abridged: IpAddr = "::".parse().unwrap();
let unabridged: IpAddr = "0:0:0:0:0:0:0:0".parse().unwrap();
assert!(abridged.is_ipv6());
assert_eq!(unabridged, abridged)
}
#[test]
fn abridged_loopback_ipv6_cidr_decoded_successfully() {
let abridged: IpNet = "::1/0".parse().unwrap();
let unabridged: IpNet = "0:0:0:0:0:0:0:1/0".parse().unwrap();
assert!(abridged.addr().is_ipv6());
assert_eq!(unabridged, abridged)
}
#[test]
fn abridged_unspecified_ipv6_cidr_decoded_successfully() {
let abridged: IpNet = "::/0".parse().unwrap();
let unabridged: IpNet = "0:0:0:0:0:0:0:0/0".parse().unwrap();
assert!(abridged.addr().is_ipv6());
assert_eq!(unabridged, abridged)
}
}
mod embedded_ipv4_tests {
use super::*;
#[test]
fn parse_ipv4_embedded_in_ipv6_returns_ipv4() {
let addr = parse_address("::ffff:192.168.1.1");
assert!(matches!(addr, IPv4(_)));
}
#[test]
fn parse_ipv4_embedded_in_ipv6_with_host_prefix() {
let addr = parse_address("host/::ffff:10.0.0.1");
assert!(matches!(addr, IPv4(_)));
}
}
}