use std::fmt;
use std::str::FromStr;
use std::time::Duration;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
pub const DEFAULT_BLOCK_SIZE: u16 = 512;
pub const DEFAULT_WINDOW_SIZE: u16 = 1;
pub const DEFAULT_WINDOW_WAIT: Duration = Duration::from_millis(0);
pub const DEFAULT_MAX_RETRIES: usize = 6;
pub const DEFAULT_ROLLOVER: Rollover = Rollover::Enforce0;
#[derive(Debug, PartialEq)]
pub enum RequestType {
Read(u64),
Write,
}
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[allow(dead_code)]
pub enum Rollover {
None,
Enforce0,
Enforce1,
DontCare,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OptionsPrivate {
pub repeat_count: u8,
pub clean_on_error: bool,
pub max_retries: usize,
pub rollover: Rollover,
}
impl Default for OptionsPrivate {
fn default() -> Self {
Self {
repeat_count: 1,
clean_on_error: true,
max_retries: DEFAULT_MAX_RETRIES,
rollover: DEFAULT_ROLLOVER,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OptionsProtocol {
pub block_size: u16,
pub window_size: u16,
pub window_wait: Duration,
pub timeout: Duration,
pub transfer_size: Option<u64>,
}
impl OptionsProtocol {
pub fn parse(
options: &mut [TransferOption],
request_type: RequestType,
) -> anyhow::Result<OptionsProtocol> {
let mut opt_common = OptionsProtocol::default();
for option in options {
let TransferOption {
option: option_type,
value,
} = option;
match option_type {
OptionType::BlockSize => {
if *value == 0 {
log::warn!(" Invalid block size 0. Changed to {DEFAULT_BLOCK_SIZE}.");
*value = DEFAULT_BLOCK_SIZE as u64;
} else if 65464 < *value {
log::warn!(" Invalid block size {}. Changed to 65464.", *value);
*value = 65464;
}
opt_common.block_size = *value as u16;
}
OptionType::TransferSize => match request_type {
RequestType::Read(size) => {
*value = size;
opt_common.transfer_size = Some(size);
}
RequestType::Write => opt_common.transfer_size = Some(*value),
},
OptionType::Timeout => {
if *value == 0 {
log::warn!(" Invalid timeout value 0. Changed to 1.");
*value = 1;
} else if 255 < *value {
log::warn!(" Invalid timeout value {}. Changed to 255.", *value);
*value = 255;
}
opt_common.timeout = Duration::from_secs(*value);
}
OptionType::TimeoutMs => {
if *value == 0 {
log::warn!(" Invalid timeoutms value 0. Changed to 1.");
*value = 1;
}
opt_common.timeout = Duration::from_millis(*value);
}
OptionType::WindowSize => {
if *value == 0 {
log::warn!(" Invalid window size 0. Changed to 1.");
*value = 1;
} else if 65535 < *value {
log::warn!(" Invalid window size {}. Changed to 65535.", *value);
*value = 65535;
}
opt_common.window_size = *value as u16;
}
OptionType::WindowWait => {
opt_common.window_wait = Duration::from_millis(*value);
}
}
}
Ok(opt_common)
}
}
impl Default for OptionsProtocol {
fn default() -> Self {
Self {
block_size: DEFAULT_BLOCK_SIZE,
window_size: DEFAULT_WINDOW_SIZE,
window_wait: DEFAULT_WINDOW_WAIT,
timeout: DEFAULT_TIMEOUT,
transfer_size: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TransferOption {
pub option: OptionType,
pub value: u64,
}
impl TransferOption {
pub fn as_bytes(&self) -> Vec<u8> {
[
self.option.as_str().as_bytes(),
&[0x00],
self.value.to_string().as_bytes(),
&[0x00],
]
.concat()
}
}
#[allow(dead_code)]
pub struct OptionFmt<'a>(pub &'a [TransferOption]);
impl fmt::Display for OptionFmt<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, e) in self.0.iter().enumerate() {
if i != 0 {
write!(f, ", ")?
}
write!(f, "{}:{}", e.option.as_str(), e.value)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OptionType {
BlockSize,
TransferSize,
Timeout,
TimeoutMs,
WindowSize,
WindowWait,
}
impl OptionType {
pub fn as_str(&self) -> &'static str {
match self {
OptionType::BlockSize => "blksize",
OptionType::TransferSize => "tsize",
OptionType::Timeout => "timeout",
OptionType::TimeoutMs => "timeoutms",
OptionType::WindowSize => "windowsize",
OptionType::WindowWait => "windowwait",
}
}
}
impl FromStr for OptionType {
type Err = &'static str;
fn from_str(value: &str) -> Result<Self, &'static str> {
match value {
"blksize" => Ok(OptionType::BlockSize),
"tsize" => Ok(OptionType::TransferSize),
"timeout" => Ok(OptionType::Timeout),
"timeoutms" => Ok(OptionType::TimeoutMs),
"windowsize" => Ok(OptionType::WindowSize),
"windowwait" => Ok(OptionType::WindowWait),
_ => Err("Invalid option type"),
}
}
}