use bytes::{Buf, BytesMut};
use enum_dispatch::enum_dispatch;
use crate::actions::{Abort, Continue, Discard, Quit, QuitNc, Reject, Replycode, Skip, Tempfail};
use crate::{
error::STAGE_DECODING, AddHeader, AddRecipient, ChangeHeader, DeleteRecipient, InsertHeader,
InvalidData, NotEnoughData, ProtocolError, Quarantine, ReplaceBody,
};
use super::commands::Connect;
use super::commands::Helo;
use super::commands::Macro;
use super::commands::Recipient;
use super::commands::Unknown;
use super::commands::{Body, EndOfBody};
use super::commands::{Data, Mail};
use super::commands::{EndOfHeader, Header};
use super::optneg::OptNeg;
pub(crate) trait Parsable: Sized {
const CODE: u8;
fn parse(buffer: BytesMut) -> Result<Self, ProtocolError>;
}
macro_rules! parse_command {
($container_name:ident, $($variant:ident),+$(,)?) => {
#[allow(missing_docs)]
#[enum_dispatch]
#[derive(Debug, Clone)]
pub enum $container_name {
$($variant($variant),)+
}
impl $container_name {
pub fn parse(mut buffer: BytesMut) -> Result<Self, ProtocolError> {
if buffer.is_empty() {
return Err(NotEnoughData::new(
STAGE_DECODING,
"Command",
"code missing to detect which command it is",
1,
0,
buffer
).into());
}
let code = buffer.get_u8();
match code {
$($variant::CODE => Ok($variant::parse(buffer)?.into()),)+
_ => {
Err(InvalidData{msg: "Unknown command sent with code", offending_bytes: BytesMut::from_iter(&[code])}.into())
}
}
}
}
$(impl From<$variant> for $container_name {
fn from(value: $variant) -> Self {
Self::$variant(value)
}
})+
}
}
parse_command!(
ClientCommand,
Abort,
OptNeg,
Quit,
QuitNc,
Macro,
Unknown,
Connect,
Helo,
Mail,
Recipient,
Header,
EndOfHeader,
Data,
Body,
EndOfBody,
);
parse_command!(
ServerCommand,
OptNeg,
Abort,
Continue,
Discard,
Reject,
Tempfail,
Skip,
Replycode,
AddRecipient,
DeleteRecipient,
ReplaceBody,
AddHeader,
InsertHeader,
ChangeHeader,
Quarantine,
);
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use bytes::BytesMut;
use super::*;
#[test]
fn test_create_abort() {
let data = vec![b'A'];
let command =
ClientCommand::parse(BytesMut::from_iter(data)).expect("Failed parsing abort data");
assert_matches!(command, ClientCommand::Abort(_));
}
#[test]
fn test_create_optneg() {
let data = vec![b'O', 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0];
let command =
ClientCommand::parse(BytesMut::from_iter(data)).expect("Failed parsing optneg data");
assert_matches!(command, ClientCommand::OptNeg(o) if o.version == 6);
}
}