diff --git a/RFC-nits.md b/RFC-nits.md new file mode 100644 index 0000000..06c08a5 --- /dev/null +++ b/RFC-nits.md @@ -0,0 +1,103 @@ +# Various issues with RFCs + +## RFC 3597 - "Handling of Unknown DNS Resource Record (RR) Types" + +[Section 5](https://tools.ietf.org/html/rfc3597#section-5) defines a +generic (master file) format for unknown types, which can also be used +for (partially) known types to be compatible with other software. + +It also says: + +> An implementation MAY also choose to represent some RRs of known type +> using the above generic representations for the type, class and/or +> RDATA, which carries the benefit of making the resulting master file +> portable to servers where these types are unknown. + +and provides examples: + +> e.example. IN A \# 4 0A000001 +> e.example. CLASS1 TYPE1 10.0.0.2 + +I.e. it allows mixing unknown and known format. + +Given that the `\# ` format can also be a valid known +representation it is a bad idea to mix them (the obvious example being +`TXT`). + +It is acceptable to use the generic `CLASS...` specifier with known and +unknown TYPEs, but the generic representation of RDATA must only be used +with the generic `TYPE...` representation; and the generic `TYPE...` +representation must always be followed by the generic representation of +RDATA. + +## RFC 4034 + +[Section 3.2](https://tools.ietf.org/html/rfc4034#section-3.2) says: + +> The Signature Expiration Time and Inception Time field values MUST be +> represented either as an unsigned decimal integer indicating seconds +> since 1 January 1970 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in +> UTC, where: +> +> - YYYY is the year (0001-9999, but see Section 3.1.5); +> - MM is the month number (01-12); +> - DD is the day of the month (01-31); +> - HH is the hour, in 24 hour notation (00-23); +> - mm is the minute (00-59); and +> - SS is the second (00-59). + +But in UTC the second sometimes might be `60` to handle leap seconds; +these value can't be represented in POSIX time (seconds since epoch +*without* leap seconds), so the `YYYYMMDDHHmmSS` simply isn't exact UTC. + +## RFC 6895 + +[Sectoin 3.1](https://tools.ietf.org/html/rfc6895#section-3.1) says: + +> Allocated RRTYPEs have mnemonics that must be completely disjoint +> from the mnemonics used for CLASSes and that must match the regular +> expression below. In addition, the generic CLASS and RRTYPE names +> specified in Section 5 of [RFC3597] cannot be assigned as new RRTYPE +> mnemonics. +> +> [A-Z][A-Z0-9\-]*[A-Z0-9] +> but not +> (TYPE|CLASS)[0-9]* + +TYPE 255 doesn't have a clear mnemonic though; usually `ANY` is used, +but the official name is `*`. + +Now: + +- `*` doesn't match the required format +- `ANY` conflicts with CLASS `ANY` (255) +- but a mnemonic is required because the TYPE is allocated + +RFC 6895 indicates that `*` means `(ALL/ANY)`, so one might read it +suggests using `ALL` as mnemonic for TYPE 255. + +Side note: RFC 1035 doesn't specify a mnemonic for (Q)CLASS 255 either, +but the registry put `ANY` between `(`..`)`, the same as it did for the +other mnemonic from RFC 1035. + +The QCLASS 254 looks different again, but `NONE` is probably a safe +mnemonic for it. + +See also: + +- Re: [dnsext] WGLC: RFC6195bis IANA guidance (https://mailarchive.ietf.org/arch/msg/dnsext/kKBfBhQIJmRDQ-xb_iJD-A4EeZE) + +### Proposal + +Given the common use of `ANY` for TYPE 255, it should be marked as the +official mnemonic. + +Since QCLASS `ANY` is basically useless it could be obsoleted. + +BCP 42 (currently RFC 6895) should be updated to say `ANY` is a valid +mnemonic for both TYPE 255 and CLASS 255, and, if ambiguous, should be +interpreted as TYPE 255. + +See also: + +- [dnsext] rfc6195bis draft : thoughts on CLASS sub-registry (https://mailarchive.ietf.org/arch/msg/dnsext/fA086yr5V3QrVkmxF7HcuBIX92A) diff --git a/lib/dnsbox-base/src/common_types/time.rs b/lib/dnsbox-base/src/common_types/time.rs deleted file mode 100644 index 92fa779..0000000 --- a/lib/dnsbox-base/src/common_types/time.rs +++ /dev/null @@ -1,142 +0,0 @@ -use bytes::Bytes; -use errors::*; -use ser::packet::{DnsPacketData, DnsPacketWriteContext}; -use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field}; -use std::fmt; -use std::io::Cursor; - -/// timestamp in seconds since epoch (ignoring leap seconds) -/// -/// Is expected to wrap around. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Time(pub u32); - -impl DnsPacketData for Time { - fn deserialize(data: &mut Cursor) -> Result { - Ok(Time(DnsPacketData::deserialize(data)?)) - } - - fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { - self.0.serialize(context, packet) - } -} - - -struct Tm { - year: u16, // 1970...9999 - month: u8, // 01...12 - day: u8, // 01..31 - hour: u8, // 00..23 - minute: u8, // 00..59 - second: u8, // 00..59 -} - -impl Tm { - // 0..365 - fn dayofyear(&self) -> u16 { - let is_leap_year = (0 == self.year % 4) && ((0 != self.year % 100) || (0 == self.year % 400)); - static MONTH_START: [u16; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - let leap_add = if self.month > 2 && is_leap_year { 1 } else { 0 }; - MONTH_START[self.month as usize - 1] + leap_add + (self.day as u16 - 1) - } - - fn epoch(&self) -> u64 { - let yday = self.dayofyear(); - self.second as u64 - + 60 * self.minute as u64 - + 3600 * self.hour as u64 - + 86400 * yday as u64 - + 31536000 * (self.year as u64 - 1970) - + 86400 * ((self.year as u64 - 1969) / 4) - - 86400 * ((self.year as u64 - 1901) / 100) - + 86400 * ((self.year as u64 - 1900 + 299) / 400) - } -} - -impl ::std::str::FromStr for Tm { - type Err = ::failure::Error; - fn from_str(s: &str) -> ::errors::Result { - ensure!(s.len() == 14, "Tm string must be exactly 14 digits long"); - ensure!(s.as_bytes().iter().all(|&b| b >= b'0' && b <= b'9'), "Tm string must be exactly 14 digits long"); - let year = s[0..4].parse::()?; - ensure!(year >= 1970, "year must be >= 1970"); - ensure!(year <= 9999, "year must be <= 9999"); - - fn p(s: &str, min: u8, max: u8, name: &'static str) -> ::errors::Result { - let v = s.parse::()?; - ensure!(v >= min && v <= max, "{} {} out of range {}-{}", name, v, min, max); - Ok(v) - } - - let month = p(&s[4..6], 1, 12, "month")?; - let day = p(&s[6..8], 1, 31, "day")?; - let hour = p(&s[8..10], 0, 23, "hour")?; - let minute = p(&s[10..12], 0, 59, "minute")?; - let second = p(&s[12..14], 0, 59, "second")?; - - if 2 == month { - let is_leap_year = (0 == year % 4) && ((0 != year % 100) || (0 == year % 400)); - ensure!(day < 30, "day {} out of range in february", day); - ensure!(is_leap_year || day < 29, "day {} out of range in february (not a leap year)", day); - } else { - static DAYS_IN_MONTHS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - let max_days = DAYS_IN_MONTHS[month as usize - 1]; - ensure!(day <= max_days, "day {} out of range for month {}", day, month); - } - - Ok(Tm{ year, month, day, hour, minute, second }) - } -} - -impl DnsTextData for Time { - fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { - let field = next_field(data)?; - let epoch = field.parse::(); - if field.len() == 14 && epoch.is_err() { - let tm = field.parse::()?; - Ok(Time(tm.epoch() as u32)) - } else { - Ok(Time(epoch?)) - } - } - - fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -pub const TIME48_MAX: u64 = 0xffff_ffff_ffff; - -/// 48-bit timestamp in seconds since epoch (ignoring leap seconds) -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Time48(pub u64); - -impl DnsPacketData for Time48 { - fn deserialize(data: &mut Cursor) -> Result { - let high16 = u16::deserialize(data)? as u64; - let low32 = u32::deserialize(data)? as u64; - Ok(Time48(high16 | low32)) - } - - fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { - ensure!(self.0 <= TIME48_MAX, "time48 overflow"); - let high16 = (self.0 >> 32) as u16; - let low32 = self.0 as u32; - high16.serialize(context, packet)?; - low32.serialize(context, packet)?; - Ok(()) - } -} - -impl DnsTextData for Time48 { - fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { - let field = next_field(data)?; - let epoch = field.parse::()?; - ensure!(epoch <= TIME48_MAX, "time48 overflow"); - Ok(Time48(epoch)) - } - - fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/lib/dnsbox-base/src/common_types/time/epoch.rs b/lib/dnsbox-base/src/common_types/time/epoch.rs new file mode 100644 index 0000000..6ea456d --- /dev/null +++ b/lib/dnsbox-base/src/common_types/time/epoch.rs @@ -0,0 +1,304 @@ +//! epoch is seconds since 1 January, 1970, without leap seconds. +//! +//! year 0 is "1 BC", year -1 is "2 BC" and so on + +use errors::*; +use std::fmt; + +fn is_leap_year(year: i16) -> bool { + 0 == year % 4 && (0 != year % 100 || 0 == year % 400) +} + +#[test] +fn test_is_leap_year() { + assert!(!is_leap_year(-500)); + assert!(is_leap_year(-404)); + assert!(is_leap_year(-400)); + assert!(is_leap_year(-396)); + assert!(!is_leap_year(-300)); + assert!(!is_leap_year(-200)); + assert!(!is_leap_year(-100)); + assert!(is_leap_year(-4)); + assert!(!is_leap_year(-3)); + assert!(!is_leap_year(-1)); + assert!(is_leap_year(0)); + assert!(!is_leap_year(1)); + assert!(!is_leap_year(3)); + assert!(is_leap_year(4)); + assert!(!is_leap_year(100)); + assert!(!is_leap_year(200)); + assert!(!is_leap_year(300)); + assert!(is_leap_year(396)); + assert!(is_leap_year(400)); + assert!(is_leap_year(404)); + assert!(!is_leap_year(500)); +} + +fn month_day_of_year_since_march(month: u8) -> u16 { + debug_assert!(month >= 1 && month <= 12); + let month_from_march = if month > 2 { month as u16 - 3} else { month as u16 + 9 }; + (153 * month_from_march + 2) / 5 +} + +fn month_and_day_from_day_of_year_since_march(day_of_year: i32) -> (u8, u8) { + let month_from_march = (5 * day_of_year + 2) / 153; + debug_assert!(month_from_march >= 0 && month_from_march <= 11); + + let day = day_of_year - (153 * month_from_march + 2) / 5 + 1; + debug_assert!(day >= 1 && day <= 31); + + let month_from_march = month_from_march as u8; + + let month = if month_from_march < 10 { month_from_march + 3 } else { month_from_march - 9 }; + debug_assert!(month >= 1 && month <= 12); + + (month, day as u8) +} + +#[test] +fn test_month_day_of_year_since_march() { + static MONTH_START: [u16; 12] = [306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, ]; + static DAYS_IN_MONTHS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + for (m, &s) in MONTH_START.iter().enumerate() { + assert_eq!(month_day_of_year_since_march(m as u8 + 1), s); + assert_eq!(month_and_day_from_day_of_year_since_march(s as i32), (m as u8 + 1, 1)); + assert_eq!(month_and_day_from_day_of_year_since_march(s as i32 + DAYS_IN_MONTHS[m] as i32 - 1), (m as u8 + 1, DAYS_IN_MONTHS[m])); + } + assert_eq!(month_and_day_from_day_of_year_since_march(365), (2, 29)); +} + +#[inline(always)] +fn pos_div_rem(n: i32, pos_div: i32) -> (i32, i32) { + // try to avoid overflows + debug_assert!(pos_div > 0); + let res = n / pos_div; + let rem = n % pos_div; + debug_assert_eq!(res * pos_div + rem, n); + if rem < 0 { + let rem = rem + pos_div; + let res = res - 1; + debug_assert_eq!(res * pos_div + rem, n); + debug_assert!(rem >= 0 && rem < pos_div); + (res, rem) + } else { + debug_assert!(rem >= 0 && rem < pos_div); + (res, rem) + } +} + +#[inline(always)] +fn pos_div_rem64(n: i64, pos_div: i64) -> (i64, i64) { + // try to avoid overflows + debug_assert!(pos_div > 0); + let res = n / pos_div; + let rem = n % pos_div; + debug_assert_eq!(res * pos_div + rem, n); + if rem < 0 { + let rem = rem + pos_div; + let res = res - 1; + debug_assert_eq!(res * pos_div + rem, n); + debug_assert!(rem >= 0 && rem < pos_div); + (res, rem) + } else { + debug_assert!(rem >= 0 && rem < pos_div); + (res, rem) + } +} + +// year 0 is era 0, year-of-era 0 +// -> (ear, year_of_era) +fn era_split_year(year: i32) -> (i32, i32) { + pos_div_rem(year, 400) +} + +#[test] +fn test_era_split_year() { + // -800...-401, -400...-1, 0...399, 400...799 + assert_eq!(era_split_year(-801), (-3, 399)); + assert_eq!(era_split_year(-800), (-2, 0)); + assert_eq!(era_split_year(-799), (-2, 1)); + assert_eq!(era_split_year(-401), (-2, 399)); + assert_eq!(era_split_year(-400), (-1, 0)); + assert_eq!(era_split_year(-399), (-1, 1)); + assert_eq!(era_split_year(-1), (-1, 399)); + assert_eq!(era_split_year(0), (0, 0)); + assert_eq!(era_split_year(1), (0, 1)); + assert_eq!(era_split_year(399), (0, 399)); + assert_eq!(era_split_year(400), (1, 0)); + assert_eq!(era_split_year(401), (1, 1)); + assert_eq!(era_split_year(799), (1, 399)); + assert_eq!(era_split_year(1970), (4, 370)); +} + +// -> (era, day_of_era) +fn split_days_since_march1_y0_into_era(days: i32) -> (i32, i32) { + pos_div_rem(days, ERA_DAYS) +} + +fn year_of_era_from_day_of_era(day_of_era: i32) -> i32 { + let res = (day_of_era - day_of_era/1460 + day_of_era/36524 - day_of_era/146096) / 365; + debug_assert!(res >= 0 && res <= 399); + res +} + +#[test] +fn test_year_of_era_from_day_of_era() { + for year_of_era in 0..399 { + let ds = first_day_of_year_in_era_from_year_of_era(year_of_era); + assert_eq!(year_of_era_from_day_of_era(ds + 0), year_of_era); + assert_eq!(year_of_era_from_day_of_era(ds + 364), year_of_era); + // 29. february counts to previous year; so if next ("real") year is leap year, + // the current year_of_era has a leap day. + if is_leap_year(year_of_era as i16 + 1) { + assert_eq!(year_of_era_from_day_of_era(ds + 365), year_of_era); + } + } +} + +const EPOCH_DAYS_SINCE_MARCH1_Y0: i32 = 719468; + +// there are 100 - 4 + 1 leap years in 400 years. +const ERA_DAYS: i32 = 365 * 400 + 100 - 4 + 1; // 146097 + +fn first_day_of_year_in_era_from_year_of_era(year_of_era: i32) -> i32 { + 365 * year_of_era + year_of_era / 4 - year_of_era / 100 +} + +// days since march 1st in 1 BC (year "0") +fn days_since_march1_y0(year: i16, month: u8, day: u8) -> i32 { + // group 400 years as one era, but split between february and + // march; count jan/feb to previous year, make march 1st start + // of year + let year = year as i32 - if month <= 2 { 1 } else { 0 }; + + let (era, year_of_era) = era_split_year(year); + + let day_of_year = month_day_of_year_since_march(month) + day as u16 - 1; + let day_of_era = first_day_of_year_in_era_from_year_of_era(year_of_era) + day_of_year as i32; + + era as i32 * ERA_DAYS + day_of_era +} + +fn split_days_since_march1_y0(days: i32) -> (i32, u8, u8) { + let (era, day_of_era) = split_days_since_march1_y0_into_era(days); + let year_of_era = year_of_era_from_day_of_era(day_of_era); + let day_of_year = day_of_era - first_day_of_year_in_era_from_year_of_era(year_of_era); + + let (month, day) = month_and_day_from_day_of_year_since_march(day_of_year); + + let year = 400 * era + year_of_era + if month <= 2 { 1 } else { 0 }; + + (year as i32, month, day) +} + +#[test] +fn test_days_since_march1_y0() { + assert_eq!(days_since_march1_y0(-1, 3, 1), -366); + assert_eq!(days_since_march1_y0(0, 3, 1), 0); + assert_eq!(days_since_march1_y0(1, 3, 1), 365); + assert_eq!(days_since_march1_y0(1970, 1, 1), EPOCH_DAYS_SINCE_MARCH1_Y0); + assert_eq!(days_since_march1_y0(1970, 1, 1), + /* regular days in years: */ 365 * 1969 + /* days from leap years: */ + (1969 / 4 - 15) + /* days from march 1st year 0 to january 1st year 1: */ + 306); + + assert_eq!(split_days_since_march1_y0(-366), (-1, 3, 1)); + assert_eq!(split_days_since_march1_y0(0), (0, 3, 1)); + assert_eq!(split_days_since_march1_y0(365), (1, 3, 1)); + assert_eq!(split_days_since_march1_y0(EPOCH_DAYS_SINCE_MARCH1_Y0), (1970, 1, 1)); +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Tm { + pub year: i16, // absolute year: 1 is "1 AD", 0 is "1 BC". + pub month: u8, // 01...12 + pub day: u8, // 01..31 + pub hour: u8, // 00..23 + pub minute: u8, // 00..59 + pub second: u8, // 00..59 +} + +#[allow(unused)] +impl Tm { + pub fn from_epoch(epoch: i64) -> Result { + let (day, time_of_day) = pos_div_rem64(epoch, 86400); + let days_since_march1_y0 = day + EPOCH_DAYS_SINCE_MARCH1_Y0 as i64; + ensure!((days_since_march1_y0 as i32) as i64 == days_since_march1_y0, "days in epoch out of range"); + let days_since_march1_y0 = days_since_march1_y0 as i32; + + let (year, month, day) = split_days_since_march1_y0(days_since_march1_y0); + ensure!((year as i16) as i32 == year, "year in epoch out of range"); + let year = year as i16; + + let (minute_of_day, second) = pos_div_rem(time_of_day as i32, 60); + let (hour, minute) = pos_div_rem(minute_of_day, 60); + + Ok(Tm{ + year, + month, + day, + hour: hour as u8, + minute: minute as u8, + second: second as u8, + }) + } + + fn days_since_epoch(&self) -> i32 { + let days = days_since_march1_y0(self.year as i16, self.month, self.day); + days - EPOCH_DAYS_SINCE_MARCH1_Y0 + } + + fn day_seconds(&self) -> u32 { + self.second as u32 + + 60 * self.minute as u32 + + 3600 * self.hour as u32 + } + + pub fn epoch(&self) -> i64 { + 86400 * self.days_since_epoch() as i64 + self.day_seconds() as i64 + } + + #[allow(non_snake_case)] + pub fn parse_YYYYMMDDHHmmSS(s: &str) -> Result { + ensure!(s.len() == 14, "Tm string must be exactly 14 digits long"); + ensure!(s.as_bytes().iter().all(|&b| b >= b'0' && b <= b'9'), "Tm string must be exactly 14 digits long"); + let year = s[0..4].parse::()?; + ensure!(year >= 1, "year must be >= 1"); + ensure!(year <= 9999, "year must be <= 9999"); + + fn p(s: &str, min: u8, max: u8, name: &'static str) -> ::errors::Result { + let v = s.parse::()?; + ensure!(v >= min && v <= max, "{} {} out of range {}-{}", name, v, min, max); + Ok(v) + } + + let month = p(&s[4..6], 1, 12, "month")?; + let day = p(&s[6..8], 1, 31, "day")?; + let hour = p(&s[8..10], 0, 23, "hour")?; + let minute = p(&s[10..12], 0, 59, "minute")?; + let second = p(&s[12..14], 0, 59, "second")?; + + if 2 == month { + ensure!(day < 30, "day {} out of range in february", day); + ensure!(is_leap_year(year) || day < 29, "day {} out of range in february (not a leap year)", day); + } else { + static DAYS_IN_MONTHS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let max_days = DAYS_IN_MONTHS[month as usize - 1]; + ensure!(day <= max_days, "day {} out of range for month {}", day, month); + } + + Ok(Tm{ year, month, day, hour, minute, second }) + } + + #[allow(non_snake_case)] + pub fn format_YYYYMMDDHHmmSS(&self, f: &mut W) -> fmt::Result + where + W: fmt::Write + ?Sized + { + if self.year < 0 || self.year > 9999 { return Err(fmt::Error); } + write!(f, "{:04}{:02}{:02}{:02}{:02}{:02}", + self.year, self.month, self.day, + self.hour, self.minute, self.second + ) + } +} diff --git a/lib/dnsbox-base/src/common_types/time/mod.rs b/lib/dnsbox-base/src/common_types/time/mod.rs new file mode 100644 index 0000000..527483f --- /dev/null +++ b/lib/dnsbox-base/src/common_types/time/mod.rs @@ -0,0 +1,78 @@ +use bytes::Bytes; +use errors::*; +use ser::packet::{DnsPacketData, DnsPacketWriteContext}; +use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field}; +use std::fmt; +use std::io::Cursor; + +mod epoch; + +/// timestamp in seconds since epoch (ignoring leap seconds) +/// +/// Is expected to wrap around. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Time(pub u32); + +impl DnsPacketData for Time { + fn deserialize(data: &mut Cursor) -> Result { + Ok(Time(DnsPacketData::deserialize(data)?)) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + self.0.serialize(context, packet) + } +} + +impl DnsTextData for Time { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { + let field = next_field(data)?; + let epoch = field.parse::(); + if field.len() == 14 && epoch.is_err() { + let tm = epoch::Tm::parse_YYYYMMDDHHmmSS(field)?; + Ok(Time(tm.epoch() as u32)) + } else { + Ok(Time(epoch?)) + } + } + + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + epoch::Tm::from_epoch(self.0 as i64).unwrap().format_YYYYMMDDHHmmSS(&mut*f.format_field()?) + // write!(f, "{}", self.0) + } +} + +pub const TIME48_MAX: u64 = 0xffff_ffff_ffff; + +/// 48-bit timestamp in seconds since epoch (ignoring leap seconds) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Time48(pub u64); + +impl DnsPacketData for Time48 { + fn deserialize(data: &mut Cursor) -> Result { + let high16 = u16::deserialize(data)? as u64; + let low32 = u32::deserialize(data)? as u64; + Ok(Time48(high16 | low32)) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + ensure!(self.0 <= TIME48_MAX, "time48 overflow"); + let high16 = (self.0 >> 32) as u16; + let low32 = self.0 as u32; + high16.serialize(context, packet)?; + low32.serialize(context, packet)?; + Ok(()) + } +} + +impl DnsTextData for Time48 { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { + let field = next_field(data)?; + let epoch = field.parse::()?; + ensure!(epoch <= TIME48_MAX, "time48 overflow"); + Ok(Time48(epoch)) + } + + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/lib/dnsbox-base/src/records/powerdns_tests.rs b/lib/dnsbox-base/src/records/powerdns_tests.rs index 5ffaff1..1654a89 100644 --- a/lib/dnsbox-base/src/records/powerdns_tests.rs +++ b/lib/dnsbox-base/src/records/powerdns_tests.rs @@ -515,8 +515,9 @@ fn test_IPSECKEY() { fn test_RRSIG() { check(types::RRSIG, "SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", - // None, // we use the epoch as canoninc format - Some("SOA 8 3 300 1369267200 1368057600 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y="), + None, + // if epoch is canoninc format: + // Some("SOA 8 3 300 1369267200 1368057600 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y="), b"\x00\x06\x08\x03\x00\x00\x01\x2c\x51\x9d\x5c\x00\x51\x8a\xe7\x00\xd3\xc8\x03\x72\x65\x63\x04\x74\x65\x73\x74\x00\x79\xc5\x8a\x0f\xf3\xac\x74\x08\x97\xa5\xb3\x3f\xb2\x03\xd3\xf3\x62\x95\x0f\xf5\xa2\x42\x79\xea\x73\x1a\x09\x82\x21\xf7\x8b\x11\xda\xf8\xb3\x80\x71\x85\x3b\x14\x6e\xd5\xe0\x14\x51\x27\x12\xeb\x89\x8d\x5e\xd2\xb0\x76\x80\x0b\x37\x90\x27\xa5\xf4\x3e\x1b\xcd\xf2\xba\xc0\x0a\x86\xe0\x38\xbf\x78\x75\x20\x1e\x9b\x51\x95\xa4\x76\xc9\xc1\x0b\x27\x8b\x75\xa6\x3c\xeb\x9e\xe1\xa2\x23\x30\x35\xc0\xcd\xa2\xcd\x19\x9e\x13\xe5\xe8\xcb\x5d\xfa\xed\x0b\xb4\xa4\xec\xf9\x0c\xbc\xd3\x7d\x02\x13\x58\x5b\x01\xda\x25\x33\x37\x86", ); } diff --git a/lib/dnsbox-base/src/records/structs.rs b/lib/dnsbox-base/src/records/structs.rs index 1be81d4..819c840 100644 --- a/lib/dnsbox-base/src/records/structs.rs +++ b/lib/dnsbox-base/src/records/structs.rs @@ -183,8 +183,8 @@ pub struct SIG { // the TTL on the SIG record. not supported to be omitted here // (TODO?). original_ttl: u32, - signature_expiration: Time, - signature_inception: Time, + signature_expiration: u32, + signature_inception: u32, key_tag: u16, signers_name: DnsCanonicalName, signature: Base64RemainingBlob, @@ -483,8 +483,8 @@ pub struct EUI64 { #[RRClass(ANY)] pub struct TKEY { algorithm: DnsName, - inception: Time, - expiration: Time, + inception: u32, + expiration: u32, mode: u16, error: u16, key: Base64LongBlob, diff --git a/lib/dnsbox-base/src/ser/text/mod.rs b/lib/dnsbox-base/src/ser/text/mod.rs index 153dd79..d61ed83 100644 --- a/lib/dnsbox-base/src/ser/text/mod.rs +++ b/lib/dnsbox-base/src/ser/text/mod.rs @@ -87,6 +87,11 @@ impl<'a> DnsTextFormatter<'a> { self.w } + pub fn format_field<'b>(&'b mut self) -> Result, fmt::Error> { + self.next_field()?; + Ok(DnsTextFormatField{ inner: self }) + } + pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { self.next_field()?; self.w.write_fmt(args)?; @@ -95,6 +100,30 @@ impl<'a> DnsTextFormatter<'a> { } } +pub struct DnsTextFormatField<'a: 'b, 'b> { + inner: &'b mut DnsTextFormatter<'a>, +} + +impl<'a, 'b> ::std::ops::Deref for DnsTextFormatField<'a, 'b> { + type Target = fmt::Write + 'a; + + fn deref(&self) -> &Self::Target { + self.inner.w + } +} + +impl<'a, 'b> ::std::ops::DerefMut for DnsTextFormatField<'a, 'b> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.w + } +} + +impl<'a, 'b> Drop for DnsTextFormatField<'a, 'b> { + fn drop(&mut self) { + self.inner.end_field(); + } +} + #[derive(Clone, Debug, Default)] pub struct DnsTextContext { zone_class: Option,