use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::LazyLock;
use crate::cbor_utils::map_get;
use crate::native::core::{NativeTestCase, StopTest};
use ciborium::Value;
fn encode_string(s: String) -> Value {
Value::Tag(91, Box::new(Value::Bytes(s.into_bytes())))
}
fn days_in_month(year: i128, month: i128) -> i128 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
if is_leap { 29 } else { 28 }
}
_ => unreachable!("days_in_month: month {} out of range 1..=12", month),
}
}
pub(super) fn interpret_date(ntc: &mut NativeTestCase) -> Result<Value, StopTest> {
let (year, month, day) = draw_date(ntc)?;
Ok(encode_string(format!("{year:04}-{month:02}-{day:02}")))
}
pub(super) fn interpret_time(ntc: &mut NativeTestCase) -> Result<Value, StopTest> {
let (hour, minute, second, microsecond) = draw_time(ntc)?;
Ok(encode_string(format_time(
hour,
minute,
second,
microsecond,
)))
}
pub(super) fn interpret_datetime(ntc: &mut NativeTestCase) -> Result<Value, StopTest> {
let (year, month, day) = draw_date(ntc)?;
let (hour, minute, second, microsecond) = draw_time(ntc)?;
let time_part = format_time(hour, minute, second, microsecond);
Ok(encode_string(format!(
"{year:04}-{month:02}-{day:02}T{time_part}"
)))
}
fn draw_date(ntc: &mut NativeTestCase) -> Result<(i128, i128, i128), StopTest> {
let year = 2000 + ntc.draw_integer(1 - 2000, 9999 - 2000)?;
let month = ntc.draw_integer(1, 12)?;
let day = ntc.draw_integer(1, days_in_month(year, month))?;
Ok((year, month, day))
}
fn draw_time(ntc: &mut NativeTestCase) -> Result<(i128, i128, i128, i128), StopTest> {
let hour = ntc.draw_integer(0, 23)?;
let minute = ntc.draw_integer(0, 59)?;
let second = ntc.draw_integer(0, 59)?;
let microsecond = ntc.draw_integer(0, 999_999)?;
Ok((hour, minute, second, microsecond))
}
fn format_time(hour: i128, minute: i128, second: i128, microsecond: i128) -> String {
if microsecond == 0 {
format!("{hour:02}:{minute:02}:{second:02}")
} else {
format!("{hour:02}:{minute:02}:{second:02}.{microsecond:06}")
}
}
pub(super) fn interpret_uuid(ntc: &mut NativeTestCase, schema: &Value) -> Result<Value, StopTest> {
use crate::cbor_utils::as_u64;
let version = map_get(schema, "version").and_then(as_u64).map(|v| v as u8);
let hi = ntc.draw_integer(0, u64::MAX as i128)? as u64;
let lo = ntc.draw_integer(0, u64::MAX as i128)? as u64;
let mut n: u128 = (u128::from(hi) << 64) | u128::from(lo);
if let Some(v) = version {
n &= !(0xc000u128 << 48);
n |= 0x8000u128 << 48;
n &= !(0xf000u128 << 64);
n |= u128::from(v) << 76;
} else if n == 0 {
n = 1;
}
let bytes = n.to_be_bytes();
Ok(encode_string(format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
bytes[6],
bytes[7],
bytes[8],
bytes[9],
bytes[10],
bytes[11],
bytes[12],
bytes[13],
bytes[14],
bytes[15],
)))
}
const SPECIAL_IPV4_CIDRS: &[&str] = &[
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.0.0/29",
"192.0.0.8/32",
"192.0.0.9/32",
"192.0.0.10/32",
"192.0.0.170/32",
"192.0.0.171/32",
"192.0.2.0/24",
"192.31.196.0/24",
"192.52.193.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"192.175.48.0/24",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"240.0.0.0/4",
"255.255.255.255/32",
];
const SPECIAL_IPV6_CIDRS: &[&str] = &[
"::1/128",
"::/128",
"::ffff:0:0/96",
"64:ff9b::/96",
"64:ff9b:1::/48",
"100::/64",
"2001::/23",
"2001::/32",
"2001:1::1/128",
"2001:1::2/128",
"2001:2::/48",
"2001:3::/32",
"2001:4:112::/48",
"2001:10::/28",
"2001:20::/28",
"2001:db8::/32",
"2002::/16",
"2620:4f:8000::/48",
"fc00::/7",
"fe80::/10",
];
struct V4Network {
base: u32,
size_minus_1: u32,
}
struct V6Network {
base: u128,
size_minus_1: u128,
}
static SPECIAL_IPV4_NETWORKS: LazyLock<Vec<V4Network>> = LazyLock::new(|| {
SPECIAL_IPV4_CIDRS
.iter()
.map(|s| parse_v4_cidr(s))
.collect()
});
static SPECIAL_IPV6_NETWORKS: LazyLock<Vec<V6Network>> = LazyLock::new(|| {
SPECIAL_IPV6_CIDRS
.iter()
.map(|s| parse_v6_cidr(s))
.collect()
});
fn parse_v4_cidr(s: &str) -> V4Network {
let (addr, prefix) = s.split_once('/').unwrap();
let addr: Ipv4Addr = addr.parse().unwrap();
let prefix: u32 = prefix.parse().unwrap();
assert!(
(1..=32).contains(&prefix),
"IPv4 prefix must be in 1..=32, got /{prefix}"
);
let mask = u32::MAX << (32 - prefix);
let base = u32::from(addr) & mask;
let size_minus_1 = !mask;
V4Network { base, size_minus_1 }
}
fn parse_v6_cidr(s: &str) -> V6Network {
let (addr, prefix) = s.split_once('/').unwrap();
let addr: Ipv6Addr = addr.parse().unwrap();
let prefix: u32 = prefix.parse().unwrap();
assert!(
(1..=128).contains(&prefix),
"IPv6 prefix must be in 1..=128, got /{prefix}"
);
let mask = u128::MAX << (128 - prefix);
let base = u128::from(addr) & mask;
let size_minus_1 = !mask;
assert!(
size_minus_1 <= i128::MAX as u128,
"IPv6 special range too wide to fit offset in i128: prefix /{prefix}"
);
V6Network { base, size_minus_1 }
}
pub(super) fn interpret_ip_address(
ntc: &mut NativeTestCase,
schema: &Value,
) -> Result<Value, StopTest> {
use crate::cbor_utils::as_u64;
let version = map_get(schema, "version")
.and_then(as_u64)
.expect("ip_address schema must have a \"version\" field");
match version {
4 => interpret_ipv4(ntc),
6 => interpret_ipv6(ntc),
_ => panic!("ip_address: unsupported version {version}; expected 4 or 6"),
}
}
fn interpret_ipv4(ntc: &mut NativeTestCase) -> Result<Value, StopTest> {
let addr_int: u32 = if ntc.draw_integer(0, 1)? == 0 {
let a = ntc.draw_integer(0, 255)? as u32;
let b = ntc.draw_integer(0, 255)? as u32;
let c = ntc.draw_integer(0, 255)? as u32;
let d = ntc.draw_integer(0, 255)? as u32;
(a << 24) | (b << 16) | (c << 8) | d
} else {
let nets = &*SPECIAL_IPV4_NETWORKS;
let idx = ntc.draw_integer(0, (nets.len() - 1) as i128)? as usize;
let net = &nets[idx];
let offset = ntc.draw_integer(0, net.size_minus_1 as i128)? as u32;
net.base + offset
};
Ok(encode_string(Ipv4Addr::from(addr_int).to_string()))
}
fn interpret_ipv6(ntc: &mut NativeTestCase) -> Result<Value, StopTest> {
let addr_int: u128 = if ntc.draw_integer(0, 1)? == 0 {
let hi = ntc.draw_integer(0, u64::MAX as i128)? as u64;
let lo = ntc.draw_integer(0, u64::MAX as i128)? as u64;
(u128::from(hi) << 64) | u128::from(lo)
} else {
let nets = &*SPECIAL_IPV6_NETWORKS;
let idx = ntc.draw_integer(0, (nets.len() - 1) as i128)? as usize;
let net = &nets[idx];
let offset = ntc.draw_integer(0, net.size_minus_1 as i128)? as u128;
net.base + offset
};
Ok(encode_string(Ipv6Addr::from(addr_int).to_string()))
}
#[cfg(test)]
#[path = "../../../tests/embedded/native/schema/special_tests.rs"]
mod tests;