use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reference {
object_token: u64,
region: Option<Vec<u8>>,
file_name: Option<String>,
loc_id: Option<u64>,
}
impl Reference {
pub fn print_token(token: u64) -> String {
format!("{token:#x}")
}
pub fn init_package() -> bool {
true
}
pub fn create_object(object_token: u64, file_name: Option<String>) -> Self {
Self {
object_token,
region: None,
file_name,
loc_id: None,
}
}
pub fn create_region(object_token: u64, region: Vec<u8>, file_name: Option<String>) -> Self {
Self {
object_token,
region: Some(region),
file_name,
loc_id: None,
}
}
pub fn destroy(self) {}
pub fn set_loc_id(&mut self, loc_id: u64) {
self.loc_id = Some(loc_id);
}
pub fn get_loc_id(&self) -> Option<u64> {
self.loc_id
}
pub fn reopen_file(&self) -> Option<&str> {
self.file_name.as_deref()
}
pub fn equal(&self, other: &Self) -> bool {
self == other
}
pub fn copy(&self) -> Self {
self.clone()
}
pub fn get_obj_token(&self) -> u64 {
self.object_token
}
pub fn set_obj_token(&mut self, token: u64) {
self.object_token = token;
}
pub fn get_region(&self) -> Option<&[u8]> {
self.region.as_deref()
}
pub fn get_file_name(&self) -> Option<&str> {
self.file_name.as_deref()
}
pub fn encode(&self) -> Result<Vec<u8>> {
let mut out = Vec::new();
Self::encode_obj_token_into(self.object_token, &mut out);
Self::encode_region_into(self.region.as_deref(), &mut out)?;
Ok(out)
}
pub fn encode_obj_token(&self) -> Vec<u8> {
let mut out = Vec::new();
Self::encode_obj_token_into(self.object_token, &mut out);
out
}
pub fn encode_region(&self) -> Result<Vec<u8>> {
let mut out = Vec::new();
Self::encode_region_into(self.region.as_deref(), &mut out)?;
Ok(out)
}
pub fn decode_region(bytes: &[u8]) -> Result<Option<Vec<u8>>> {
if bytes.is_empty() {
return Ok(None);
} else {
let len_u64 = read_u64_le_at(bytes, 0, "reference region length")?;
let len = usize::try_from(len_u64).map_err(|_| {
Error::InvalidFormat("reference region length exceeds usize".into())
})?;
let end = 8usize
.checked_add(len)
.ok_or_else(|| Error::InvalidFormat("reference region length overflow".into()))?;
if bytes.len() != end {
return Err(Error::InvalidFormat(
"reference region payload has invalid length".into(),
));
}
Ok(Some(bytes[8..end].to_vec()))
}
}
pub fn encode_heap(&self) -> Result<Vec<u8>> {
self.encode()
}
pub fn encode_token_obj_compat(&self) -> Vec<u8> {
self.encode_obj_token()
}
pub fn decode_token_obj_compat(bytes: &[u8]) -> Result<u64> {
if bytes.len() != 8 {
return Err(Error::InvalidFormat(
"object reference token payload has invalid length".into(),
));
}
read_u64_le_at(bytes, 0, "object reference token")
}
pub fn decode_token_region_compat(bytes: &[u8]) -> Result<Vec<u8>> {
Self::decode_region(bytes)?
.ok_or_else(|| Error::InvalidFormat("region reference token payload is empty".into()))
}
pub fn create_object_api(object_token: u64, file_name: Option<String>) -> Self {
Self::create_object(object_token, file_name)
}
pub fn create_region_api(
object_token: u64,
region: Vec<u8>,
file_name: Option<String>,
) -> Self {
Self::create_region(object_token, region, file_name)
}
pub fn open_attr_api_common(&self) -> u64 {
self.object_token
}
pub fn encode_token_region_compat(&self) -> Result<Vec<u8>> {
self.encode_region()
}
pub fn get_region_api(&self) -> Option<&[u8]> {
self.get_region()
}
fn encode_obj_token_into(token: u64, out: &mut Vec<u8>) {
out.extend_from_slice(&token.to_le_bytes());
}
fn encode_region_into(region: Option<&[u8]>, out: &mut Vec<u8>) -> Result<()> {
let len = region.map_or(Ok(0u64), |region| {
u64::try_from(region.len())
.map_err(|_| Error::InvalidFormat("reference region length exceeds u64".into()))
})?;
out.extend_from_slice(&len.to_le_bytes());
if let Some(region) = region {
out.extend_from_slice(region);
}
Ok(())
}
}
fn checked_window<'a>(data: &'a [u8], pos: usize, len: usize, context: &str) -> Result<&'a [u8]> {
let end = pos
.checked_add(len)
.ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
data.get(pos..end)
.ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
}
fn read_u64_le_at(data: &[u8], pos: usize, context: &str) -> Result<u64> {
let bytes: [u8; 8] = checked_window(data, pos, 8, context)?
.try_into()
.map_err(|_| Error::InvalidFormat(format!("{context} is truncated")))?;
Ok(u64::from_le_bytes(bytes))
}
#[cfg(test)]
mod tests {
use super::Reference;
#[test]
fn reference_aliases_roundtrip() {
assert!(Reference::init_package());
assert_eq!(Reference::print_token(42), "0x2a");
let mut r = Reference::create_region(7, vec![1, 2, 3], Some("a.h5".into()));
assert_eq!(r.get_obj_token(), 7);
r.set_obj_token(8);
r.set_loc_id(9);
assert_eq!(r.get_loc_id(), Some(9));
assert_eq!(r.reopen_file(), Some("a.h5"));
assert_eq!(r.get_file_name(), Some("a.h5"));
assert_eq!(r.get_region(), Some([1, 2, 3].as_slice()));
assert!(r.equal(&r.copy()));
assert_eq!(
Reference::decode_token_obj_compat(&r.encode_obj_token()).unwrap(),
8
);
assert_eq!(
Reference::decode_token_region_compat(&r.encode_token_region_compat().unwrap())
.unwrap(),
vec![1, 2, 3]
);
assert_eq!(
Reference::decode_region(&r.encode_region().unwrap()).unwrap(),
Some(vec![1, 2, 3])
);
assert!(Reference::decode_token_obj_compat(&[0; 7]).is_err());
assert!(Reference::decode_token_region_compat(&[2, 0, 0, 0, 0, 0, 0, 0, 1]).is_err());
assert!(Reference::decode_region(&u64::MAX.to_le_bytes()).is_err());
assert_eq!(r.open_attr_api_common(), 8);
assert_eq!(r.get_region_api(), Some([1, 2, 3].as_slice()));
Reference::create_object_api(1, None).destroy();
Reference::create_region_api(1, vec![4], None).destroy();
}
}