From d81b445085e606331edc36e164470e07d918587b Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Fri, 10 Oct 2025 12:57:58 -0700 Subject: [PATCH] Limit setting/getting year to 20th century. Limit setting the year to the 20th centure. Generate an error for all date/time read functions when the century bit is set. --- CHANGELOG.md | 7 ++ src/ds323x/datetime.rs | 151 ++++++++++++++++++-------------- src/lib.rs | 5 ++ tests/common/mod.rs | 25 ++++++ tests/datetime.rs | 192 +++++++++++++++++++++++++++++++---------- 5 files changed, 271 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adaa735..68e7c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] - ReleaseDate +### Added + +- [breaking-change] Return Error::InvalidInputData when attempting to set the year to something + outside of the range 2000-2099 because all chips have a problem with the year 2100 and beyond. +- [breaking-change] Return Error::InvalidDeviceCentury when attempting to get a date and/or time value + and the the device's century bit is set because all chips have a problem withe year 2100 and beyond. + ## [0.7.0] - 2025-10-11 ### Added diff --git a/src/ds323x/datetime.rs b/src/ds323x/datetime.rs index 41fd610..0749941 100644 --- a/src/ds323x/datetime.rs +++ b/src/ds323x/datetime.rs @@ -19,11 +19,13 @@ where let mut data = [0; 8]; self.iface.read_data(&mut data)?; - let year = year_from_registers( - data[Register::MONTH as usize + 1], - data[Register::YEAR as usize + 1], - ); - let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1] & !BitFlags::CENTURY); + let century = data[Register::MONTH as usize + 1] & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + let year = 2000 + (packed_bcd_to_decimal(data[Register::YEAR as usize + 1]) as u16); + let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1]); let hour = hours_from_register(data[Register::HOURS as usize + 1]); let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); @@ -36,10 +38,9 @@ where } fn set_datetime(&mut self, datetime: &NaiveDateTime) -> Result<(), Self::Error> { - if datetime.year() < 2000 || datetime.year() > 2100 { + if !(2000..=2099).contains(&datetime.year()) { return Err(Error::InvalidInputData); } - let (month, year) = month_year_to_registers(datetime.month() as u8, datetime.year() as u16); let mut payload = [ Register::SECONDS, decimal_to_packed_bcd(datetime.second() as u8), @@ -47,8 +48,8 @@ where hours_to_register(Hours::H24(datetime.hour() as u8))?, datetime.weekday().number_from_sunday() as u8, decimal_to_packed_bcd(datetime.day() as u8), - month, - year, + decimal_to_packed_bcd(datetime.month() as u8), + decimal_to_packed_bcd((datetime.year() - 2000) as u8), ]; self.iface.write_data(&mut payload) } @@ -59,21 +60,50 @@ where DI: ReadData> + WriteData>, { fn seconds(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::SECONDS) } fn minutes(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::MINUTES) } fn hours(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + let data = self.iface.read_register(Register::HOURS)?; Ok(hours_from_register(data)) } fn time(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + let mut data = [0; 4]; self.iface.read_data(&mut data)?; + let hour = hours_from_register(data[Register::HOURS as usize + 1]); let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); @@ -83,24 +113,47 @@ where } fn weekday(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::DOW) } fn day(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::DOM) } fn month(&mut self) -> Result { let data = self.iface.read_register(Register::MONTH)?; - let value = data & !BitFlags::CENTURY; - Ok(packed_bcd_to_decimal(value)) + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + Ok(packed_bcd_to_decimal(data)) } fn year(&mut self) -> Result { - let mut data = [0; 3]; - data[0] = Register::MONTH; - self.iface.read_data(&mut data)?; - Ok(year_from_registers(data[1], data[2])) + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + Ok(2000 + (self.read_register_decimal(Register::YEAR)? as u16)) } fn date(&mut self) -> Result { @@ -109,12 +162,15 @@ where self.iface.read_data(&mut data)?; let offset = Register::DOM as usize; - let year = year_from_registers( - data[Register::MONTH as usize + 1 - offset], - data[Register::YEAR as usize + 1 - offset], - ); - let month = - packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset] & !BitFlags::CENTURY); + + let century = data[Register::MONTH as usize + 1 - offset] & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + let year = + 2000 + (packed_bcd_to_decimal(data[Register::YEAR as usize + 1 - offset]) as u16); + let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1 - offset]); let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); some_or_invalid_error(date) @@ -174,39 +230,30 @@ where } fn set_year(&mut self, year: u16) -> Result<(), Self::Error> { - if !(2000..=2100).contains(&year) { + if !(2000..=2099).contains(&year) { return Err(Error::InvalidInputData); } let data = self.iface.read_register(Register::MONTH)?; let month_bcd = data & !BitFlags::CENTURY; - if year > 2099 { - let mut data = [ - Register::MONTH, - BitFlags::CENTURY | month_bcd, - decimal_to_packed_bcd((year - 2100) as u8), - ]; - self.iface.write_data(&mut data) - } else { - let mut data = [ - Register::MONTH, - month_bcd, - decimal_to_packed_bcd((year - 2000) as u8), - ]; - self.iface.write_data(&mut data) - } + + let mut data = [ + Register::MONTH, + month_bcd, + decimal_to_packed_bcd((year - 2000) as u8), + ]; + self.iface.write_data(&mut data) } fn set_date(&mut self, date: &rtcc::NaiveDate) -> Result<(), Self::Error> { - if date.year() < 2000 || date.year() > 2100 { + if !(2000..=2099).contains(&date.year()) { return Err(Error::InvalidInputData); } - let (month, year) = month_year_to_registers(date.month() as u8, date.year() as u16); let mut payload = [ Register::DOW, date.weekday().number_from_sunday() as u8, decimal_to_packed_bcd(date.day() as u8), - month, - year, + decimal_to_packed_bcd(date.month() as u8), + decimal_to_packed_bcd((date.year() - 2000) as u8), ]; self.iface.write_data(&mut payload) } @@ -241,28 +288,6 @@ fn hours_from_register(data: u8) -> Hours { } } -fn year_from_registers(month: u8, year: u8) -> u16 { - let century = month & BitFlags::CENTURY; - let year = packed_bcd_to_decimal(year); - if century != 0 { - 2100 + u16::from(year) - } else { - 2000 + u16::from(year) - } -} - -fn month_year_to_registers(month: u8, year: u16) -> (u8, u8) { - if year > 2099 { - let month = BitFlags::CENTURY | decimal_to_packed_bcd(month); - (month, decimal_to_packed_bcd((year - 2100) as u8)) - } else { - ( - decimal_to_packed_bcd(month), - decimal_to_packed_bcd((year - 2000) as u8), - ) - } -} - fn is_24h_format(hours_data: u8) -> bool { hours_data & BitFlags::H24_H12 == 0 } diff --git a/src/lib.rs b/src/lib.rs index 5efa490..f98afda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -388,6 +388,11 @@ pub enum Error { /// It was not possible to read a valid date and/or time. /// The device is probably missing initialization. InvalidDeviceState, + /// Device century is not the 20th century. + /// + /// The device does not produce valid dates for centuries + /// other than the 20th century. + InvalidDeviceCentury, } /// Square-wave output frequency diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a1ff32c..df3c5a1 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -23,6 +23,7 @@ impl Register { pub const DOW: u8 = 0x03; pub const DOM: u8 = 0x04; pub const MONTH: u8 = 0x05; + pub const YEAR: u8 = 0x06; pub const ALARM1_SECONDS: u8 = 0x07; pub const ALARM2_MINUTES: u8 = 0x0B; pub const CONTROL: u8 = 0x0E; @@ -36,6 +37,7 @@ pub struct BitFlags; #[allow(unused)] impl BitFlags { + pub const CENTURY: u8 = 0b1000_0000; pub const EOSC: u8 = 0b1000_0000; pub const BBSQW: u8 = 0b0100_0000; pub const TEMP_CONV: u8 = 0b0010_0000; @@ -123,6 +125,16 @@ macro_rules! assert_invalid_input_data { }; } +#[macro_export] +macro_rules! assert_invalid_device_century { + ($result:expr) => { + match $result { + Err(Error::InvalidDeviceCentury) => (), + _ => panic!("InvalidDeviceCentury error not returned."), + } + }; +} + #[macro_export] macro_rules! set_invalid_test { ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $value:expr) => { @@ -135,6 +147,19 @@ macro_rules! set_invalid_test { }; } +#[macro_export] +macro_rules! get_invalid_test { + ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $transactions:expr) => { + #[test] + fn $name() { + let trans = $transactions; + let mut dev = $create_method(&trans); + assert_invalid_device_century!(dev.$method()); + $destroy_method(dev); + } + }; +} + #[macro_export] macro_rules! call_test { ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $transactions:expr) => { diff --git a/tests/datetime.rs b/tests/datetime.rs index a10acb8..e69e976 100644 --- a/tests/datetime.rs +++ b/tests/datetime.rs @@ -2,8 +2,8 @@ use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as S use rtcc::NaiveDateTime; mod common; use self::common::{ - destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, Register, - DEVICE_ADDRESS as DEV_ADDR, + destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, BitFlags, + Register, DEVICE_ADDRESS as DEV_ADDR, }; #[allow(unused)] // Rust 1.31.0 is confused due to the macros use ds323x::Rtcc; @@ -78,6 +78,31 @@ macro_rules! read_set_param_test { }; } +macro_rules! read_get_param_test { + ($name:ident, $method:ident, $register:ident, $value:expr, $binary_value:expr) => { + _get_param_test!( + $name, + $method, + $value, + [ + I2cTrans::write_read(DEV_ADDR, vec![Register::MONTH], vec![1]), + I2cTrans::write_read(DEV_ADDR, vec![Register::$register], vec![$binary_value]) + ], + [ + SpiTrans::transaction_start(), + SpiTrans::transfer_in_place(vec![Register::MONTH, 0], vec![Register::MONTH, 1]), + SpiTrans::transaction_end(), + SpiTrans::transaction_start(), + SpiTrans::transfer_in_place( + vec![Register::$register, 0], + vec![Register::$register, $binary_value] + ), + SpiTrans::transaction_end(), + ] + ); + }; +} + macro_rules! set_invalid_param_test { ($name:ident, $method:ident, $value:expr) => { mod $name { @@ -117,6 +142,50 @@ macro_rules! set_invalid_param_range_test { }; } +macro_rules! get_invalid_device_century_test { + ($name:ident, $method:ident) => { + mod $name { + use super::*; + get_invalid_test!( + cannot_get_invalid_ds3231, + $method, + new_ds3231, + destroy_ds3231, + [I2cTrans::write_read( + DEV_ADDR, + vec![Register::MONTH], + vec![BitFlags::CENTURY | 0] + )] + ); + get_invalid_test!( + cannot_get_invalid_ds3232, + $method, + new_ds3232, + destroy_ds3232, + [I2cTrans::write_read( + DEV_ADDR, + vec![Register::MONTH], + vec![BitFlags::CENTURY | 0] + )] + ); + get_invalid_test!( + cannot_get_invalid_ds3234, + $method, + new_ds3234, + destroy_ds3234, + [ + SpiTrans::transaction_start(), + SpiTrans::transfer_in_place( + vec![Register::MONTH, 0], + vec![Register::MONTH, BitFlags::CENTURY | 0] + ), + SpiTrans::transaction_end(), + ] + ); + } + }; +} + macro_rules! for_all { ($name:ident) => { mod $name { @@ -133,51 +202,58 @@ macro_rules! for_all { mod seconds { use super::*; - get_param_test!(get, seconds, SECONDS, 1, 1); + read_get_param_test!(get, seconds, SECONDS, 1, 1); set_param_test!(set, set_seconds, SECONDS, 1, 1); set_invalid_param_test!(invalid, set_seconds, 60); + get_invalid_device_century_test!(invalid_century, seconds); } mod minutes { use super::*; - get_param_test!(get, minutes, MINUTES, 1, 1); + read_get_param_test!(get, minutes, MINUTES, 1, 1); set_param_test!(set, set_minutes, MINUTES, 1, 1); set_invalid_param_test!(invalid, set_minutes, 60); + get_invalid_device_century_test!(invalid_century, minutes); } mod hours_24h { use super::*; - get_param_test!(get, hours, HOURS, Hours::H24(21), 0b0010_0001); + read_get_param_test!(get, hours, HOURS, Hours::H24(21), 0b0010_0001); set_param_test!(set, set_hours, HOURS, Hours::H24(21), 0b0010_0001); set_invalid_param_test!(invalid, set_hours, Hours::H24(24)); + get_invalid_device_century_test!(invalid_century, hours); } mod hours_12h_am { use super::*; - get_param_test!(get, hours, HOURS, Hours::AM(12), 0b0101_0010); + read_get_param_test!(get, hours, HOURS, Hours::AM(12), 0b0101_0010); set_param_test!(set, set_hours, HOURS, Hours::AM(12), 0b0101_0010); set_invalid_param_range_test!(invalid, set_hours, Hours::AM(0), Hours::AM(13)); + get_invalid_device_century_test!(invalid_century, hours); } mod hours_12h_pm { use super::*; - get_param_test!(get, hours, HOURS, Hours::PM(12), 0b0111_0010); + read_get_param_test!(get, hours, HOURS, Hours::PM(12), 0b0111_0010); set_param_test!(set, set_hours, HOURS, Hours::PM(12), 0b0111_0010); set_invalid_param_range_test!(invalid, set_hours, Hours::PM(0), Hours::PM(13)); + get_invalid_device_century_test!(invalid_century, hours); } mod weekday { use super::*; - get_param_test!(get, weekday, DOW, 1, 1); + read_get_param_test!(get, weekday, DOW, 1, 1); set_param_test!(set, set_weekday, DOW, 1, 1); set_invalid_param_range_test!(invalid, set_weekday, 0, 8); + get_invalid_device_century_test!(invalid_century, weekday); } mod day { use super::*; - get_param_test!(get, day, DOM, 1, 1); + read_get_param_test!(get, day, DOM, 1, 1); set_param_test!(set, set_day, DOM, 1, 1); set_invalid_param_range_test!(invalid, set_day, 0, 32); + get_invalid_device_century_test!(invalid_century, day); } mod month { @@ -185,19 +261,14 @@ mod month { get_param_test!(get, month, MONTH, 1, 1); read_set_param_test!(set, set_month, MONTH, 12, 0b0000_0010, 0b0001_0010); set_invalid_param_range_test!(invalid, set_month, 0, 13); - - mod keeps_century { - use super::*; - get_param_test!(get, month, MONTH, 12, 0b1001_0010); - read_set_param_test!(set, set_month, MONTH, 12, 0b1000_0010, 0b1001_0010); - } + get_invalid_device_century_test!(invalid_century, month); } mod year { use super::*; - get_param_read_array_test!(century0_get, year, 2099, MONTH, [0, 0b1001_1001], [0, 0]); + read_get_param_test!(get, year, YEAR, 2099, 0b1001_1001); read_set_param_write_two_test!( - century0_set, + set, set_year, 2099, MONTH, @@ -206,18 +277,8 @@ mod year { 0b1001_1001 ); - get_param_read_array_test!(century1_get, year, 2100, MONTH, [0b1000_0000, 0], [0, 0]); - read_set_param_write_two_test!( - century1_set, - set_year, - 2100, - MONTH, - 0b0001_0010, - 0b1001_0010, - 0 - ); - - set_invalid_param_range_test!(invalid, set_year, 1999, 2101); + set_invalid_param_range_test!(invalid, set_year, 1999, 2100); + get_invalid_device_century_test!(invalid_century, year); } macro_rules! invalid_dt_test { @@ -233,7 +294,7 @@ macro_rules! invalid_dt_test { } #[test] fn datetime_too_big() { - let dt = new_datetime(2101, 1, 2, 3, 4, 5); + let dt = new_datetime(2100, 1, 2, 3, 4, 5); let mut dev = $create_method(&[]); assert_invalid_input_data!(dev.set_datetime(&dt)); $destroy_method(dev); @@ -247,7 +308,7 @@ macro_rules! invalid_dt_test { } #[test] fn date_too_big() { - let d = new_date(2101, 1, 2); + let d = new_date(2100, 1, 2); let mut dev = $create_method(&[]); assert_invalid_input_data!(dev.set_date(&d)); $destroy_method(dev); @@ -297,6 +358,26 @@ macro_rules! dt_test { $destroy_method(dev); } + #[test] + fn get_datetime_invalid_century() { + let _dt = new_datetime(2018, 8, 13, 23, 59, 58); + let mut dev = $create_method(&$mac_trans_read!( + SECONDS, + [ + 0b0101_1000, + 0b0101_1001, + 0b0010_0011, + 0b0000_0010, + 0b0001_0011, + 0b0000_1000 | BitFlags::CENTURY, + 0b0001_1000 + ], + [0, 0, 0, 0, 0, 0, 0] + )); + assert_invalid_device_century!(dev.datetime()); + $destroy_method(dev); + } + #[test] fn set_datetime() { let dt = new_datetime(2018, 8, 13, 23, 59, 58); @@ -328,6 +409,18 @@ macro_rules! dt_test { $destroy_method(dev); } + #[test] + fn get_date_invalid_century() { + let _d = new_date(2018, 8, 13); + let mut dev = $create_method(&$mac_trans_read!( + DOM, + [0b0001_0011, 0b0000_1000 | BitFlags::CENTURY, 0b0001_1000], + [0, 0, 0] + )); + assert_invalid_device_century!(dev.date()); + $destroy_method(dev); + } + #[test] fn set_date() { let d = new_date(2018, 8, 13); @@ -340,25 +433,32 @@ macro_rules! dt_test { } #[test] - fn set_date_century() { - let d = new_date(2100, 8, 13); - let mut dev = $create_method(&$mac_trans_write!( - DOW, - [0b0000_0110, 0b0001_0011, 0b1000_1000, 0] - )); - dev.set_date(&d).unwrap(); + fn get_time() { + let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); + let month_transaction = $mac_trans_read!(MONTH, [1], [0]); + let time_transaction = + $mac_trans_read!(SECONDS, [0b0101_1000, 0b0101_1001, 0b0010_0011], [0, 0, 0]); + let mut transactions = vec![]; + let mut i: usize = 0; + while i < month_transaction.len() { + transactions.push(month_transaction[i].clone()); + i += 1; + } + let mut j: usize = 0; + while j < time_transaction.len() { + transactions.push(time_transaction[j].clone()); + j += 1; + } + let mut dev = $create_method(&transactions); + assert_eq!(t, dev.time().unwrap()); $destroy_method(dev); } #[test] - fn get_time() { - let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); - let mut dev = $create_method(&$mac_trans_read!( - SECONDS, - [0b0101_1000, 0b0101_1001, 0b0010_0011], - [0, 0, 0] - )); - assert_eq!(t, dev.time().unwrap()); + fn get_time_invalid_century() { + let transactions = $mac_trans_read!(MONTH, [0 | BitFlags::CENTURY], [0]); + let mut dev = $create_method(&transactions); + assert_invalid_device_century!(dev.time()); $destroy_method(dev); }