use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkType {
Hard,
Soft,
External,
UserDefined(u8),
}
#[derive(Debug, Clone)]
pub struct LinkMessage {
pub name: String,
pub link_type: LinkType,
pub creation_order: Option<u64>,
pub char_encoding: u8,
pub hard_link_addr: Option<u64>,
pub soft_link_target: Option<String>,
pub external_link: Option<(String, String)>,
}
impl LinkMessage {
pub fn decode(data: &[u8], sizeof_addr: u8) -> Result<Self> {
if data.is_empty() {
return Err(Error::InvalidFormat("empty link message".into()));
}
let mut pos = 0;
let version = data[pos];
pos += 1;
if version != 1 {
return Err(Error::Unsupported(format!(
"link message version {version}"
)));
}
let flags = data[pos];
pos += 1;
let size_of_len_of_link_name = flags & 0x03; let has_creation_order = flags & 0x04 != 0;
let has_link_type = flags & 0x08 != 0;
let has_char_encoding = flags & 0x10 != 0;
let link_type = if has_link_type {
let t = data[pos];
pos += 1;
match t {
0 => LinkType::Hard,
1 => LinkType::Soft,
64 => LinkType::External,
other => LinkType::UserDefined(other),
}
} else {
LinkType::Hard };
let creation_order = if has_creation_order {
let val = read_le_u64(&data[pos..], 8);
pos += 8;
Some(val)
} else {
None
};
let char_encoding = if has_char_encoding {
let e = data[pos];
pos += 1;
e
} else {
0 };
let name_len_size = 1 << size_of_len_of_link_name; let name_len = read_le_u64(&data[pos..], name_len_size) as usize;
pos += name_len_size;
if pos + name_len > data.len() {
return Err(Error::InvalidFormat("link name exceeds message bounds".into()));
}
let name = String::from_utf8_lossy(&data[pos..pos + name_len]).to_string();
pos += name_len;
let mut hard_link_addr = None;
let mut soft_link_target = None;
let mut external_link = None;
match link_type {
LinkType::Hard => {
if pos + sizeof_addr as usize > data.len() {
return Err(Error::InvalidFormat("hard link addr exceeds message bounds".into()));
}
hard_link_addr = Some(read_le_u64(&data[pos..], sizeof_addr as usize));
}
LinkType::Soft => {
if pos + 2 > data.len() {
return Err(Error::InvalidFormat("soft link target length exceeds bounds".into()));
}
let target_len = read_le_u64(&data[pos..], 2) as usize;
pos += 2;
if pos + target_len > data.len() {
return Err(Error::InvalidFormat("soft link target exceeds bounds".into()));
}
soft_link_target =
Some(String::from_utf8_lossy(&data[pos..pos + target_len]).to_string());
}
LinkType::External => {
if pos + 2 > data.len() {
return Err(Error::InvalidFormat("external link info length exceeds bounds".into()));
}
let info_len = read_le_u64(&data[pos..], 2) as usize;
pos += 2;
if pos + info_len > data.len() || info_len < 2 {
return Err(Error::InvalidFormat("external link info exceeds bounds".into()));
}
let ext_version = data[pos];
pos += 1;
let mut info_consumed = 1;
if ext_version >= 1 {
let _ext_flags = data[pos];
pos += 1;
info_consumed += 1;
}
let end = (pos + info_len - info_consumed).min(data.len());
let null_pos = data[pos..end].iter().position(|&b| b == 0).unwrap_or(end - pos);
let filename = String::from_utf8_lossy(&data[pos..pos + null_pos]).to_string();
pos = (pos + null_pos + 1).min(end);
let null_pos2 = data[pos..end].iter().position(|&b| b == 0).unwrap_or(end - pos);
let obj_path = String::from_utf8_lossy(&data[pos..pos + null_pos2]).to_string();
external_link = Some((filename, obj_path));
}
LinkType::UserDefined(_) => {
}
}
Ok(LinkMessage {
name,
link_type,
creation_order,
char_encoding,
hard_link_addr,
soft_link_target,
external_link,
})
}
}
fn read_le_u64(data: &[u8], size: usize) -> u64 {
let mut val = 0u64;
for i in 0..size.min(8).min(data.len()) {
val |= (data[i] as u64) << (i * 8);
}
val
}