use super::error::OptBlockError;
use super::header_constants::ALLOWED_OPT_BLOCK_IDS;
#[derive(Debug, PartialEq, Clone)]
pub struct OptBlock {
id: String,
data: String,
length: usize,
next: Option<Box<OptBlock>>,
}
impl OptBlock {
pub fn new(id: &str, data: &str, next: Option<OptBlock>) -> Result<Self, OptBlockError> {
let mut opt_block = Self::new_empty();
opt_block.set_id(id)?;
opt_block.set_data(data)?;
opt_block.set_next(next);
Ok(opt_block)
}
pub fn new_empty() -> Self {
Self {
id: String::new(),
data: String::new(),
length: 0,
next: None,
}
}
pub fn new_from_str(s: &str, num_opt_blocks: usize) -> Result<Self, OptBlockError> {
if !s.is_ascii() {
return Err(OptBlockError::NonAsciiInput);
}
if s.len() < 4 {
return Err(OptBlockError::StringTooShort {
minimum: 4,
actual: s.len(),
});
}
let mut opt_block = Self::new_empty();
opt_block.set_id(&s[..2])?;
let data_start_offset;
if &s[2..4] == "00" {
if s.len() < 256 {
return Err(OptBlockError::ExtendedLengthStringTooShort {
minimum: 256,
actual: s.len(),
});
}
let ext_block_len = &s[4..10];
opt_block.length = Self::ext_len_from_str(ext_block_len)?;
data_start_offset = 10;
} else {
opt_block.length = Self::len_from_str(&s[2..4])?;
data_start_offset = 4;
}
if s.len() < opt_block.length {
return Err(OptBlockError::StringTooShortForLength {
required: opt_block.length,
actual: s.len(),
});
}
opt_block.set_data(&s[data_start_offset..opt_block.length])?;
if num_opt_blocks > 1 {
let next_block_str = &s[opt_block.length..];
let next_block = Self::new_from_str(next_block_str, num_opt_blocks - 1)?;
opt_block.set_next(Some(next_block));
}
Ok(opt_block)
}
pub fn export_str(&self) -> Result<String, OptBlockError> {
if self.length < 4 {
return Err(OptBlockError::Uninitialized {
length: self.length,
});
}
let mut result = String::new();
result.push_str(&self.id);
if self.length < 256 {
result.push_str(&format!("{:02X}", self.length,));
} else {
result.push_str(&format!("0002{:04X}", self.length,));
}
result.push_str(&self.data);
if let Some(next) = &self.next {
result.push_str(&next.export_str()?);
}
Ok(result)
}
pub fn set_id(&mut self, id: &str) -> Result<(), OptBlockError> {
if Self::is_allowed_id(id) {
self.id = id.to_string();
Ok(())
} else {
Err(OptBlockError::InvalidId(id.to_string()))
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn set_data(&mut self, data: &str) -> Result<(), OptBlockError> {
if self.id.len() != 2 {
return Err(OptBlockError::IdNotSet);
}
if !data.is_ascii() {
return Err(OptBlockError::NonAsciiData(data.to_string()));
}
self.data = data.to_string();
self.set_length()?;
Ok(())
}
pub fn data(&self) -> &str {
&self.data
}
fn set_length(&mut self) -> Result<(), OptBlockError> {
const MAX_OPT_BLOCK_LENGTH: usize = 65535;
let minimum_length = self.id.len() + 2 + self.data.len();
self.length = if minimum_length < 256 {
minimum_length
} else {
minimum_length + 6
};
if self.length > MAX_OPT_BLOCK_LENGTH {
let actual = self.length;
self.length = 0;
return Err(OptBlockError::BlockTooLong {
maximum: MAX_OPT_BLOCK_LENGTH,
actual,
});
}
Ok(())
}
pub fn length(&self) -> &usize {
&self.length
}
pub fn set_next(&mut self, next_block: Option<OptBlock>) {
self.next = next_block.map(Box::new);
}
pub fn next(&self) -> Option<&OptBlock> {
self.next.as_deref()
}
pub fn append(&mut self, opt_block_to_append: OptBlock) {
match &mut self.next {
Some(next_block) => {
next_block.append(opt_block_to_append);
}
None => {
self.set_next(Some(opt_block_to_append));
}
}
}
pub fn is_allowed_id(id: &str) -> bool {
ALLOWED_OPT_BLOCK_IDS.contains(&id)
}
pub fn total_length(&self) -> usize {
let mut total = self.length;
if let Some(next) = &self.next {
total += next.total_length();
}
total
}
fn len_from_str(s: &str) -> Result<usize, OptBlockError> {
if s.len() != 2 {
return Err(OptBlockError::InvalidLengthFieldWidth {
value: s.to_string(),
expected: 2,
});
}
let length = usize::from_str_radix(s, 16).map_err(|source| {
OptBlockError::InvalidLengthFieldHex {
value: s.to_string(),
source,
}
})?;
if length < 4 {
return Err(OptBlockError::LengthFieldTooSmall {
minimum: 4,
actual: length,
});
}
Ok(length)
}
fn ext_len_from_str(s: &str) -> Result<usize, OptBlockError> {
if s.len() != 6 {
return Err(OptBlockError::InvalidExtendedLengthField(s.to_string()));
}
let length_of_length = &s[0..2];
if length_of_length != "02" {
return Err(OptBlockError::InvalidLengthOfLengthField(
length_of_length.to_string(),
));
}
let encoded_length = &s[2..6];
let length = usize::from_str_radix(encoded_length, 16).map_err(|source| {
OptBlockError::InvalidExtendedLengthHex {
value: encoded_length.to_string(),
source,
}
})?;
if length <= 255 {
return Err(OptBlockError::ExtendedLengthTooSmall {
value: encoded_length.to_string(),
});
}
Ok(length)
}
}