use core::fmt;
use binrw::{BinRead, BinWrite};
#[derive(BinRead, BinWrite, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[br(little)]
pub struct OsVersionPatch(pub u32);
impl OsVersionPatch {
#[must_use]
pub const fn new(version: OsVersion, patch: OsPatch) -> Self {
Self((version.0 << 11) + patch.0 as u32)
}
#[must_use]
pub const fn version(self) -> OsVersion {
OsVersion(self.0 >> 11)
}
#[must_use]
pub const fn patch(self) -> OsPatch {
OsPatch((self.0 & 0x7ff) as u16)
}
}
impl fmt::Debug for OsVersionPatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "OsVersionPatch({}, {})", self.version(), self.patch())
}
}
#[non_exhaustive]
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum OsPatchError {
#[error("`year` under 2000, which is not supported by the format.")]
YearTooSmall,
#[error("`year` over 6095, which is not supported by the format (`year - 2000` won't fit into 12 bits).")]
YearWontFit,
#[error("`month` over 15, which is not supported by the format (won't fit into 4 bits).")]
MonthWontFit,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OsPatch(pub u16);
impl OsPatch {
pub const fn new(year: u16, month: u8) -> Result<Self, OsPatchError> {
const U12_MAX: u16 = 2_u16.pow(12) - 1;
const U4_MAX: u8 = 2_u8.pow(4) - 1;
let Some(years_after_2000) = year.checked_sub(2000) else {
return Err(OsPatchError::YearTooSmall);
};
if years_after_2000 > U12_MAX {
return Err(OsPatchError::YearWontFit);
}
if month > U4_MAX {
return Err(OsPatchError::MonthWontFit);
}
Ok(Self((years_after_2000 << 4) + month as u16))
}
#[must_use]
pub const fn year(self) -> u16 {
(self.0 >> 4) + 2000
}
#[must_use]
pub const fn month(self) -> u8 {
(self.0 & 0xf) as u8
}
}
impl fmt::Display for OsPatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{:02}", self.year(), self.month())
}
}
impl fmt::Debug for OsPatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(thiserror::Error, Debug, PartialEq)]
#[error("A version component is over 127, which is not supported by the format (won't fit into 7 bits).")]
pub struct OsVersionWontFitError;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OsVersion(pub u32);
impl OsVersion {
pub const fn new(a: u8, b: u8, c: u8) -> Result<Self, OsVersionWontFitError> {
const U7_MAX: u8 = 2_u8.pow(7) - 1;
if a > U7_MAX || b > U7_MAX || c > U7_MAX {
return Err(OsVersionWontFitError);
}
Ok(Self(((a as u32) << 14) | ((b as u32) << 7) | c as u32))
}
#[must_use]
pub const fn version_parts(self) -> (u8, u8, u8) {
let x = self.0;
let a = (x >> 14) & 0x7f; let b = (x >> 7) & 0x7f; let c = x & 0x7f; (a as u8, b as u8, c as u8)
}
}
impl fmt::Display for OsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (a, b, c) = self.version_parts();
write!(f, "{a}.{b}.{c}")
}
}
impl fmt::Debug for OsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::{format, string::ToString as _};
use super::*;
#[test]
fn basic() {
let vp = OsVersionPatch(0x1800_0186);
assert_eq!(format!("{vp:?}"), "OsVersionPatch(12.0.0, 2024-06)");
assert_eq!(vp.version().to_string(), "12.0.0");
assert_eq!(vp.patch().to_string(), "2024-06");
assert_eq!(vp, OsVersionPatch::new(vp.version(), vp.patch()));
assert_eq!(Ok(vp.version()), OsVersion::new(12, 0, 0));
assert_eq!(Ok(vp.patch()), OsPatch::new(2024, 6));
}
#[test]
fn truncating_behavior() {
let ver = OsVersion(0b1111_1111 << 14);
assert_eq!(ver.version_parts(), (0b0111_1111, 0, 0));
assert_eq!(format!("{ver:?}"), "127.0.0");
assert_eq!(ver.to_string(), "127.0.0");
}
#[test]
fn errors() {
assert_eq!(OsVersion::new(0, 0, 0), Ok(OsVersion(0)));
assert_eq!(OsVersion::new(128, 0, 0), Err(OsVersionWontFitError));
assert_eq!(OsVersion::new(0, 128, 0), Err(OsVersionWontFitError));
assert_eq!(OsVersion::new(0, 0, 128), Err(OsVersionWontFitError));
assert_eq!(OsPatch::new(0, 0), Err(OsPatchError::YearTooSmall));
assert_eq!(OsPatch::new(1999, 0), Err(OsPatchError::YearTooSmall));
OsPatch::new(2000, 0).unwrap();
OsPatch::new(6095, 0).unwrap();
assert_eq!(OsPatch::new(6096, 0), Err(OsPatchError::YearWontFit));
OsPatch::new(2000, 15).unwrap();
assert_eq!(OsPatch::new(2000, 16), Err(OsPatchError::MonthWontFit));
}
}