step
This commit is contained in:
parent
d362520632
commit
92a76cd57a
103
RFC-nits.md
Normal file
103
RFC-nits.md
Normal file
@ -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 `\# <len> <hex>` 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)
|
@ -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<Bytes>) -> Result<Self> {
|
|
||||||
Ok(Time(DnsPacketData::deserialize(data)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> 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<Self> {
|
|
||||||
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::<u16>()?;
|
|
||||||
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<u8> {
|
|
||||||
let v = s.parse::<u8>()?;
|
|
||||||
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<Self> {
|
|
||||||
let field = next_field(data)?;
|
|
||||||
let epoch = field.parse::<u32>();
|
|
||||||
if field.len() == 14 && epoch.is_err() {
|
|
||||||
let tm = field.parse::<Tm>()?;
|
|
||||||
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<Bytes>) -> Result<Self> {
|
|
||||||
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<u8>) -> 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<Self> {
|
|
||||||
let field = next_field(data)?;
|
|
||||||
let epoch = field.parse::<u64>()?;
|
|
||||||
ensure!(epoch <= TIME48_MAX, "time48 overflow");
|
|
||||||
Ok(Time48(epoch))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
304
lib/dnsbox-base/src/common_types/time/epoch.rs
Normal file
304
lib/dnsbox-base/src/common_types/time/epoch.rs
Normal file
@ -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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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::<i16>()?;
|
||||||
|
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<u8> {
|
||||||
|
let v = s.parse::<u8>()?;
|
||||||
|
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<W>(&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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
78
lib/dnsbox-base/src/common_types/time/mod.rs
Normal file
78
lib/dnsbox-base/src/common_types/time/mod.rs
Normal file
@ -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<Bytes>) -> Result<Self> {
|
||||||
|
Ok(Time(DnsPacketData::deserialize(data)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> Result<()> {
|
||||||
|
self.0.serialize(context, packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DnsTextData for Time {
|
||||||
|
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
|
||||||
|
let field = next_field(data)?;
|
||||||
|
let epoch = field.parse::<u32>();
|
||||||
|
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<Bytes>) -> Result<Self> {
|
||||||
|
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<u8>) -> 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<Self> {
|
||||||
|
let field = next_field(data)?;
|
||||||
|
let epoch = field.parse::<u64>()?;
|
||||||
|
ensure!(epoch <= TIME48_MAX, "time48 overflow");
|
||||||
|
Ok(Time48(epoch))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
@ -515,8 +515,9 @@ fn test_IPSECKEY() {
|
|||||||
fn test_RRSIG() {
|
fn test_RRSIG() {
|
||||||
check(types::RRSIG,
|
check(types::RRSIG,
|
||||||
"SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=",
|
"SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=",
|
||||||
// None, // we use the epoch as canoninc format
|
None,
|
||||||
Some("SOA 8 3 300 1369267200 1368057600 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y="),
|
// 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",
|
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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -183,8 +183,8 @@ pub struct SIG {
|
|||||||
// the TTL on the SIG record. not supported to be omitted here
|
// the TTL on the SIG record. not supported to be omitted here
|
||||||
// (TODO?).
|
// (TODO?).
|
||||||
original_ttl: u32,
|
original_ttl: u32,
|
||||||
signature_expiration: Time,
|
signature_expiration: u32,
|
||||||
signature_inception: Time,
|
signature_inception: u32,
|
||||||
key_tag: u16,
|
key_tag: u16,
|
||||||
signers_name: DnsCanonicalName,
|
signers_name: DnsCanonicalName,
|
||||||
signature: Base64RemainingBlob,
|
signature: Base64RemainingBlob,
|
||||||
@ -483,8 +483,8 @@ pub struct EUI64 {
|
|||||||
#[RRClass(ANY)]
|
#[RRClass(ANY)]
|
||||||
pub struct TKEY {
|
pub struct TKEY {
|
||||||
algorithm: DnsName,
|
algorithm: DnsName,
|
||||||
inception: Time,
|
inception: u32,
|
||||||
expiration: Time,
|
expiration: u32,
|
||||||
mode: u16,
|
mode: u16,
|
||||||
error: u16,
|
error: u16,
|
||||||
key: Base64LongBlob,
|
key: Base64LongBlob,
|
||||||
|
@ -87,6 +87,11 @@ impl<'a> DnsTextFormatter<'a> {
|
|||||||
self.w
|
self.w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format_field<'b>(&'b mut self) -> Result<DnsTextFormatField<'a, 'b>, fmt::Error> {
|
||||||
|
self.next_field()?;
|
||||||
|
Ok(DnsTextFormatField{ inner: self })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
|
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
|
||||||
self.next_field()?;
|
self.next_field()?;
|
||||||
self.w.write_fmt(args)?;
|
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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct DnsTextContext {
|
pub struct DnsTextContext {
|
||||||
zone_class: Option<common_types::Class>,
|
zone_class: Option<common_types::Class>,
|
||||||
|
Loading…
Reference in New Issue
Block a user