1use core::fmt;
2
3use binrw::{BinRead, BinWrite};
4
5#[derive(BinRead, BinWrite, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[br(little)]
20pub struct OsVersionPatch(pub u32);
21
22impl OsVersionPatch {
23 #[must_use]
25 pub const fn new(version: OsVersion, patch: OsPatch) -> Self {
26 Self((version.0 << 11) + patch.0 as u32)
27 }
28 #[must_use]
30 pub const fn version(self) -> OsVersion {
31 OsVersion(self.0 >> 11)
32 }
33 #[must_use]
35 pub const fn patch(self) -> OsPatch {
36 OsPatch((self.0 & 0x7ff) as u16)
37 }
38}
39
40impl fmt::Debug for OsVersionPatch {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(f, "OsVersionPatch({}, {})", self.version(), self.patch())
43 }
44}
45
46#[non_exhaustive]
48#[derive(thiserror::Error, Debug, PartialEq)]
49pub enum OsPatchError {
50 #[error("`year` under 2000, which is not supported by the format.")]
52 YearTooSmall,
53 #[error("`year` over 6095, which is not supported by the format (`year - 2000` won't fit into 12 bits).")]
55 YearWontFit,
56 #[error("`month` over 15, which is not supported by the format (won't fit into 4 bits).")]
58 MonthWontFit,
59}
60
61#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub struct OsPatch(pub u16);
69impl OsPatch {
70 pub const fn new(year: u16, month: u8) -> Result<Self, OsPatchError> {
80 const U12_MAX: u16 = 2_u16.pow(12) - 1;
81 const U4_MAX: u8 = 2_u8.pow(4) - 1;
82
83 let Some(years_after_2000) = year.checked_sub(2000) else {
84 return Err(OsPatchError::YearTooSmall);
85 };
86
87 if years_after_2000 > U12_MAX {
88 return Err(OsPatchError::YearWontFit);
89 }
90
91 if month > U4_MAX {
92 return Err(OsPatchError::MonthWontFit);
93 }
94
95 Ok(Self((years_after_2000 << 4) + month as u16))
96 }
97 #[must_use]
99 pub const fn year(self) -> u16 {
100 (self.0 >> 4) + 2000
102 }
103 #[must_use]
105 pub const fn month(self) -> u8 {
106 (self.0 & 0xf) as u8
108 }
109}
110
111impl fmt::Display for OsPatch {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}-{:02}", self.year(), self.month())
114 }
115}
116impl fmt::Debug for OsPatch {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 fmt::Display::fmt(self, f)
119 }
120}
121
122#[derive(thiserror::Error, Debug, PartialEq)]
126#[error("A version component is over 127, which is not supported by the format (won't fit into 7 bits).")]
127pub struct OsVersionWontFitError;
128
129#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub struct OsVersion(pub u32);
142impl OsVersion {
143 pub const fn new(a: u8, b: u8, c: u8) -> Result<Self, OsVersionWontFitError> {
150 const U7_MAX: u8 = 2_u8.pow(7) - 1;
151
152 if a > U7_MAX || b > U7_MAX || c > U7_MAX {
153 return Err(OsVersionWontFitError);
154 }
155
156 Ok(Self(((a as u32) << 14) | ((b as u32) << 7) | c as u32))
157 }
158 #[must_use]
160 pub const fn version_parts(self) -> (u8, u8, u8) {
161 let x = self.0;
162 let a = (x >> 14) & 0x7f; let b = (x >> 7) & 0x7f; let c = x & 0x7f; (a as u8, b as u8, c as u8)
166 }
167}
168
169impl fmt::Display for OsVersion {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 let (a, b, c) = self.version_parts();
172 write!(f, "{a}.{b}.{c}")
173 }
174}
175impl fmt::Debug for OsVersion {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 fmt::Display::fmt(self, f)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use alloc::{format, string::ToString as _};
184
185 use super::*;
186
187 #[test]
188 fn basic() {
189 let vp = OsVersionPatch(0x1800_0186);
190 assert_eq!(format!("{vp:?}"), "OsVersionPatch(12.0.0, 2024-06)");
191 assert_eq!(vp.version().to_string(), "12.0.0");
192 assert_eq!(vp.patch().to_string(), "2024-06");
193 assert_eq!(vp, OsVersionPatch::new(vp.version(), vp.patch()));
194 assert_eq!(Ok(vp.version()), OsVersion::new(12, 0, 0));
195 assert_eq!(Ok(vp.patch()), OsPatch::new(2024, 6));
196 }
197
198 #[test]
199 fn truncating_behavior() {
200 let ver = OsVersion(0b1111_1111 << 14);
201 assert_eq!(ver.version_parts(), (0b0111_1111, 0, 0));
202 assert_eq!(format!("{ver:?}"), "127.0.0");
203 assert_eq!(ver.to_string(), "127.0.0");
204 }
205
206 #[test]
207 fn errors() {
208 assert_eq!(OsVersion::new(0, 0, 0), Ok(OsVersion(0)));
209 assert_eq!(OsVersion::new(128, 0, 0), Err(OsVersionWontFitError));
210 assert_eq!(OsVersion::new(0, 128, 0), Err(OsVersionWontFitError));
211 assert_eq!(OsVersion::new(0, 0, 128), Err(OsVersionWontFitError));
212
213 assert_eq!(OsPatch::new(0, 0), Err(OsPatchError::YearTooSmall));
214 assert_eq!(OsPatch::new(1999, 0), Err(OsPatchError::YearTooSmall));
215 OsPatch::new(2000, 0).unwrap();
216
217 OsPatch::new(6095, 0).unwrap();
218 assert_eq!(OsPatch::new(6096, 0), Err(OsPatchError::YearWontFit));
219
220 OsPatch::new(2000, 15).unwrap();
221 assert_eq!(OsPatch::new(2000, 16), Err(OsPatchError::MonthWontFit));
222 }
223}