use crate::v4::error::Result;
use jiff::Zoned;
use mac_address::MacAddress;
use std::fmt::Display;
use std::net::Ipv4Addr;
use std::str::FromStr;
pub(super) const PAGE_SIZE: usize = 200;
#[derive(Clone, Debug, PartialEq)]
pub(super) enum LeaseStatus {
Offered,
Acked,
}
impl Display for LeaseStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LeaseStatus::Offered => write!(f, "Offered"),
LeaseStatus::Acked => write!(f, "Acked"),
}
}
}
impl From<&str> for LeaseStatus {
fn from(value: &str) -> Self {
match value {
"Offered" => Self::Offered,
"Acked" => Self::Acked,
_ => panic!("Unknown value used for LeaseStatus"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct Lease {
pub(super) line: u32,
pub(super) ip: Ipv4Addr,
pub(super) status: LeaseStatus,
pub(super) is_static: bool,
pub(super) when: Zoned,
}
impl Lease {
#[inline]
pub(super) fn new(ip: Ipv4Addr, status: LeaseStatus, is_static: bool) -> Self {
Self {
line: 0,
ip,
status,
is_static,
when: Zoned::now(),
}
}
pub(super) fn from_string(string: String, line: u32) -> Result<(MacAddress, Lease)> {
let fields: Vec<&str> = string.split(',').collect();
if fields.len() != 5 {
return Err("Invalid number of fields in lease file");
}
let fields: [&str; 5] = fields.try_into().unwrap();
let [mac_str, ip_str, status_str, static_str, when_str] = fields;
let owner = MacAddress::from_str(mac_str.trim_start_matches('\0'))
.expect("Problem parsing lease mac address");
let ip = Ipv4Addr::from_str(ip_str).expect("Problem parsing lease ip address");
let status = LeaseStatus::from(status_str);
let is_static = static_str
.parse()
.expect("Problem parsing lease is_static boolean");
let when = Zoned::from_str(when_str.trim_end()).expect("Problem parsing lease time");
let lease = Self {
line,
ip,
status,
is_static,
when,
};
Ok((owner, lease))
}
#[inline]
pub(super) fn extend(&mut self) {
self.when = Zoned::now();
}
pub(super) fn is_expired(&self, lease_time: u32) -> bool {
let elapsed = self.when.duration_until(&Zoned::now());
elapsed.as_secs() >= lease_time as i64
}
pub(super) fn to_string(&self, owner: MacAddress) -> String {
let mut string = String::with_capacity(PAGE_SIZE);
string.push_str(&owner.to_string());
string.push(',');
string.push_str(&self.ip.to_string()); string.push(',');
string.push_str(&self.status.to_string()); string.push(',');
string.push_str(&self.is_static.to_string()); string.push(',');
string.push_str(&self.when.to_string());
string
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_expired() {
let lease = Lease {
line: 0,
ip: Ipv4Addr::UNSPECIFIED,
status: LeaseStatus::Offered,
is_static: false,
when: Zoned::yesterday(&Zoned::now()).expect("Problem getting yesterday"),
};
let lease_time = 120;
assert_eq!(lease.is_expired(lease_time), true);
}
#[test]
fn test_not_is_expired() {
let lease = Lease {
line: 0,
ip: Ipv4Addr::UNSPECIFIED,
status: LeaseStatus::Offered,
is_static: false,
when: Zoned::yesterday(&Zoned::now()).expect("Problem getting yesterday"),
};
let lease_time = 172800;
assert_eq!(lease.is_expired(lease_time), false);
}
#[test]
fn test_lease_to_string() {
let lease = Lease {
line: 0,
ip: Ipv4Addr::UNSPECIFIED,
status: LeaseStatus::Offered,
is_static: false,
when: jiff::civil::date(2026, 1, 1)
.at(12, 0, 0, 0)
.in_tz("America/New_York")
.unwrap(),
};
let mac_address = MacAddress::new([0x01, 0x02, 0x03, 0x0A, 0x0B, 0x0C]);
let expected_string =
"01:02:03:0A:0B:0C,0.0.0.0,Offered,false,2026-01-01T12:00:00-05:00[America/New_York]";
assert_eq!(&lease.to_string(mac_address), expected_string);
}
#[test]
fn test_lease_from_string() {
let expected_lease = Lease {
line: 0,
ip: Ipv4Addr::UNSPECIFIED,
status: LeaseStatus::Offered,
is_static: false,
when: jiff::civil::date(2026, 1, 1)
.at(12, 0, 0, 0)
.in_tz("America/New_York")
.unwrap(),
};
let expected_mac_address = MacAddress::new([0x01, 0x02, 0x03, 0x0A, 0x0B, 0x0C]);
let lease_string =
"01:02:03:0A:0B:0C,0.0.0.0,Offered,false,2026-01-01T12:00:00-05:00[America/New_York]"
.to_string();
let (parsed_mac_address, parsed_lease) =
Lease::from_string(lease_string, 0).expect("Problem parsing lease from string");
assert_eq!(parsed_mac_address, expected_mac_address);
assert_eq!(parsed_lease, expected_lease);
}
}