use std::{ fmt::Display, path::PathBuf, sync::{atomic::AtomicUsize, Arc}, time::{Duration, Instant}, }; use serde::{Deserialize, Serialize}; use smtp_proto::Response; use tokio::sync::oneshot; use crate::listener::limiter::ConcurrencyLimiter; pub type QueueId = u64; #[derive(Debug)] pub enum Event { Queue(Schedule>), Manage(QueueRequest), Done(WorkerResult), Stop, } #[derive(Debug)] pub enum QueueRequest { List { from: Option, to: Option, before: Option, after: Option, result_tx: oneshot::Sender>, }, Status { queue_ids: Vec, result_tx: oneshot::Sender>>, }, Cancel { queue_ids: Vec, item: Option, result_tx: oneshot::Sender>, }, Retry { queue_ids: Vec, item: Option, time: Instant, result_tx: oneshot::Sender>, }, } #[derive(Debug)] pub enum WorkerResult { Done, Retry(Schedule>), OnHold(OnHold>), } #[derive(Debug)] pub struct OnHold { pub next_due: Option, pub limiters: Vec, pub message: T, } #[derive(Debug)] pub struct Schedule { pub due: Instant, pub inner: T, } #[derive(Debug)] pub struct Message { pub id: QueueId, pub created: u64, pub path: PathBuf, pub return_path: String, pub return_path_lcase: String, pub return_path_domain: String, pub recipients: Vec, pub domains: Vec, pub flags: u64, pub env_id: Option, pub priority: i16, pub size: usize, pub queue_refs: Vec, } #[derive(Debug, PartialEq, Eq)] pub struct Domain { pub domain: String, pub retry: Schedule, pub notify: Schedule, pub expires: Instant, pub status: Status<(), Error>, pub changed: bool, } #[derive(Debug, PartialEq, Eq)] pub struct Recipient { pub domain_idx: usize, pub address: String, pub address_lcase: String, pub status: Status, HostResponse>, pub flags: u64, pub orcpt: Option, } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Status { #[serde(rename = "scheduled")] Scheduled, #[serde(rename = "completed")] Completed(T), #[serde(rename = "temp_fail")] TemporaryFailure(E), #[serde(rename = "perm_fail")] PermanentFailure(E), } #[derive(Debug, PartialEq, Eq)] pub struct HostResponse { pub hostname: T, pub response: Response, } #[derive(Debug, PartialEq, Eq)] pub enum Error { DnsError(String), UnexpectedResponse(HostResponse), ConnectionError(ErrorDetails), TlsError(ErrorDetails), DaneError(ErrorDetails), MtaStsError(String), RateLimited, ConcurrencyLimited, Io(String), } #[derive(Debug, PartialEq, Eq)] pub struct ErrorDetails { pub entity: String, pub details: String, } #[derive(Debug)] pub struct UsedQuota { pub id: u64, pub size: usize, pub limiter: Arc, } #[derive(Debug)] pub struct QuotaLimiter { pub max_size: usize, pub max_messages: usize, pub size: AtomicUsize, pub messages: AtomicUsize, } impl PartialEq for UsedQuota { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.size == other.size } } impl Eq for UsedQuota {} impl Ord for Schedule { fn cmp(&self, other: &Self) -> std::cmp::Ordering { other.due.cmp(&self.due) } } impl PartialOrd for Schedule { fn partial_cmp(&self, other: &Self) -> Option { other.due.partial_cmp(&self.due) } } impl PartialEq for Schedule { fn eq(&self, other: &Self) -> bool { self.due == other.due } } impl Eq for Schedule {} impl Schedule { pub fn now() -> Self { Schedule { due: Instant::now(), inner: T::default(), } } pub fn later(duration: Duration) -> Self { Schedule { due: Instant::now() + duration, inner: T::default(), } } } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::UnexpectedResponse(response) => { write!( f, "Unexpected response from '{}': {}", response.hostname.entity, response.response ) } Error::DnsError(err) => { write!(f, "DNS lookup failed: {err}") } Error::ConnectionError(details) => { write!( f, "Connection to '{}' failed: {}", details.entity, details.details ) } Error::TlsError(details) => { write!( f, "TLS error from '{}': {}", details.entity, details.details ) } Error::DaneError(details) => { write!( f, "DANE failed to authenticate '{}': {}", details.entity, details.details ) } Error::MtaStsError(details) => { write!(f, "MTA-STS auth failed: {details}") } Error::RateLimited => { write!(f, "Rate limited") } Error::ConcurrencyLimited => { write!(f, "Too many concurrent connections to remote server") } Error::Io(err) => { write!(f, "Queue error: {err}") } } } } impl Display for Status<(), Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Status::Scheduled => write!(f, "Scheduled"), Status::Completed(_) => write!(f, "Completed"), Status::TemporaryFailure(err) => write!(f, "Temporary Failure: {err}"), Status::PermanentFailure(err) => write!(f, "Permanent Failure: {err}"), } } } impl Display for Status, HostResponse> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Status::Scheduled => write!(f, "Scheduled"), Status::Completed(response) => write!(f, "Delivered: {}", response.response), Status::TemporaryFailure(err) => write!(f, "Temporary Failure: {}", err.response), Status::PermanentFailure(err) => write!(f, "Permanent Failure: {}", err.response), } } }