Add Id type for creating unique message identifiers; Change DateTime

to only deal with instants after the Unix epoch and simplify the math.
This commit is contained in:
Nathan Fisher 2023-06-26 00:16:05 -04:00
parent 64b5051341
commit af49f327ec
13 changed files with 232 additions and 48 deletions

View file

@ -9,6 +9,7 @@ libc = "0.2.146"
rustls-pemfile = "1.0" rustls-pemfile = "1.0"
sha2 = "0.10" sha2 = "0.10"
time = "0.3" time = "0.3"
tinyrand = "0.5"
x509-parser = "0.15" x509-parser = "0.15"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -1,2 +1,69 @@
use {
crate::prelude::ParseRequestError,
std::{fmt, io, string::FromUtf8Error, time::SystemTimeError},
};
#[derive(Debug)] #[derive(Debug)]
pub struct Error; pub enum Error {
Io(io::Error),
Request(ParseRequestError),
Storage(String),
Rustls(rustls::Error),
Time(SystemTimeError),
Utf8,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "{e}"),
Self::Storage(e) => write!(f, "{e}"),
Self::Request(e) => write!(f, "{e}"),
Self::Rustls(e) => write!(f, "{e}"),
Self::Time(e) => write!(f, "{e}"),
Self::Utf8 => write!(f, "Utf8 error"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Request(e) => Some(e),
Self::Rustls(e) => Some(e),
Self::Time(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl From<ParseRequestError> for Error {
fn from(value: ParseRequestError) -> Self {
Self::Request(value)
}
}
impl From<rustls::Error> for Error {
fn from(value: rustls::Error) -> Self {
Self::Rustls(value)
}
}
impl From<SystemTimeError> for Error {
fn from(value: SystemTimeError) -> Self {
Self::Time(value)
}
}
impl From<FromUtf8Error> for Error {
fn from(_value: FromUtf8Error) -> Self {
Self::Utf8
}
}

View file

@ -3,6 +3,19 @@ pub mod builder;
pub mod error; pub mod error;
pub mod verifier; pub mod verifier;
use {
crate::{
prelude::{ClientCertificateStore, MailStore},
request::Request,
response::Response,
},
std::{
io::Read,
sync::{Arc, Mutex},
time::{SystemTime, UNIX_EPOCH},
},
};
pub use self::{ pub use self::{
builder::Builder, builder::Builder,
error::Error, error::Error,
@ -14,4 +27,28 @@ pub struct Connection {
pub inner: rustls::ServerConnection, pub inner: rustls::ServerConnection,
} }
impl Connection {} impl Connection {
pub fn handle_request<C: ClientCertificateStore + FingerPrintStore, S: MailStore>(
&mut self,
verifier: Verifier<C>,
mailstore: Arc<Mutex<S>>,
) -> Result<Response, Error> {
let mut buf: Vec<u8> = Vec::with_capacity(2048);
self.inner.reader().read_to_end(&mut buf)?;
let raw = String::from_utf8(buf)?;
let request: Request = raw.parse()?;
let res = if let Ok(store) = mailstore.lock() {
if store.has_mailuser(&request.recipient.to_string()) {
let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
//store.add_message(&request.recipient.to_string(), "Inbox", request.message)
// .map_err(|e| Error::Storage(e.to_string()))?;
true
} else {
false
}
} else {
false
};
todo!()
}
}

View file

@ -1,5 +1,5 @@
use crate::mailuser::Mailuser; use crate::mailuser::Mailuser;
use rustls::server::ClientCertVerifier; use rustls::server::{ClientCertVerified, ClientCertVerifier};
use std::sync::Mutex; use std::sync::Mutex;
#[derive(Debug)] #[derive(Debug)]
@ -23,7 +23,7 @@ impl<S: FingerPrintStore> ClientCertVerifier for Verifier<S> {
end_entity: &rustls::Certificate, end_entity: &rustls::Certificate,
intermediates: &[rustls::Certificate], intermediates: &[rustls::Certificate],
now: std::time::SystemTime, now: std::time::SystemTime,
) -> Result<rustls::server::ClientCertVerified, rustls::Error> { ) -> Result<ClientCertVerified, rustls::Error> {
todo!() todo!()
} }
} }

