use int_enum::IntEnum;
use crate::flags::TransmissionFlags;
#[repr(u32)]
#[derive(Debug, IntEnum, PartialEq, Eq)]
pub(crate) enum OptionReplyType {
Ack = 1,
Server = 2,
Info = 3,
MetaContext = 4,
}
#[derive(Debug)]
pub(crate) enum InfoPayload {
Export(u64, TransmissionFlags),
Name(String),
Description(String),
BlockSize(u32, u32, u32),
}
impl InfoPayload {
fn to_vec(&self) -> Vec<u8> {
match self {
InfoPayload::Export(size, flags) => {
let mut data = Vec::with_capacity(12);
data.extend(&0u16.to_be_bytes());
data.extend(&size.to_be_bytes());
data.extend(&flags.bits().to_be_bytes());
data
}
InfoPayload::Name(name) => {
let mut data = Vec::new();
data.extend(1u16.to_be_bytes());
data.extend(name.bytes());
data
}
InfoPayload::Description(desc) => {
let mut data = Vec::new();
data.extend(2u16.to_be_bytes());
data.extend(desc.bytes());
data
}
InfoPayload::BlockSize(min, preferred, max) => {
let mut data = Vec::with_capacity(14);
data.extend(3u16.to_be_bytes());
data.extend(min.to_be_bytes());
data.extend(preferred.to_be_bytes());
data.extend(max.to_be_bytes());
data
}
}
}
}
#[derive(Debug)]
pub(crate) enum OptionReply {
Ack,
Server(String),
Info(InfoPayload),
MetaContext(u32, String),
}
impl OptionReply {
pub(crate) fn get_reply_type(&self) -> OptionReplyType {
match self {
OptionReply::Ack => OptionReplyType::Ack,
OptionReply::Server(_) => OptionReplyType::Server,
OptionReply::Info(_) => OptionReplyType::Info,
OptionReply::MetaContext(_, _) => OptionReplyType::MetaContext,
}
}
pub(crate) fn get_data(&self) -> Vec<u8> {
match self {
OptionReply::Ack => vec![],
OptionReply::Server(name) => name.as_bytes().to_vec(),
OptionReply::Info(payload) => payload.to_vec(),
OptionReply::MetaContext(id, name) => {
let mut data = id.to_be_bytes().to_vec();
data.extend(name.as_bytes());
data
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_option_reply_type_conversion() {
assert_eq!(OptionReplyType::Ack as u32, 1);
assert_eq!(OptionReplyType::Server as u32, 2);
assert_eq!(OptionReplyType::Info as u32, 3);
assert_eq!(OptionReplyType::MetaContext as u32, 4);
match OptionReplyType::try_from(1u32) {
Ok(reply_type) => assert!(matches!(reply_type, OptionReplyType::Ack)),
Err(_) => panic!("Failed to convert 1 to OptionReplyType::Ack"),
}
match OptionReplyType::try_from(2u32) {
Ok(reply_type) => assert!(matches!(reply_type, OptionReplyType::Server)),
Err(_) => panic!("Failed to convert 2 to OptionReplyType::Server"),
}
match OptionReplyType::try_from(3u32) {
Ok(reply_type) => assert!(matches!(reply_type, OptionReplyType::Info)),
Err(_) => panic!("Failed to convert 3 to OptionReplyType::Info"),
}
match OptionReplyType::try_from(4u32) {
Ok(reply_type) => assert!(matches!(reply_type, OptionReplyType::MetaContext)),
Err(_) => panic!("Failed to convert 4 to OptionReplyType::MetaContext"),
}
assert!(OptionReplyType::try_from(99u32).is_err());
}
#[test]
fn test_option_reply_get_reply_type() {
match OptionReply::Ack.get_reply_type() {
OptionReplyType::Ack => (),
_ => panic!("OptionReply::Ack returned wrong reply type"),
}
match OptionReply::Server("export1".to_string()).get_reply_type() {
OptionReplyType::Server => (),
_ => panic!("OptionReply::Server returned wrong reply type"),
}
match OptionReply::Info(InfoPayload::Name("export1".to_string())).get_reply_type() {
OptionReplyType::Info => (),
_ => panic!("OptionReply::Info returned wrong reply type"),
}
match OptionReply::MetaContext(42, "meta".to_string()).get_reply_type() {
OptionReplyType::MetaContext => (),
_ => panic!("OptionReply::MetaContext returned wrong reply type"),
}
}
#[test]
fn test_option_reply_get_data_ack() {
let data = OptionReply::Ack.get_data();
assert!(data.is_empty());
}
#[test]
fn test_option_reply_get_data_server() {
let export_name = "test_export";
let data = OptionReply::Server(export_name.to_string()).get_data();
assert_eq!(data, export_name.as_bytes());
}
#[test]
fn test_option_reply_get_data_meta_context() {
let id = 42u32;
let name = "meta_context";
let data = OptionReply::MetaContext(id, name.to_string()).get_data();
let mut expected = id.to_be_bytes().to_vec();
expected.extend_from_slice(name.as_bytes());
assert_eq!(data, expected);
}
#[test]
fn test_info_payload_export() {
use crate::flags::TransmissionFlags;
let size = 1024u64;
let flags = TransmissionFlags::HAS_FLAGS | TransmissionFlags::READ_ONLY;
let flags_bits = flags.bits();
let flags2 = TransmissionFlags::HAS_FLAGS | TransmissionFlags::READ_ONLY;
let data = InfoPayload::Export(size, flags2).to_vec();
assert_eq!(data.len(), 12);
assert_eq!(&data[0..2], &[0, 0]);
let size_bytes = size.to_be_bytes();
assert_eq!(&data[2..10], &size_bytes);
let flags_bytes = flags_bits.to_be_bytes();
assert_eq!(&data[10..12], &flags_bytes);
}
#[test]
fn test_info_payload_name() {
let name = "test_export";
let data = InfoPayload::Name(name.to_string()).to_vec();
assert_eq!(&data[0..2], &[0, 1]);
assert_eq!(&data[2..], name.as_bytes());
}
#[test]
fn test_info_payload_description() {
let desc = "Test export description";
let data = InfoPayload::Description(desc.to_string()).to_vec();
assert_eq!(&data[0..2], &[0, 2]);
assert_eq!(&data[2..], desc.as_bytes());
}
#[test]
fn test_info_payload_block_size() {
let min = 512u32;
let preferred = 4096u32;
let max = 33554432u32;
let data = InfoPayload::BlockSize(min, preferred, max).to_vec();
assert_eq!(data.len(), 14);
assert_eq!(&data[0..2], &[0, 3]);
let min_bytes = min.to_be_bytes();
assert_eq!(&data[2..6], &min_bytes);
let preferred_bytes = preferred.to_be_bytes();
assert_eq!(&data[6..10], &preferred_bytes);
let max_bytes = max.to_be_bytes();
assert_eq!(&data[10..14], &max_bytes);
}
#[test]
fn test_info_payload_via_option_reply() {
let desc = "Test description";
let info_payload = InfoPayload::Description(desc.to_string());
let option_reply = OptionReply::Info(info_payload);
let data = option_reply.get_data();
assert_eq!(&data[0..2], &[0, 2]);
assert_eq!(&data[2..], desc.as_bytes());
}
}