use crate::util::bounded::{LengthExceeded, bytes::BoundedBytes, str::BoundedString};
use std::{
io, mem,
ops::Deref,
str::{self, FromStr},
};
const XATTR_LENGTH_LIMIT: usize = u32::MAX as usize;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[repr(transparent)]
pub struct XattrName(BoundedString<XATTR_LENGTH_LIMIT>);
impl XattrName {
#[inline]
pub fn new(value: impl Into<Box<str>>) -> Result<Self, LengthExceeded> {
BoundedString::new(value).map(Self)
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl Deref for XattrName {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl TryFrom<String> for XattrName {
type Error = LengthExceeded;
#[inline]
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<&str> for XattrName {
type Error = LengthExceeded;
#[inline]
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<XattrName> for String {
#[inline]
fn from(value: XattrName) -> Self {
value.0.into()
}
}
impl FromStr for XattrName {
type Err = LengthExceeded;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[repr(transparent)]
pub struct XattrValue(BoundedBytes<XATTR_LENGTH_LIMIT>);
impl XattrValue {
#[inline]
pub fn new(value: impl Into<Box<[u8]>>) -> Result<Self, LengthExceeded> {
BoundedBytes::new(value).map(Self)
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
}
impl Deref for XattrValue {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.0.as_slice()
}
}
impl TryFrom<Vec<u8>> for XattrValue {
type Error = LengthExceeded;
#[inline]
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<&[u8]> for XattrValue {
type Error = LengthExceeded;
#[inline]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<XattrValue> for Vec<u8> {
#[inline]
fn from(value: XattrValue) -> Self {
value.0.into()
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ExtendedAttribute {
name: XattrName,
value: XattrValue,
}
impl ExtendedAttribute {
#[inline]
pub const fn new(name: XattrName, value: XattrValue) -> Self {
Self { name, value }
}
#[inline]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline]
pub fn value(&self) -> &[u8] {
self.value.as_slice()
}
pub(crate) fn try_from_bytes(value: &[u8]) -> io::Result<Self> {
let (len, value) = value
.split_first_chunk::<{ mem::size_of::<u32>() }>()
.ok_or(io::ErrorKind::UnexpectedEof)?;
let len = u32::from_be_bytes(*len) as usize;
let (name, value) = value
.split_at_checked(len)
.ok_or(io::ErrorKind::UnexpectedEof)?;
let name = str::from_utf8(name).map_err(|_| io::ErrorKind::InvalidData)?;
let name = XattrName::new(name.to_owned())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let (len, value) = value
.split_first_chunk::<{ mem::size_of::<u32>() }>()
.ok_or(io::ErrorKind::UnexpectedEof)?;
let len = u32::from_be_bytes(*len) as usize;
let value = value.get(..len).ok_or(io::ErrorKind::UnexpectedEof)?;
let value = XattrValue::new(value.to_vec())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(Self { name, value })
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let name_bytes = self.name.as_str().as_bytes();
let value_bytes = self.value.as_slice();
let mut vec =
Vec::with_capacity(name_bytes.len() + value_bytes.len() + mem::size_of::<u32>() * 2);
vec.extend_from_slice(&(name_bytes.len() as u32).to_be_bytes());
vec.extend_from_slice(name_bytes);
vec.extend_from_slice(&(value_bytes.len() as u32).to_be_bytes());
vec.extend_from_slice(value_bytes);
vec
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[test]
fn xattr() {
let xattr = ExtendedAttribute::new(
XattrName::try_from("name").unwrap(),
XattrValue::try_from(b"value".as_slice()).unwrap(),
);
assert_eq!(
xattr,
ExtendedAttribute::try_from_bytes(&xattr.to_bytes()).unwrap()
);
}
#[test]
fn xattr_name_roundtrip() {
let name = XattrName::new("user.foo").unwrap();
assert_eq!(name.as_str(), "user.foo");
assert_eq!(String::from(name), "user.foo");
}
#[test]
fn xattr_name_parses_from_str() {
let name: XattrName = "user.comment".parse().unwrap();
assert_eq!(name.as_str(), "user.comment");
}
#[test]
fn xattr_value_accepts_arbitrary_bytes() {
let value = XattrValue::new(vec![0xFF, 0x00, 0x80]).unwrap();
assert_eq!(value.as_slice(), &[0xFF, 0x00, 0x80]);
}
#[test]
fn xattr_value_default_is_empty() {
assert!(XattrValue::default().is_empty());
}
}