View file

@ -1,4 +1,4 @@
use crate::prelude::{DateTime, Link, Mailbox, Recipients}; use crate::prelude::{DateTime, Id, Link, Mailbox, Recipients};
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
@ -30,6 +30,12 @@ pub enum GemtextNode {
Link(Link), Link(Link),
} }
impl From<Id> for GemtextNode {
fn from(value: Id) -> Self {
Self::Timestamp(value.into())
}
}
impl fmt::Display for GemtextNode { impl fmt::Display for GemtextNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {

View file

@ -264,9 +264,7 @@ impl MailStore for Filesystem {
let mut path = self.path.clone(); let mut path = self.path.clone();
path.push(&mb.host.to_string()); path.push(&mb.host.to_string());
path.push(&mb.username); path.push(&mb.username);
fs::DirBuilder::new() fs::DirBuilder::new().recursive(true).create(&path)?;
.recursive(true)
.create(&path)?;
let p = CString::new(path.to_str().ok_or(Error::Utf8)?.to_string())?; let p = CString::new(path.to_str().ok_or(Error::Utf8)?.to_string())?;
if let Some(pw) = pw::Passwd::getpw()? { if let Some(pw) = pw::Passwd::getpw()? {
let groups = pw.groups()?; let groups = pw.groups()?;

View file

@ -10,7 +10,7 @@ mod filesystem;
pub use filesystem::{Error as FilesystemError, Filesystem, MultiDomain}; pub use filesystem::{Error as FilesystemError, Filesystem, MultiDomain};
pub trait MailStore { pub trait MailStore {
type Error; type Error: std::error::Error;
/// Retreives a list of all valid mailusers on this server /// Retreives a list of all valid mailusers on this server
fn users(&self) -> Vec<Mailuser>; fn users(&self) -> Vec<Mailuser>;

62
src/message/id.rs Normal file
View file

@ -0,0 +1,62 @@
use {
std::{
error::Error,
fmt,
fs::File,
io::{self, Read},
time::{SystemTime, UNIX_EPOCH},
},
tinyrand::{RandRange, Seeded, StdRand},
};
const ALPHABET: [char; 67] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '@', '%', '&', '=', '+',
];
#[derive(Clone, Debug)]
pub struct Id {
pub time: u64,
pub id: [char; 8],
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.", self.time)?;
self.id.iter().try_for_each(|c| write!(f, "{c}"))
}
}
impl Id {
pub fn new() -> Result<Self, Box<dyn Error>> {
let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let mut rand = StdRand::seed(random_seed()?);
let mut id = ['0', '0', '0', '0', '0', '0', '0', '0'];
for item in &mut id {
if let Some(c) = ALPHABET.get(rand.next_range(0..68)) {
*item = *c;
}
}
Ok(Self { time, id })
}
}
fn u64_from_bytes(b: [u8; 8]) -> u64 {
u64::from(b[0])
| (u64::from(b[1]) << 8)
| (u64::from(b[2]) << 16)
| (u64::from(b[3]) << 24)
| (u64::from(b[4]) << 32)
| (u64::from(b[5]) << 40)
| (u64::from(b[6]) << 48)
| (u64::from(b[7]) << 56)
}
fn random_seed() -> io::Result<u64> {
let mut buf = [0, 0, 0, 0, 0, 0, 0, 0];
let mut fd = File::open("/dev/urandom")?;
fd.read_exact(&mut buf)?;
Ok(u64_from_bytes(buf))
}

View file

@ -6,9 +6,10 @@ use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
mod error; mod error;
mod id;
mod link; mod link;
mod parser; mod parser;
pub use {error::Error, link::Link, parser::Parser}; pub use {error::Error, id::Id, link::Link, parser::Parser};
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct Recipients { pub struct Recipients {

View file

@ -22,6 +22,7 @@ impl Parser {
} }
} }
#[allow(clippy::missing_panics_doc)]
pub fn parse(mut self, content: &str) -> Result<Message, super::Error> { pub fn parse(mut self, content: &str) -> Result<Message, super::Error> {
let lines = content.lines(); let lines = content.lines();
for l in lines { for l in lines {

View file

@ -6,7 +6,7 @@ pub use super::{
mailbox::{Error as ParseMailboxError, Mailbox}, mailbox::{Error as ParseMailboxError, Mailbox},
mailstore::{Account, Domain, Filesystem, FilesystemError, Folder, MailStore}, mailstore::{Account, Domain, Filesystem, FilesystemError, Folder, MailStore},
mailuser::Mailuser, mailuser::Mailuser,
message::{Error as ParseMessageError, Link, Message, Recipients}, message::{Error as ParseMessageError, Id, Link, Message, Recipients},
//receiver, //receiver,
request::{Error as ParseRequestError, Request}, request::{Error as ParseRequestError, Request},
response::{Error as ParseResponseError, Response}, response::{Error as ParseResponseError, Response},

View file

@ -1,4 +1,6 @@
//! A container representing a moment in ISO-8601 format Time //! A container representing a moment in ISO-8601 format Time
#![allow(clippy::module_name_repetitions)]
use crate::prelude::Id;
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
mod error; mod error;
@ -8,13 +10,13 @@ pub use {error::Error, parser::Parser};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const SECS_PER_DAY: i64 = 86400; const SECS_PER_DAY: u64 = 86400;
fn days_since_epoch(ts: i64) -> i64 { fn days_since_epoch(ts: u64) -> u64 {
ts / SECS_PER_DAY ts / SECS_PER_DAY
} }
fn is_leap(year: i64) -> bool { fn is_leap(year: u32) -> bool {
year % 4 == 0 && (year % 100 != 0 || year % 16 == 0) year % 4 == 0 && (year % 100 != 0 || year % 16 == 0)
} }
@ -22,7 +24,7 @@ fn is_leap(year: i64) -> bool {
/// given month is February, then we also need to know /// given month is February, then we also need to know
/// whether it is a leap year or not, hence this function /// whether it is a leap year or not, hence this function
/// takes the year as an argument as well. /// takes the year as an argument as well.
pub fn days_in_month(month: u8, year: i64) -> i64 { pub fn days_in_month(month: u8, year: u32) -> u8 {
assert!(month < 13); assert!(month < 13);
match month { match month {
1 | 3 | 5 | 7 | 10 | 12 => 31, 1 | 3 | 5 | 7 | 10 | 12 => 31,
@ -142,8 +144,8 @@ impl fmt::Display for DateTime {
impl PartialOrd for DateTime { impl PartialOrd for DateTime {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let a: i64 = (*self).into(); let a: u64 = (*self).into();
let b: i64 = (*other).into(); let b: u64 = (*other).into();
a.partial_cmp(&b) a.partial_cmp(&b)
} }
} }
@ -156,12 +158,8 @@ impl FromStr for DateTime {
} }
} }
impl From<i64> for DateTime { impl From<u64> for DateTime {
fn from(ts: i64) -> Self { fn from(ts: u64) -> Self {
// Only allow using this function for positive timestamp values,
// ie no dates before 1970-01-01
assert!(ts.is_positive());
let mut year = 1970; let mut year = 1970;
let mut days = days_since_epoch(ts); let mut days = days_since_epoch(ts);
loop { loop {
@ -174,8 +172,9 @@ impl From<i64> for DateTime {
} }
} }
let mut month = 1; let mut month = 1;
while days_in_month(month, year) <= days { let m_days = u64::from(days_in_month(month, year));
days -= days_in_month(month, year); while m_days <= days {
days -= m_days;
month += 1; month += 1;
} }
let seconds = ts % SECS_PER_DAY; let seconds = ts % SECS_PER_DAY;
@ -184,8 +183,8 @@ impl From<i64> for DateTime {
let hour = minutes / 60; let hour = minutes / 60;
let minute = minutes % 60; let minute = minutes % 60;
DateTime { DateTime {
year: year as u32, year,
month: month.try_into().unwrap(), month,
day: (days + 1).try_into().unwrap(), day: (days + 1).try_into().unwrap(),
hour: Some((hour).try_into().unwrap()), hour: Some((hour).try_into().unwrap()),
minute: Some((minute).try_into().unwrap()), minute: Some((minute).try_into().unwrap()),
@ -195,20 +194,28 @@ impl From<i64> for DateTime {
} }
} }
impl From<DateTime> for i64 { #[allow(clippy::cast_possible_truncation)]
impl From<Id> for DateTime {
fn from(value: Id) -> Self {
let time = value.time;
time.into()
}
}
impl From<DateTime> for u64 {
fn from(dt: DateTime) -> Self { fn from(dt: DateTime) -> Self {
let mut seconds = dt.second.unwrap_or(0).into(); let mut seconds = dt.second.unwrap_or(0).into();
seconds += dt.minute.unwrap_or(0) as i64 * 60; seconds += u64::from(dt.minute.unwrap_or(0)) * 60;
seconds += (dt.hour.unwrap_or(1)) as i64 * 3600; seconds += u64::from(dt.hour.unwrap_or(1)) * 3600;
seconds += SECS_PER_DAY * (dt.day as i64 - 1); seconds += SECS_PER_DAY * (u64::from(dt.day) - 1);
let mut m = 1; let mut m = 1;
while m < dt.month { while m < dt.month {
seconds += SECS_PER_DAY * days_in_month(m, dt.year.into()); seconds += SECS_PER_DAY * u64::from(days_in_month(m, dt.year));
m += 1; m += 1;
} }
let mut y = 1970; let mut y = 1970;
while y < dt.year { while y < dt.year {
if is_leap(y.into()) { if is_leap(y) {
seconds += SECS_PER_DAY * 366; seconds += SECS_PER_DAY * 366;
} else { } else {
seconds += SECS_PER_DAY * 365; seconds += SECS_PER_DAY * 365;
@ -216,9 +223,9 @@ impl From<DateTime> for i64 {
y += 1; y += 1;
} }
if let Some(TimeZone::Offset(offset)) = dt.tz { if let Some(TimeZone::Offset(offset)) = dt.tz {
let mut offset_seconds = offset.hours as i64 * 3600; let mut offset_seconds = u64::from(offset.hours) * 3600;
if let Some(offset_minutes) = offset.minutes { if let Some(offset_minutes) = offset.minutes {
offset_seconds += offset_minutes as i64 * 60; offset_seconds += u64::from(offset_minutes) * 60;
} }
match offset.sign { match offset.sign {
Sign::Positive => seconds += offset_seconds, Sign::Positive => seconds += offset_seconds,
@ -237,7 +244,7 @@ impl DateTime {
/// offset is applied and the result returned. /// offset is applied and the result returned.
pub fn normalize(&mut self) -> &Self { pub fn normalize(&mut self) -> &Self {
if let Some(TimeZone::Offset(_)) = self.tz { if let Some(TimeZone::Offset(_)) = self.tz {
*self = i64::from(*self).into(); *self = u64::from(*self).into();
} }
self self
} }

View file

@ -55,7 +55,7 @@ impl<'a> Parser<'a> {
#[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_panics_doc)]
fn validate_day(&self, day: u8) -> Result<(), Error> { fn validate_day(&self, day: u8) -> Result<(), Error> {
let max = days_in_month(self.month.unwrap(), self.year.unwrap() as i64) as u8; let max = days_in_month(self.month.unwrap(), self.year.unwrap());
if day > max { if day > max {
Err(Error::InvalidDay) Err(Error::InvalidDay)
} else { } else {
@ -316,18 +316,22 @@ impl<'a> Parser<'a> {
} }
_ => {} _ => {}
} }
if self.year.is_none() || self.month.is_none() || self.day.is_none() { if let Some(year) = self.year {
return Err(Error::Truncated); if let Some(month) = self.month {
} if let Some(day) = self.day {
Ok(DateTime { return Ok(DateTime {
year: self.year.unwrap(), year,
month: self.month.unwrap(), month,
day: self.day.unwrap(), day,
hour: self.hour, hour: self.hour,
minute: self.minute, minute: self.minute,
second: self.second, second: self.second,
tz: self.tz, tz: self.tz,
}) });
}
}
}
Err(Error::Truncated)
} }
} }