use crate::{AudexError, Result};
use std::collections::HashMap;
use std::fmt;
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
const _DEFAULT_BUFFER_SIZE: usize = 2_usize.pow(20);
pub fn intround(value: f64) -> i64 {
if value.is_nan() || value.is_infinite() {
return 0;
}
let fract = value.fract();
let truncated = value.trunc();
let integral = if truncated <= i64::MIN as f64 {
i64::MIN
} else if truncated >= i64::MAX as f64 {
i64::MAX
} else {
truncated as i64
};
let epsilon = value.abs().max(1.0) * f64::EPSILON * 4.0;
if (fract.abs() - 0.5).abs() < epsilon {
if integral % 2 == 0 {
integral
} else {
if value > 0.0 {
integral.saturating_add(1)
} else {
integral.saturating_sub(1)
}
}
} else {
let rounded = value.round();
if rounded >= i64::MAX as f64 {
i64::MAX
} else if rounded <= i64::MIN as f64 {
i64::MIN
} else {
rounded as i64
}
}
}
#[derive(Debug, Clone)]
pub struct BitReaderError {
pub message: String,
}
impl fmt::Display for BitReaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for BitReaderError {}
impl From<std::io::Error> for BitReaderError {
fn from(err: std::io::Error) -> Self {
BitReaderError {
message: format!("IO error: {}", err),
}
}
}
#[derive(Debug, Clone)]
pub struct ValueError {
pub message: String,
}
impl fmt::Display for ValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for ValueError {}
pub struct BitReader<R: Read + Seek> {
reader: R,
buffer: u64,
bits_in_buffer: u8,
bits_read: u64,
initial_position: u64,
}
impl<R: Read + Seek> BitReader<R> {
pub fn new(mut reader: R) -> std::result::Result<Self, BitReaderError> {
let byte_pos = reader.stream_position().map_err(|e| BitReaderError {
message: format!("Unable to get initial position: {}", e),
})?;
let initial_position = byte_pos.checked_mul(8).ok_or_else(|| BitReaderError {
message: format!(
"stream position {} overflows when converted to bits",
byte_pos
),
})?;
Ok(Self {
reader,
buffer: 0,
bits_in_buffer: 0,
bits_read: 0,
initial_position,
})
}
pub fn bits(&mut self, count: i32) -> std::result::Result<i32, BitReaderError> {
if count < 0 {
return Err(BitReaderError {
message: "negative count".to_string(),
});
}
if count == 0 {
return Ok(0);
}
if count > 32 {
return Err(BitReaderError {
message: format!(
"bits() cannot read {} bits into i32 — use read_bits() for counts > 32",
count
),
});
}
if (count as u8) > self.bits_in_buffer {
let n_bytes = (count as u8 - self.bits_in_buffer).div_ceil(8) as usize;
let mut data = vec![0u8; n_bytes];
match self.reader.read_exact(&mut data) {
Ok(()) => {
for &b in &data {
self.buffer = (self.buffer << 8) | (b as u64);
}
self.bits_in_buffer = match self.bits_in_buffer.checked_add((n_bytes * 8) as u8)
{
Some(v) => v,
None => {
return Err(BitReaderError {
message: format!(
"internal buffer overflow: adding {} bits to {} would exceed u8 range",
n_bytes * 8,
self.bits_in_buffer
),
});
}
};
if self.bits_in_buffer > 64 {
return Err(BitReaderError {
message: format!(
"internal buffer overflow: {} bits exceeds 64-bit capacity",
self.bits_in_buffer
),
});
}
}
Err(_) => {
return Err(BitReaderError {
message: "not enough data".to_string(),
});
}
}
}
self.bits_in_buffer -= count as u8;
let mask = if count >= 64 {
u64::MAX
} else {
(1u64 << count) - 1
};
let value = ((self.buffer >> self.bits_in_buffer) & mask) as i32;
if self.bits_in_buffer > 0 && self.bits_in_buffer < 64 {
self.buffer &= (1u64 << self.bits_in_buffer) - 1;
} else if self.bits_in_buffer == 0 {
self.buffer = 0;
}
self.bits_read += count as u64;
Ok(value)
}
pub fn bytes(&mut self, count: i32) -> std::result::Result<Vec<u8>, BitReaderError> {
if count < 0 {
return Err(BitReaderError {
message: "negative count".to_string(),
});
}
if count == 0 {
return Ok(Vec::new());
}
const MAX_BYTE_COUNT: i32 = 0x0FFF_FFFF; if count > MAX_BYTE_COUNT {
return Err(BitReaderError {
message: format!("byte count {} exceeds maximum of {}", count, MAX_BYTE_COUNT),
});
}
let total = count as usize;
if self.is_aligned() {
const MAX_INITIAL_CAPACITY: usize = 64 * 1024;
if total <= MAX_INITIAL_CAPACITY {
let mut result = vec![0u8; total];
self.reader.read_exact(&mut result).map_err(|e| {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
BitReaderError {
message: "not enough data".to_string(),
}
} else {
BitReaderError::from(e)
}
})?;
self.bits_read += (total as u64) * 8;
Ok(result)
} else {
let mut result = Vec::with_capacity(MAX_INITIAL_CAPACITY);
let mut remaining = total;
while remaining > 0 {
let chunk_size = std::cmp::min(remaining, MAX_INITIAL_CAPACITY);
let mut chunk = vec![0u8; chunk_size];
self.reader.read_exact(&mut chunk).map_err(|e| {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
BitReaderError {
message: "not enough data".to_string(),
}
} else {
BitReaderError::from(e)
}
})?;
result.extend_from_slice(&chunk);
self.bits_read += (chunk_size as u64) * 8;
remaining -= chunk_size;
}
Ok(result)
}
} else {
let mut result = Vec::with_capacity(std::cmp::min(total, 64 * 1024));
for _ in 0..count {
let byte_value = self.bits(8)?;
result.push(byte_value as u8);
}
Ok(result)
}
}
pub fn skip(&mut self, count: i32) -> std::result::Result<(), BitReaderError> {
if count < 0 {
return Err(BitReaderError {
message: "negative count".to_string(),
});
}
if count == 0 {
return Ok(());
}
let mut remaining = count as u64;
if remaining <= self.bits_in_buffer as u64 {
self.bits_in_buffer -= remaining as u8;
if self.bits_in_buffer > 0 && self.bits_in_buffer < 64 {
self.buffer &= (1u64 << self.bits_in_buffer) - 1;
} else if self.bits_in_buffer == 0 {
self.buffer = 0;
}
self.bits_read += remaining;
return Ok(());
}
remaining -= self.bits_in_buffer as u64;
self.bits_read += self.bits_in_buffer as u64;
self.buffer = 0;
self.bits_in_buffer = 0;
let bytes_to_skip = remaining / 8;
if bytes_to_skip > 0 {
if bytes_to_skip > i64::MAX as u64 {
return Err(BitReaderError {
message: format!(
"skip too large: {} bytes exceeds seekable range",
bytes_to_skip
),
});
}
let pos_before = self.reader.stream_position().map_err(|e| BitReaderError {
message: format!("seek failed: {}", e),
})?;
let pos_after = self
.reader
.seek(std::io::SeekFrom::Current(bytes_to_skip as i64))
.map_err(|e| BitReaderError {
message: format!("seek failed: {}", e),
})?;
let actually_skipped = pos_after - pos_before;
if actually_skipped < bytes_to_skip {
return Err(BitReaderError {
message: "not enough data".to_string(),
});
}
let end_pos =
self.reader
.seek(std::io::SeekFrom::End(0))
.map_err(|e| BitReaderError {
message: format!("seek failed: {}", e),
})?;
if pos_after > end_pos {
self.reader.seek(std::io::SeekFrom::Start(end_pos)).ok();
return Err(BitReaderError {
message: "not enough data".to_string(),
});
}
self.reader
.seek(std::io::SeekFrom::Start(pos_after))
.map_err(|e| BitReaderError {
message: format!("seek failed: {}", e),
})?;
self.bits_read += actually_skipped * 8;
remaining -= actually_skipped * 8;
}
let remaining_bits = remaining % 8;
if remaining_bits > 0 {
let _ = self.bits(remaining_bits as i32)?;
}
Ok(())
}
pub fn align(&mut self) {
if self.bits_in_buffer > 0 {
let bits_to_skip = self.bits_in_buffer;
self.bits_read += bits_to_skip as u64;
self.buffer = 0;
self.bits_in_buffer = 0;
}
}
pub fn tell(&self) -> u64 {
self.initial_position + self.bits_read
}
pub fn get_position(&self) -> u64 {
self.bits_read
}
pub fn is_aligned(&self) -> bool {
self.bits_in_buffer == 0
}
pub fn read_bit(&mut self) -> std::result::Result<bool, BitReaderError> {
Ok(self.bits(1)? != 0)
}
pub fn read_bits(&mut self, count: u32) -> std::result::Result<u64, BitReaderError> {
if count > 64 {
return Err(BitReaderError {
message: "Cannot read more than 64 bits".to_string(),
});
}
if count <= 32 {
Ok((self.bits(count as i32)? as u32) as u64)
} else {
let high_bits = count - 32;
let high = (self.bits(high_bits as i32)? as u32) as u64;
let low = (self.bits(32)? as u32) as u64;
Ok((high << 32) | low)
}
}
pub fn skip_bits(&mut self, count: u32) -> std::result::Result<(), BitReaderError> {
if count > i32::MAX as u32 {
return Err(BitReaderError {
message: format!("skip_bits count {} exceeds maximum ({})", count, i32::MAX),
});
}
self.skip(count as i32)
}
pub fn peek_bits(&mut self, count: u32) -> std::result::Result<u64, BitReaderError> {
if count > 64 {
return Err(BitReaderError {
message: "Cannot peek more than 64 bits".to_string(),
});
}
let saved_buffer = self.buffer;
let saved_bits_in_buffer = self.bits_in_buffer;
let saved_bits_read = self.bits_read;
let saved_position = self.reader.stream_position().map_err(|e| BitReaderError {
message: format!("Unable to get position: {}", e),
})?;
let result = self.read_bits(count);
self.buffer = saved_buffer;
self.bits_in_buffer = saved_bits_in_buffer;
self.bits_read = saved_bits_read;
self.reader
.seek(SeekFrom::Start(saved_position))
.map_err(|e| BitReaderError {
message: format!("Unable to restore position: {}", e),
})?;
result
}
pub fn read_bytes_aligned(
&mut self,
count: usize,
) -> std::result::Result<Vec<u8>, BitReaderError> {
if !self.is_aligned() {
return Err(BitReaderError {
message: "Cannot read bytes from non-byte-aligned position".to_string(),
});
}
if count > i32::MAX as usize {
return Err(BitReaderError {
message: format!("Byte count {} exceeds maximum of {}", count, i32::MAX),
});
}
self.bytes(count as i32)
}
pub fn position(&self) -> u64 {
self.bits_read
}
pub fn seek_bits(&mut self, pos: u64) -> std::result::Result<(), BitReaderError> {
let byte_pos = pos / 8;
let bit_offset = (pos % 8) as u8;
let absolute_byte = (self.initial_position / 8)
.checked_add(byte_pos)
.ok_or_else(|| BitReaderError {
message: format!(
"Seek position overflow: initial_position / 8 ({}) + byte_pos ({}) exceeds u64 range",
self.initial_position / 8,
byte_pos
),
})?;
self.reader
.seek(SeekFrom::Start(absolute_byte))
.map_err(|e| BitReaderError {
message: format!("Seek failed: {}", e),
})?;
self.buffer = 0;
self.bits_in_buffer = 0;
self.bits_read = pos;
if bit_offset > 0 {
self.skip_bits(bit_offset as u32)?;
}
Ok(())
}
pub fn can_read(&mut self, count: u32) -> bool {
self.peek_bits(count).is_ok()
}
pub fn align_and_count(&mut self) -> u8 {
let bits_to_skip = if self.bits_in_buffer > 0 {
self.bits_in_buffer
} else {
0
};
self.align();
bits_to_skip
}
pub fn into_inner(self) -> R {
self.reader
}
}
#[derive(Debug, Clone, Copy)]
pub struct BitPaddedInt {
value: u64,
bits: u8,
bigendian: bool,
}
impl BitPaddedInt {
pub fn new(value: u64, bits: u8, bigendian: bool) -> Result<Self> {
if bits == 0 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be >= 1".to_string(),
));
}
if bits > 8 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be <= 8".to_string(),
));
}
Ok(Self {
value,
bits,
bigendian,
})
}
pub fn new_optional(
value: impl Into<u64>,
bits: Option<u8>,
bigendian: Option<bool>,
) -> Result<Self> {
Self::new(value.into(), bits.unwrap_or(7), bigendian.unwrap_or(true))
}
pub fn from_bytes(bytes: &[u8], bits: u8, bigendian: bool) -> Result<Self> {
if bits == 0 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be >= 1".to_string(),
));
}
if bits > 8 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be <= 8".to_string(),
));
}
let mask = (1u64 << bits) - 1;
let mut numeric_value = 0u64;
let mut shift = 0;
let byte_iter: Box<dyn Iterator<Item = u8>> = if bigendian {
Box::new(bytes.iter().rev().copied())
} else {
Box::new(bytes.iter().copied())
};
for byte in byte_iter {
if shift >= 64 {
return Err(AudexError::InvalidData(
"BitPaddedInt: input exceeds 64-bit capacity".to_string(),
));
}
let masked = byte as u64 & mask;
if shift + bits > 64 && (masked >> (64 - shift)) != 0 {
return Err(AudexError::InvalidData(
"BitPaddedInt: input exceeds 64-bit capacity".to_string(),
));
}
numeric_value |= masked << shift;
shift += bits;
}
Ok(Self {
value: numeric_value,
bits,
bigendian,
})
}
pub fn from_int(value: u64, bits: u8, bigendian: bool) -> Result<Self> {
if bits == 0 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be >= 1".to_string(),
));
}
if bits > 8 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be <= 8".to_string(),
));
}
Ok(Self {
value,
bits,
bigendian,
})
}
pub fn value(&self) -> u64 {
self.value
}
pub fn bits(&self) -> u8 {
self.bits
}
pub fn is_bigendian(&self) -> bool {
self.bigendian
}
pub fn as_bytes(&self, width: Option<usize>, minwidth: Option<usize>) -> Result<Vec<u8>> {
Self::to_bytes(
self.value,
self.bits,
self.bigendian,
width.unwrap_or(4),
minwidth.unwrap_or(4),
)
}
pub fn to_bytes(
value: u64,
bits: u8,
bigendian: bool,
width: usize,
minwidth: usize,
) -> Result<Vec<u8>> {
if bits == 0 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be >= 1".to_string(),
));
}
if bits > 8 {
return Err(AudexError::InvalidData(
"BitPaddedInt bits must be <= 8".to_string(),
));
}
const MAX_MINWIDTH: usize = 1024;
if minwidth > MAX_MINWIDTH {
return Err(AudexError::InvalidData(format!(
"BitPaddedInt minwidth {} exceeds maximum of {}",
minwidth, MAX_MINWIDTH
)));
}
let mask = (1u64 << bits) - 1;
let mut bytes = Vec::new();
let mut remaining_value = value;
if width == 0 {
while remaining_value > 0 || bytes.len() < minwidth {
bytes.push((remaining_value & mask) as u8);
if remaining_value > 0 {
remaining_value >>= bits;
}
}
} else {
for _ in 0..width {
bytes.push((remaining_value & mask) as u8);
remaining_value >>= bits;
}
if remaining_value > 0 {
return Err(AudexError::InvalidData(format!(
"Value too wide (>{} bytes)",
width
)));
}
}
if bigendian {
bytes.reverse();
}
Ok(bytes)
}
pub fn to_str(
value: u64,
bits: Option<u8>,
bigendian: Option<bool>,
width: Option<i32>,
minwidth: Option<usize>,
) -> Result<Vec<u8>> {
let bits = bits.unwrap_or(7);
let bigendian = bigendian.unwrap_or(true);
let width = match width {
Some(-1) | None => 0, Some(w) if w >= 0 => w as usize,
Some(w) => return Err(AudexError::InvalidData(format!("Invalid width: {}", w))),
};
let minwidth = minwidth.unwrap_or(4);
Self::to_bytes(value, bits, bigendian, width, minwidth)
}
pub fn has_valid_padding(input: &[u8], bits: u8) -> bool {
if bits == 0 || bits > 8 {
return false;
}
let mask = (((1u64 << (8 - bits)) - 1) << bits) as u8;
for &byte in input {
if byte & mask != 0 {
return false;
}
}
true
}
pub fn has_valid_padding_int(value: u64, bits: u8) -> bool {
if bits == 0 {
return false;
}
if bits >= 8 {
return true;
}
let mask = ((1u64 << (8 - bits)) - 1) << bits;
let mut remaining = value;
while remaining > 0 {
if (remaining & mask) != 0 {
return false;
}
remaining >>= 8;
}
true
}
}
impl From<BitPaddedInt> for u64 {
fn from(bpi: BitPaddedInt) -> Self {
bpi.value
}
}
impl BitPaddedInt {
pub fn checked_from_u64(value: u64) -> std::result::Result<Self, AudexError> {
const SYNCSAFE_4BYTE_MAX: u64 = (1 << 28) - 1;
if value > SYNCSAFE_4BYTE_MAX {
return Err(AudexError::InvalidData(format!(
"value {} exceeds maximum syncsafe integer ({}) for 4-byte encoding with bits=7",
value, SYNCSAFE_4BYTE_MAX
)));
}
Ok(Self {
value,
bits: 7,
bigendian: true,
})
}
pub fn checked_add(&self, rhs: &Self) -> Result<Self> {
if self.bits != rhs.bits || self.bigendian != rhs.bigendian {
return Err(AudexError::InvalidData(format!(
"Cannot add BitPaddedInt values with different encoding parameters \
(bits: {} vs {}, bigendian: {} vs {})",
self.bits, rhs.bits, self.bigendian, rhs.bigendian
)));
}
Ok(Self {
value: self.value.saturating_add(rhs.value),
bits: self.bits,
bigendian: self.bigendian,
})
}
pub fn checked_sub(&self, rhs: &Self) -> Result<Self> {
if self.bits != rhs.bits || self.bigendian != rhs.bigendian {
return Err(AudexError::InvalidData(format!(
"Cannot subtract BitPaddedInt values with different encoding parameters \
(bits: {} vs {}, bigendian: {} vs {})",
self.bits, rhs.bits, self.bigendian, rhs.bigendian
)));
}
Ok(Self {
value: self.value.saturating_sub(rhs.value),
bits: self.bits,
bigendian: self.bigendian,
})
}
}
impl std::fmt::Display for BitPaddedInt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)
}
}
impl PartialEq for BitPaddedInt {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl Eq for BitPaddedInt {}
impl PartialOrd for BitPaddedInt {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BitPaddedInt {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl std::ops::Add for BitPaddedInt {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
assert!(
self.bits == rhs.bits && self.bigendian == rhs.bigendian,
"Cannot add BitPaddedInt values with different encoding parameters (bits: {} vs {}, bigendian: {} vs {})",
self.bits,
rhs.bits,
self.bigendian,
rhs.bigendian
);
Self {
value: self.value.saturating_add(rhs.value),
bits: self.bits,
bigendian: self.bigendian,
}
}
}
impl std::ops::Sub for BitPaddedInt {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
assert!(
self.bits == rhs.bits && self.bigendian == rhs.bigendian,
"Cannot subtract BitPaddedInt values with different encoding parameters (bits: {} vs {}, bigendian: {} vs {})",
self.bits,
rhs.bits,
self.bigendian,
rhs.bigendian
);
Self {
value: self.value.saturating_sub(rhs.value),
bits: self.bits,
bigendian: self.bigendian,
}
}
}
pub mod bom {
pub const UTF8: &[u8] = &[0xEF, 0xBB, 0xBF];
pub const UTF16LE: &[u8] = &[0xFF, 0xFE];
pub const UTF16BE: &[u8] = &[0xFE, 0xFF];
pub const UTF32LE: &[u8] = &[0xFF, 0xFE, 0x00, 0x00];
pub const UTF32BE: &[u8] = &[0x00, 0x00, 0xFE, 0xFF];
}
pub fn detect_bom(data: &[u8]) -> Option<(String, usize)> {
if data.len() >= 4 && data.starts_with(bom::UTF32LE) {
Some(("utf-32le".to_string(), 4))
} else if data.len() >= 4 && data.starts_with(bom::UTF32BE) {
Some(("utf-32be".to_string(), 4))
} else if data.len() >= 3 && data.starts_with(bom::UTF8) {
Some(("utf-8".to_string(), 3))
} else if data.len() >= 2 && data.starts_with(bom::UTF16LE) {
Some(("utf-16le".to_string(), 2))
} else if data.len() >= 2 && data.starts_with(bom::UTF16BE) {
Some(("utf-16be".to_string(), 2))
} else {
None
}
}
pub fn decode_text(
data: &[u8],
encoding: Option<&str>,
errors: &str,
remove_bom_flag: bool,
) -> Result<(String, String, bool)> {
trace_event!(
data_len = data.len(),
encoding = ?encoding,
"decoding text"
);
if data.is_empty() {
return Ok((String::new(), "utf-8".to_string(), false));
}
let (clean_data, detected_encoding_from_bom): (&[u8], Option<String>) = if remove_bom_flag {
if let Some((enc, bom_len)) = detect_bom(data) {
(&data[bom_len..], Some(enc))
} else {
(data, None)
}
} else {
(data, None)
};
let final_encoding = match (encoding, &detected_encoding_from_bom) {
(_, Some(detected)) => detected.clone(), (Some(enc), None) => enc.to_string(), (None, None) => "utf-8".to_string(), };
let bom_was_removed = detected_encoding_from_bom.is_some() && remove_bom_flag;
let decoded = decode_bytes_with_encoding(clean_data, &final_encoding, errors)?;
Ok((decoded, final_encoding, bom_was_removed))
}
fn decode_bytes_with_encoding(data: &[u8], encoding: &str, errors: &str) -> Result<String> {
let encoding_lower = encoding.to_lowercase();
let encoding_str = encoding_lower.as_str();
match encoding_str {
"utf-8" => match errors {
"strict" => match String::from_utf8(data.to_vec()) {
Ok(s) => Ok(s),
Err(e) => {
warn_event!(encoding = "utf-8", %e, "text decode failed");
Err(AudexError::InvalidData(format!(
"UTF-8 decode error: {}",
e
)))
}
},
"ignore" | "replace" => Ok(String::from_utf8_lossy(data).into_owned()),
_ => Err(AudexError::InvalidData(format!(
"Unsupported error mode: {}",
errors
))),
},
"utf-16le" => decode_utf16_bytes(data, true, errors),
"utf-16be" => decode_utf16_bytes(data, false, errors),
"utf-32le" => decode_utf32_bytes(data, true, errors),
"utf-32be" => decode_utf32_bytes(data, false, errors),
"ascii" => match errors {
"strict" => {
for &byte in data {
if byte > 127 {
return Err(AudexError::InvalidData(format!(
"Non-ASCII byte: 0x{:02x}",
byte
)));
}
}
Ok(String::from_utf8(data.to_vec())
.expect("validated as ASCII; all bytes are <= 127"))
}
"ignore" => {
let filtered: Vec<u8> = data.iter().filter(|&&b| b <= 127).copied().collect();
Ok(String::from_utf8(filtered)
.expect("validated as ASCII; non-ASCII bytes were filtered out"))
}
"replace" => {
let replaced: Vec<u8> = data
.iter()
.map(|&b| if b <= 127 { b } else { b'?' })
.collect();
Ok(String::from_utf8(replaced)
.expect("validated as ASCII; non-ASCII bytes were replaced with '?'"))
}
_ => Err(AudexError::InvalidData(format!(
"Unsupported error mode: {}",
errors
))),
},
"latin-1" | "iso-8859-1" => {
let decoded: String = data.iter().map(|&b| b as char).collect();
Ok(decoded)
}
_ => Err(AudexError::InvalidData(format!(
"Unsupported encoding: {}",
encoding
))),
}
}
fn decode_utf16_bytes(data: &[u8], is_little_endian: bool, errors: &str) -> Result<String> {
if data.len() % 2 != 0 {
return match errors {
"strict" => Err(AudexError::InvalidData(
"UTF-16 data length not aligned to 2-byte boundary".to_string(),
)),
"ignore" => {
let aligned_data = &data[..data.len() - 1];
decode_utf16_bytes(aligned_data, is_little_endian, "ignore")
}
"replace" => {
let aligned_data = &data[..data.len() - 1];
let mut result = decode_utf16_bytes(aligned_data, is_little_endian, "replace")?;
result.push('\u{FFFD}'); Ok(result)
}
_ => Err(AudexError::InvalidData(format!(
"Unsupported error mode: {}",
errors
))),
};
}
let mut utf16_values = Vec::new();
for i in (0..data.len()).step_by(2) {
let word = if is_little_endian {
u16::from_le_bytes([data[i], data[i + 1]])
} else {
u16::from_be_bytes([data[i], data[i + 1]])
};
utf16_values.push(word);
}
match errors {
"strict" => match String::from_utf16(&utf16_values) {
Ok(s) => Ok(s),
Err(e) => Err(AudexError::InvalidData(format!(
"UTF-16 decode error: {}",
e
))),
},
"ignore" | "replace" => Ok(String::from_utf16_lossy(&utf16_values)),
_ => Err(AudexError::InvalidData(format!(
"Unsupported error mode: {}",
errors
))),
}
}
fn decode_utf32_bytes(data: &[u8], is_little_endian: bool, errors: &str) -> Result<String> {
if data.len() % 4 != 0 {
return match errors {
"strict" => Err(AudexError::InvalidData(
"UTF-32 data length not aligned to 4-byte boundary".to_string(),
)),
"ignore" => {
let aligned_len = (data.len() / 4) * 4;
let aligned_data = &data[..aligned_len];
decode_utf32_bytes(aligned_data, is_little_endian, "ignore")
}
"replace" => {
let aligned_len = (data.len() / 4) * 4;
let aligned_data = &data[..aligned_len];
let mut result = decode_utf32_bytes(aligned_data, is_little_endian, "replace")?;
result.push('\u{FFFD}'); Ok(result)
}
_ => Err(AudexError::InvalidData(format!(
"Unsupported error mode: {}",
errors
))),
};
}
let mut result = String::new();
for i in (0..data.len()).step_by(4) {
let code_point = if is_little_endian {
u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]])
} else {
u32::from_be_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]])
};
match char::from_u32(code_point) {
Some(ch) => result.push(ch),
None => {
match errors {
"strict" => {
return Err(AudexError::InvalidData(format!(
"Invalid UTF-32 code point: 0x{:08X}",
code_point
)));
}
"ignore" => {
}
"replace" => {
result.push('\u{FFFD}'); }
_ => {
return Err(AudexError::InvalidData(format!(
"Unsupported error mode: {}",
errors
)));
}
}
}
}
}
Ok(result)
}
pub fn dict_match<V: Clone>(d: &HashMap<String, V>, key: &str, default: Option<V>) -> Option<V> {
if let Some(value) = d.get(key) {
return Some(value.clone());
}
for (pattern, value) in d.iter() {
if fnmatch_recursive(pattern, key) {
return Some(value.clone());
}
}
default
}
fn fnmatch_recursive(pattern: &str, text: &str) -> bool {
let pat: Vec<char> = pattern.chars().collect();
let txt: Vec<char> = text.chars().collect();
let mut pi = 0; let mut ti = 0;
let mut star_pi: Option<usize> = None;
let mut star_ti: usize = 0;
while ti < txt.len() {
if pi < pat.len() {
match pat[pi] {
'*' => {
while pi < pat.len() && pat[pi] == '*' {
pi += 1;
}
if pi == pat.len() {
return true;
}
star_pi = Some(pi);
star_ti = ti;
continue;
}
'?' => {
pi += 1;
ti += 1;
continue;
}
'[' => {
let mut end = pi + 1;
let mut bracket_content = String::new();
let mut closed = false;
while end < pat.len() {
if pat[end] == ']' && !bracket_content.is_empty() {
closed = true;
break;
}
bracket_content.push(pat[end]);
end += 1;
}
if !closed {
if txt[ti] == '[' {
pi += 1;
ti += 1;
continue;
}
} else if match_bracket_expression(&bracket_content, txt[ti]) {
pi = end + 1; ti += 1;
continue;
}
if let Some(sp) = star_pi {
pi = sp;
star_ti += 1;
ti = star_ti;
continue;
}
return false;
}
'\\' => {
let expected = if pi + 1 < pat.len() {
pi += 1;
pat[pi]
} else {
'\\'
};
if txt[ti] == expected {
pi += 1;
ti += 1;
continue;
}
}
c => {
if txt[ti] == c {
pi += 1;
ti += 1;
continue;
}
}
}
}
if let Some(sp) = star_pi {
pi = sp;
star_ti += 1;
ti = star_ti;
} else {
return false;
}
}
while pi < pat.len() && pat[pi] == '*' {
pi += 1;
}
pi == pat.len()
}
fn match_bracket_expression(bracket_content: &str, ch: char) -> bool {
if bracket_content.is_empty() {
return false;
}
let negated = bracket_content.starts_with('!') || bracket_content.starts_with('^');
let content = if negated {
&bracket_content[1..]
} else {
bracket_content
};
let matches = match_bracket_content(content, ch);
if negated { !matches } else { matches }
}
fn match_bracket_content(content: &str, ch: char) -> bool {
let mut chars = content.chars().peekable();
while let Some(c) = chars.next() {
if chars.peek() == Some(&'-') {
chars.next(); if let Some(end) = chars.next() {
if ch >= c && ch <= end {
return true;
}
} else {
if ch == c || ch == '-' {
return true;
}
}
} else {
if ch == c {
return true;
}
}
}
false
}
pub fn read_full(fileobj: &mut File, size: usize) -> Result<Vec<u8>> {
let current_pos = fileobj
.stream_position()
.map_err(|e| AudexError::InvalidData(format!("Cannot get file position: {}", e)))?;
let end_pos = fileobj
.seek(SeekFrom::End(0))
.map_err(|e| AudexError::InvalidData(format!("Cannot seek to end: {}", e)))?;
fileobj
.seek(SeekFrom::Start(current_pos))
.map_err(|e| AudexError::InvalidData(format!("Cannot restore file position: {}", e)))?;
let remaining = end_pos.saturating_sub(current_pos);
if (size as u64) > remaining {
return Err(AudexError::InvalidData(format!(
"Cannot read {} bytes: only {} bytes remaining in file",
size, remaining
)));
}
let mut buffer = vec![0u8; size];
fileobj
.read_exact(&mut buffer)
.map_err(|e| AudexError::InvalidData(format!("Cannot read {} bytes: {}", size, e)))?;
Ok(buffer)
}
pub fn seek_end(fileobj: &mut File, offset: u64) -> Result<u64> {
let file_size = get_size(fileobj)?;
if offset > file_size {
let pos = fileobj
.seek(SeekFrom::Start(0))
.map_err(|e| AudexError::InvalidData(format!("Cannot seek to start: {}", e)))?;
return Ok(pos);
}
let neg_offset = i64::try_from(offset).map(|v| -v).map_err(|_| {
AudexError::InvalidData(format!("seek_end: offset {} exceeds i64 range", offset))
})?;
let pos = fileobj
.seek(SeekFrom::End(neg_offset))
.map_err(|e| AudexError::InvalidData(format!("Cannot seek to end-{}: {}", offset, e)))?;
Ok(pos)
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FileInput {
Path(PathBuf),
File(File),
Memory(Vec<u8>),
}
impl FileInput {
pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
FileInput::Path(path.as_ref().to_path_buf())
}
pub fn is_path(&self) -> bool {
matches!(self, FileInput::Path(_))
}
pub fn is_file(&self) -> bool {
matches!(self, FileInput::File(_))
}
pub fn is_memory(&self) -> bool {
matches!(self, FileInput::Memory(_))
}
}
#[derive(Debug, Clone)]
pub struct FallbackOptions {
pub enable_memory_fallback: bool,
pub initial_buffer_size: usize,
pub max_memory_size: u64,
}
impl Default for FallbackOptions {
fn default() -> Self {
Self {
enable_memory_fallback: true,
initial_buffer_size: 64 * 1024, max_memory_size: 100 * 1024 * 1024, }
}
}
pub fn openfile(
path_or_file: FileInput,
options: &LoadFileOptions,
fallback_options: &FallbackOptions,
) -> Result<AnyFileThing> {
trace_event!(writable = options.writable, "opening file");
match path_or_file {
FileInput::Path(path_buf) => openfile_from_path(path_buf, options, fallback_options),
FileInput::File(file) => openfile_from_handle(file, options, fallback_options),
FileInput::Memory(data) => openfile_from_memory(data, options),
}
}
fn openfile_from_path(
path: PathBuf,
options: &LoadFileOptions,
fallback_options: &FallbackOptions,
) -> Result<AnyFileThing> {
trace_event!(
path = %path.display(),
writable = options.writable,
"opening file from path"
);
verify_filename(&path)?;
let mut open_options = OpenOptions::new();
if options.writable {
open_options.read(true).write(true);
if options.create {
open_options.create(true);
}
} else {
open_options.read(true);
}
match open_options.open(&path) {
Ok(mut file) => {
if let Err(e) = verify_fileobj(&mut file, options.writable) {
if fallback_options.enable_memory_fallback && !options.writable {
return try_memory_fallback(&mut file, &path, fallback_options);
} else {
return Err(e);
}
}
let file_thing = FileThing::new(file, Some(path.to_path_buf()), path.to_path_buf());
Ok(AnyFileThing::File(file_thing))
}
Err(e) => {
if fallback_options.enable_memory_fallback && !options.writable && path.exists() {
let max = fallback_options.max_memory_size;
if let Ok(meta) = std::fs::metadata(&path) {
if meta.len() > max {
warn_event!(
path = %path.display(),
size = meta.len(),
limit = max,
"file exceeds memory fallback limit"
);
return Err(AudexError::InvalidData(format!(
"File '{}' too large for memory fallback: {} bytes > {} byte cap",
path.display(),
meta.len(),
max
)));
}
}
if let Ok(mut fh) = std::fs::File::open(&path) {
let mut buffer = Vec::new();
let bound = max.saturating_add(1);
if let Ok(n) = Read::by_ref(&mut fh).take(bound).read_to_end(&mut buffer) {
if (n as u64) <= max {
let cursor = Cursor::new(buffer);
let file_thing = FileThing::new(
cursor,
Some(path.to_path_buf()),
path.to_path_buf(),
);
return Ok(AnyFileThing::Memory(file_thing));
}
}
}
}
warn_event!(
path = %path.display(),
error = %e,
"failed to open file"
);
Err(AudexError::InvalidData(format!(
"Cannot open file '{}': {}",
path.display(),
e
)))
}
}
}
fn openfile_from_handle(
mut file: File,
options: &LoadFileOptions,
fallback_options: &FallbackOptions,
) -> Result<AnyFileThing> {
if let Err(e) = verify_fileobj(&mut file, options.writable) {
if fallback_options.enable_memory_fallback && !options.writable {
let name = fileobj_name(&file);
let path = if name.is_empty() {
PathBuf::new()
} else {
PathBuf::from(name)
};
return try_memory_fallback(&mut file, &path, fallback_options);
} else {
return Err(e);
}
}
let filename_str = fileobj_name(&file);
let filename = if filename_str.is_empty() {
None
} else {
Some(PathBuf::from(&filename_str))
};
let display_name = if filename_str.is_empty() {
PathBuf::from("<file object>")
} else {
PathBuf::from(filename_str)
};
let file_thing = FileThing::new(file, filename, display_name);
Ok(AnyFileThing::File(file_thing))
}
fn openfile_from_memory(data: Vec<u8>, options: &LoadFileOptions) -> Result<AnyFileThing> {
if options.writable {
let cursor = Cursor::new(data);
let file_thing = FileThing::new(cursor, None, PathBuf::from("<memory>"));
Ok(AnyFileThing::Memory(file_thing))
} else {
let cursor = Cursor::new(data);
let file_thing = FileThing::new(cursor, None, PathBuf::from("<memory>"));
Ok(AnyFileThing::Memory(file_thing))
}
}
fn try_memory_fallback(
file: &mut File,
path: &Path,
fallback_options: &FallbackOptions,
) -> Result<AnyFileThing> {
let max = fallback_options.max_memory_size;
if let Ok(size) = get_size(file) {
if size > max {
return Err(AudexError::InvalidData(format!(
"File '{}' too large for memory fallback: {} bytes > {} bytes",
path.display(),
size,
max
)));
}
}
file.seek(SeekFrom::Start(0))
.map_err(|e| AudexError::InvalidData(format!("Cannot seek to file start: {}", e)))?;
let mut buffer = Vec::new();
let bound = max.saturating_add(1); let bytes_read = Read::by_ref(file)
.take(bound)
.read_to_end(&mut buffer)
.map_err(|e| AudexError::InvalidData(format!("Cannot read file into memory: {}", e)))?;
if bytes_read as u64 > max {
return Err(AudexError::InvalidData(format!(
"File '{}' exceeds memory fallback limit: read {} bytes > {} byte cap",
path.display(),
bytes_read,
max
)));
}
let cursor = Cursor::new(buffer);
let file_thing = FileThing::new(cursor, Some(path.to_path_buf()), path.to_path_buf());
Ok(AnyFileThing::Memory(file_thing))
}
pub fn openfile_simple(path_or_file: FileInput, options: &LoadFileOptions) -> Result<AnyFileThing> {
openfile(path_or_file, options, &FallbackOptions::default())
}
pub fn is_windows_reserved_name(name: &str) -> bool {
let upper = name.to_ascii_uppercase();
matches!(
upper.as_str(),
"CON"
| "PRN"
| "AUX"
| "NUL"
| "COM1"
| "COM2"
| "COM3"
| "COM4"
| "COM5"
| "COM6"
| "COM7"
| "COM8"
| "COM9"
| "LPT1"
| "LPT2"
| "LPT3"
| "LPT4"
| "LPT5"
| "LPT6"
| "LPT7"
| "LPT8"
| "LPT9"
)
}
pub fn is_fileobj<T: AsRef<Path> + ?Sized>(_obj: &T) -> bool {
false
}
pub fn is_fileobj_file(_fileobj: &std::fs::File) -> bool {
true
}
pub fn fileobj_name(_fileobj: &File) -> String {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = _fileobj.as_raw_fd();
if let Ok(path) = std::fs::read_link(format!("/proc/self/fd/{}", fd)) {
let path_str = path.to_string_lossy();
if path_str.starts_with("pipe:")
|| path_str.starts_with("socket:")
|| path_str == "/dev/null"
|| path_str == "/dev/stdin"
|| path_str == "/dev/stdout"
|| path_str == "/dev/stderr"
{
return String::new(); }
return path.to_string_lossy().to_string();
}
String::new()
}
#[cfg(windows)]
{
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::{HANDLE, MAX_PATH};
use windows_sys::Win32::Storage::FileSystem::{
FILE_NAME_NORMALIZED, GetFinalPathNameByHandleW, VOLUME_NAME_DOS,
};
let handle = _fileobj.as_raw_handle() as HANDLE;
unsafe {
let mut buffer: Vec<u16> = vec![0; MAX_PATH as usize + 1];
let mut required_size = GetFinalPathNameByHandleW(
handle,
buffer.as_mut_ptr(),
buffer.len() as u32,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
);
const MAX_PATH_BUFFER: u32 = 32768;
if required_size > buffer.len() as u32 {
if required_size > MAX_PATH_BUFFER {
return String::new();
}
buffer.resize(required_size as usize + 1, 0);
let actual_len = GetFinalPathNameByHandleW(
handle,
buffer.as_mut_ptr(),
buffer.len() as u32,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
);
if actual_len == 0 || actual_len > buffer.len() as u32 {
return String::new();
}
required_size = actual_len;
}
if required_size == 0 || required_size > buffer.len() as u32 {
return String::new();
}
buffer.truncate(required_size as usize);
let os_string = OsString::from_wide(&buffer);
let path_str = os_string.to_string_lossy();
let cleaned_path = path_str.strip_prefix("\\\\?\\").unwrap_or(&path_str);
if cleaned_path.starts_with("pipe:")
|| cleaned_path.starts_with("socket:")
|| is_windows_reserved_name(cleaned_path)
{
return String::new();
}
cleaned_path.to_string()
}
}
#[cfg(not(any(unix, windows)))]
{
String::new()
}
}
#[macro_export]
macro_rules! int_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident: $int_type:ty {
$(
$(#[$field_meta:meta])*
$variant:ident = $value:expr
),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
$vis struct $name($int_type);
impl $name {
$(
$(#[$field_meta])*
pub const $variant: Self = Self($value);
)*
}
impl From<$int_type> for $name {
fn from(value: $int_type) -> Self {
Self(value)
}
}
impl From<$name> for $int_type {
fn from(value: $name) -> $int_type {
value.0
}
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
$($value => f.write_str(concat!(stringify!($name), "::", stringify!($variant))),)*
other => write!(f, "{}({})", stringify!($name), other),
}
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
$($value => f.write_str(stringify!($variant)),)*
other => write!(f, "{}", other),
}
}
}
};
}
#[macro_export]
macro_rules! flags {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident: $int_type:ty {
$(
$(#[$field_meta:meta])*
$variant:ident = $value:expr
),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
$vis struct $name {
bits: $int_type,
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
let mut has_any = false;
f.write_str(stringify!($name))?;
f.write_str("(")?;
$(
if self.bits & $value == $value && $value != 0 {
if !first {
f.write_str(" | ")?;
}
f.write_str(stringify!($variant))?;
first = false;
has_any = true;
}
)*
if !has_any {
f.write_str("EMPTY")?;
}
f.write_str(")")
}
}
impl $name {
$(
$(#[$field_meta])*
pub const $variant: Self = Self { bits: $value };
)*
pub const fn from_bits(bits: $int_type) -> Option<Self> {
const ALL_BITS: $int_type = $($value)|*;
if (bits & !ALL_BITS) == 0 {
Some(Self { bits })
} else {
None
}
}
pub const fn from_bits_truncate(bits: $int_type) -> Self {
const ALL_BITS: $int_type = $($value)|*;
Self { bits: bits & ALL_BITS }
}
pub const fn bits(&self) -> $int_type {
self.bits
}
pub const fn contains(&self, other: Self) -> bool {
(self.bits & other.bits) == other.bits
}
pub const fn is_empty(&self) -> bool {
self.bits == 0
}
pub const fn is_all(&self) -> bool {
const ALL_BITS: $int_type = $($value)|*;
self.bits == ALL_BITS
}
pub const fn empty() -> Self {
Self { bits: 0 }
}
pub const fn all() -> Self {
const ALL_BITS: $int_type = $($value)|*;
Self { bits: ALL_BITS }
}
}
impl std::ops::BitOr for $name {
type Output = Self;
fn bitor(self, other: Self) -> Self {
Self { bits: self.bits | other.bits }
}
}
impl std::ops::BitOrAssign for $name {
fn bitor_assign(&mut self, other: Self) {
self.bits |= other.bits;
}
}
impl std::ops::BitAnd for $name {
type Output = Self;
fn bitand(self, other: Self) -> Self {
Self { bits: self.bits & other.bits }
}
}
impl std::ops::BitAndAssign for $name {
fn bitand_assign(&mut self, other: Self) {
self.bits &= other.bits;
}
}
impl std::ops::BitXor for $name {
type Output = Self;
fn bitxor(self, other: Self) -> Self {
Self { bits: self.bits ^ other.bits }
}
}
impl std::ops::BitXorAssign for $name {
fn bitxor_assign(&mut self, other: Self) {
self.bits ^= other.bits;
}
}
impl std::ops::Not for $name {
type Output = Self;
fn not(self) -> Self {
const ALL_BITS: $int_type = $($value)|*;
Self { bits: (!self.bits) & ALL_BITS }
}
}
impl std::ops::Sub for $name {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self { bits: self.bits & !other.bits }
}
}
impl std::ops::SubAssign for $name {
fn sub_assign(&mut self, other: Self) {
self.bits &= !other.bits;
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{:x}", self.bits)
}
}
impl From<$name> for $int_type {
fn from(flags: $name) -> $int_type {
flags.bits
}
}
};
}
pub const DEFAULT_BUFFER_SIZE: usize = _DEFAULT_BUFFER_SIZE;
pub fn resize_file<F>(file: &mut F, diff: i64, buffer_size: Option<usize>) -> Result<()>
where
F: Read + Write + Seek + 'static,
{
if diff == 0 {
return Ok(());
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let original_pos = file.stream_position()?;
let file_size = file.seek(SeekFrom::End(0))?;
file.seek(SeekFrom::Start(original_pos))?;
if diff > 0 {
let original_size = file.seek(SeekFrom::End(0))?;
let mut remaining = diff as u64;
let buf_size_u64 = buffer_size as u64;
let initial_chunk = std::cmp::min(buf_size_u64, remaining) as usize;
let mut buffer = vec![0u8; initial_chunk];
let write_result: Result<()> = (|| {
while remaining > 0 {
let chunk_size = std::cmp::min(buf_size_u64, remaining);
buffer.resize(chunk_size as usize, 0);
file.write_all(&buffer)?;
remaining -= chunk_size;
}
Ok(())
})();
if let Err(e) = write_result {
let mut rollback_attempted = false;
if let Some(file_ref) = (file as &mut dyn std::any::Any).downcast_mut::<File>() {
rollback_attempted = true;
let _ = file_ref.set_len(original_size);
} else if let Some(cursor_ref) =
(file as &mut dyn std::any::Any).downcast_mut::<std::io::Cursor<Vec<u8>>>()
{
rollback_attempted = true;
if let Ok(size) = usize::try_from(original_size) {
cursor_ref.get_mut().truncate(size);
}
}
if !rollback_attempted {
return Err(AudexError::InvalidOperation(format!(
"failed to extend writer and could not roll back partial changes: {}",
e
)));
}
return Err(e);
}
} else {
let shrink = diff.unsigned_abs();
if shrink > file_size {
return Err(AudexError::InvalidData(
"Invalid size: shrink amount exceeds file size".to_string(),
));
}
let new_size = file_size - shrink;
if let Some(file_ref) = (file as &mut dyn std::any::Any).downcast_mut::<File>() {
file_ref.set_len(new_size)?;
} else if let Some(cursor_ref) =
(file as &mut dyn std::any::Any).downcast_mut::<std::io::Cursor<Vec<u8>>>()
{
let safe_size = usize::try_from(new_size).map_err(|_| {
AudexError::InvalidData(format!(
"Truncation target size ({} bytes) exceeds addressable range",
new_size
))
})?;
cursor_ref.get_mut().truncate(safe_size);
} else {
return Err(AudexError::Unsupported(
"File truncation not supported for this file type".to_string(),
));
}
let restore_pos = std::cmp::min(original_pos, new_size);
file.seek(SeekFrom::Start(restore_pos))?;
}
Ok(())
}
pub fn truncate_writer<W>(writer: &mut W, new_size: u64) -> Result<()>
where
W: Read + Write + Seek + 'static,
{
let current_end = writer.seek(SeekFrom::End(0))?;
if new_size >= current_end {
return Ok(()); }
let writer_any = writer as &mut dyn std::any::Any;
if let Some(file_ref) = writer_any.downcast_mut::<File>() {
file_ref.set_len(new_size)?;
file_ref.seek(SeekFrom::Start(new_size))?;
return Ok(());
}
if let Some(cursor_ref) = writer_any.downcast_mut::<std::io::Cursor<Vec<u8>>>() {
let safe_size = usize::try_from(new_size).map_err(|_| {
crate::AudexError::InvalidData(format!(
"truncation size {} exceeds platform addressable range",
new_size
))
})?;
cursor_ref.get_mut().truncate(safe_size);
std::io::Seek::seek(cursor_ref, SeekFrom::Start(new_size))?;
return Ok(());
}
zero_trailing_bytes(writer, new_size, current_end)
}
pub fn truncate_writer_dyn(writer: &mut dyn crate::ReadWriteSeek, new_size: u64) -> Result<()> {
let current_end = writer.seek(SeekFrom::End(0))?;
if new_size >= current_end {
return Ok(()); }
zero_trailing_bytes(writer, new_size, current_end)
}
pub fn read_all_from_writer_limited(
writer: &mut dyn crate::ReadWriteSeek,
context: &str,
) -> Result<Vec<u8>> {
let file_size = writer.seek(SeekFrom::End(0))?;
const MAX_FULL_WRITER_READ: u64 = 512 * 1024 * 1024;
let max_read_size = MAX_FULL_WRITER_READ;
if file_size > max_read_size {
return Err(crate::AudexError::InvalidData(format!(
"File size ({} bytes) exceeds maximum for {} ({} bytes)",
file_size, context, max_read_size
)));
}
writer.seek(SeekFrom::Start(0))?;
let mut data = Vec::new();
writer.read_to_end(&mut data)?;
Ok(data)
}
fn zero_trailing_bytes<W: Write + Seek + ?Sized>(
writer: &mut W,
new_size: u64,
current_end: u64,
) -> Result<()> {
writer.seek(SeekFrom::Start(new_size))?;
let stale_len = current_end - new_size;
const CHUNK_SIZE: usize = 64 * 1024; let zeros = [0u8; CHUNK_SIZE];
let mut remaining = stale_len;
while remaining > 0 {
let to_write = (remaining as usize).min(CHUNK_SIZE);
writer.write_all(&zeros[..to_write])?;
remaining -= to_write as u64;
}
writer.seek(SeekFrom::Start(new_size))?;
Ok(())
}
pub fn move_bytes<F>(
file: &mut F,
dest: u64,
src: u64,
count: u64,
buffer_size: Option<usize>,
) -> Result<()>
where
F: Read + Write + Seek,
{
trace_event!(from = src, to = dest, size = count, "moving bytes");
if count == 0 {
return Ok(());
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_pos = file.stream_position()?;
let file_size = file.seek(SeekFrom::End(0))?;
file.seek(SeekFrom::Start(current_pos))?;
if src.saturating_add(count) > file_size {
return Err(AudexError::InvalidData(
"Area outside of file: source range exceeds file size".to_string(),
));
}
if dest > file_size || dest.saturating_add(count) > file_size {
return Err(AudexError::InvalidData(
"Area outside of file: destination range exceeds file size".to_string(),
));
}
if src == dest {
return Ok(());
}
let chunk_size = std::cmp::min(buffer_size as u64, count) as usize;
let mut buffer = vec![0u8; chunk_size];
if src < dest && dest < src + count {
let mut remaining = count;
while remaining > 0 {
let current_chunk = std::cmp::min(chunk_size as u64, remaining) as usize;
let src_offset = src + remaining - current_chunk as u64;
let dest_offset = dest + remaining - current_chunk as u64;
file.seek(SeekFrom::Start(src_offset))?;
buffer.resize(current_chunk, 0);
file.read_exact(&mut buffer[..current_chunk])?;
file.seek(SeekFrom::Start(dest_offset))?;
file.write_all(&buffer[..current_chunk])?;
remaining -= current_chunk as u64;
}
} else {
let mut remaining = count;
let mut offset = 0u64;
while remaining > 0 {
let current_chunk = std::cmp::min(chunk_size as u64, remaining) as usize;
file.seek(SeekFrom::Start(src + offset))?;
buffer.resize(current_chunk, 0);
file.read_exact(&mut buffer[..current_chunk])?;
file.seek(SeekFrom::Start(dest + offset))?;
file.write_all(&buffer[..current_chunk])?;
offset += current_chunk as u64;
remaining -= current_chunk as u64;
}
}
file.flush()?;
Ok(())
}
pub fn insert_bytes<F>(
file: &mut F,
size: u64,
offset: u64,
buffer_size: Option<usize>,
) -> Result<()>
where
F: Read + Write + Seek + 'static,
{
trace_event!(offset = offset, size = size, "inserting bytes");
if size == 0 {
return Ok(());
}
let size_i64 = i64::try_from(size).map_err(|_| {
AudexError::InvalidData(format!(
"Insert size {} exceeds maximum supported value ({})",
size,
i64::MAX
))
})?;
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_pos = file.stream_position()?;
let file_size = file.seek(SeekFrom::End(0))?;
if offset > file_size {
return Err(AudexError::InvalidData(format!(
"Offset beyond file size: {} > {}",
offset, file_size
)));
}
if offset == file_size {
resize_file(file, size_i64, Some(buffer_size))?;
file.seek(SeekFrom::Start(current_pos))?;
return Ok(());
}
resize_file(file, size_i64, Some(buffer_size))?;
let dest_offset = offset.checked_add(size).ok_or_else(|| {
AudexError::InvalidData(format!(
"Destination offset overflow: {} + {} exceeds u64 range",
offset, size
))
})?;
let bytes_to_move = file_size - offset;
if bytes_to_move > 0 {
move_bytes(file, dest_offset, offset, bytes_to_move, Some(buffer_size))?;
}
file.seek(SeekFrom::Start(offset))?;
let mut remaining = size;
let mut buffer = vec![
0u8;
std::cmp::min(
buffer_size,
usize::try_from(remaining).unwrap_or(usize::MAX)
)
];
while remaining > 0 {
let chunk_size = std::cmp::min(buffer_size as u64, remaining) as usize;
buffer.resize(chunk_size, 0);
file.write_all(&buffer)?;
remaining -= chunk_size as u64;
}
file.flush()?;
file.seek(SeekFrom::Start(current_pos))?;
Ok(())
}
pub fn delete_bytes<F>(
file: &mut F,
size: u64,
offset: u64,
buffer_size: Option<usize>,
) -> Result<()>
where
F: Read + Write + Seek + 'static,
{
trace_event!(offset = offset, size = size, "deleting bytes");
if size == 0 {
return Ok(());
}
if size > i64::MAX as u64 {
return Err(AudexError::InvalidData(format!(
"Delete size {} exceeds maximum supported value ({})",
size,
i64::MAX
)));
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_pos = file.stream_position()?;
let file_size = file.seek(SeekFrom::End(0))?;
if offset > file_size {
return Err(AudexError::InvalidData(format!(
"Delete offset ({}) exceeds file size ({})",
offset, file_size
)));
}
if size > file_size - offset {
return Err(AudexError::InvalidData("Area beyond file size".to_string()));
}
let actual_size = std::cmp::min(size, file_size - offset);
if actual_size == 0 {
file.seek(SeekFrom::Start(current_pos))?;
return Ok(());
}
let delete_end = offset + actual_size;
let bytes_after_deletion = file_size - delete_end;
if bytes_after_deletion > 0 {
move_bytes(
file,
offset,
delete_end,
bytes_after_deletion,
Some(buffer_size),
)?;
}
let actual_size_i64 = i64::try_from(actual_size)
.map_err(|_| AudexError::InvalidData("file size exceeds i64 range".to_string()))?;
let new_size = file_size - actual_size;
resize_file(file, -actual_size_i64, Some(buffer_size))?;
let new_pos = std::cmp::min(current_pos, new_size);
file.seek(SeekFrom::Start(new_pos))?;
Ok(())
}
pub fn resize_bytes<F>(file: &mut F, old_size: u64, new_size: u64, offset: u64) -> Result<()>
where
F: Read + Write + Seek + 'static,
{
trace_event!(old_size = old_size, new_size = new_size, "resizing bytes");
if old_size == new_size {
return Ok(());
}
let buffer_size = DEFAULT_BUFFER_SIZE;
let current_pos = file.stream_position()?;
let file_size = file.seek(SeekFrom::End(0))?;
file.seek(SeekFrom::Start(current_pos))?;
if offset > file_size || old_size > file_size - offset {
return Err(AudexError::InvalidData(
"Region extends beyond file size".to_string(),
));
}
if new_size > old_size {
let size_diff = new_size - old_size;
let insert_offset = offset + old_size;
insert_bytes(file, size_diff, insert_offset, Some(buffer_size))
} else {
let size_diff = old_size - new_size;
let delete_offset = offset + new_size;
delete_bytes(file, size_diff, delete_offset, Some(buffer_size))
}
}
pub fn verify_fileobj(fileobj: &mut File, writable: bool) -> Result<()> {
let current_pos = fileobj
.stream_position()
.map_err(|_| AudexError::InvalidData("not a valid file object".to_string()))?;
let mut buffer = Vec::new();
match fileobj.read(&mut buffer) {
Ok(_) => {
fileobj.seek(SeekFrom::Start(current_pos)).map_err(|e| {
AudexError::InvalidData(format!("Can't read from file object: {}", e))
})?;
}
Err(e) => {
return Err(AudexError::InvalidData(format!(
"Can't read from file object: {}",
e
)));
}
}
if writable {
match fileobj.write_all(&[]) {
Ok(()) => {}
Err(e) => {
return Err(AudexError::InvalidData(format!(
"Can't write to file object: {}",
e
)));
}
}
}
Ok(())
}
pub fn verify_filename<P: AsRef<Path> + ?Sized>(filename: &P) -> Result<()> {
let path = filename.as_ref();
let path_str = path.to_string_lossy();
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if is_windows_reserved_name(stem) {
return Err(AudexError::InvalidData(format!(
"{:?} is a reserved device name",
path
)));
}
}
if path_str.starts_with("/dev/") {
return Err(AudexError::InvalidData(format!(
"{:?} is a device path",
path
)));
}
Ok(())
}
#[derive(Debug)]
pub struct FileThing<F> {
pub fileobj: F,
pub filename: Option<PathBuf>,
pub name: PathBuf,
}
pub type FileFileThing = FileThing<File>;
pub type MemoryFileThing = FileThing<Cursor<Vec<u8>>>;
#[derive(Debug)]
#[non_exhaustive]
pub enum AnyFileThing {
File(FileFileThing),
Memory(MemoryFileThing),
}
impl AnyFileThing {
pub fn display_name(&self) -> &Path {
match self {
AnyFileThing::File(f) => f.display_name(),
AnyFileThing::Memory(f) => f.display_name(),
}
}
pub fn from_filename(&self) -> bool {
match self {
AnyFileThing::File(f) => f.from_filename(),
AnyFileThing::Memory(f) => f.from_filename(),
}
}
pub fn is_memory(&self) -> bool {
matches!(self, AnyFileThing::Memory(_))
}
pub fn is_file(&self) -> bool {
matches!(self, AnyFileThing::File(_))
}
pub fn size(&mut self) -> Result<u64> {
match self {
AnyFileThing::File(f) => get_size(&mut f.fileobj),
AnyFileThing::Memory(f) => {
let _pos = f.fileobj.position();
let size = f.fileobj.get_ref().len() as u64;
Ok(size)
}
}
}
pub fn write_back(&self) -> Result<()> {
match self {
AnyFileThing::File(_) => Ok(()), AnyFileThing::Memory(memory_thing) => write_back_memory(memory_thing),
}
}
pub fn filename(&self) -> Option<&Path> {
match self {
AnyFileThing::File(f) => f.filename.as_deref(),
AnyFileThing::Memory(f) => f.filename.as_deref(),
}
}
pub fn truncate(&mut self, size: u64) -> Result<()> {
match self {
AnyFileThing::File(f) => {
f.fileobj.set_len(size).map_err(|e| {
AudexError::InvalidData(format!("Failed to truncate file: {}", e))
})?;
Ok(())
}
AnyFileThing::Memory(f) => {
let data = f.fileobj.get_mut();
let truncate_size = usize::try_from(size).map_err(|_| {
AudexError::InvalidData(
"Truncation size exceeds platform address space".to_string(),
)
})?;
data.truncate(truncate_size);
Ok(())
}
}
}
pub fn flush(&mut self) -> Result<()> {
match self {
AnyFileThing::File(f) => {
f.fileobj
.flush()
.map_err(|e| AudexError::InvalidData(format!("Failed to flush file: {}", e)))?;
Ok(())
}
AnyFileThing::Memory(_) => Ok(()), }
}
pub fn len_hint(&mut self) -> Result<u64> {
self.size()
}
}
impl Read for AnyFileThing {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
AnyFileThing::File(f) => f.read(buf),
AnyFileThing::Memory(f) => f.read(buf),
}
}
}
impl Write for AnyFileThing {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
AnyFileThing::File(f) => f.write(buf),
AnyFileThing::Memory(f) => f.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
AnyFileThing::File(f) => f.flush(),
AnyFileThing::Memory(f) => f.flush(),
}
}
}
impl Seek for AnyFileThing {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
match self {
AnyFileThing::File(f) => f.seek(pos),
AnyFileThing::Memory(f) => f.seek(pos),
}
}
}
impl TryFrom<&Path> for AnyFileThing {
type Error = AudexError;
fn try_from(path: &Path) -> Result<Self> {
let options = LoadFileOptions::read_method();
openfile_simple(FileInput::from_path(path), &options)
}
}
impl TryFrom<PathBuf> for AnyFileThing {
type Error = AudexError;
fn try_from(path: PathBuf) -> Result<Self> {
AnyFileThing::try_from(path.as_path())
}
}
impl From<Cursor<Vec<u8>>> for AnyFileThing {
fn from(cursor: Cursor<Vec<u8>>) -> Self {
let file_thing = FileThing::new(cursor, None, PathBuf::from("<memory>"));
AnyFileThing::Memory(file_thing)
}
}
impl TryFrom<File> for AnyFileThing {
type Error = AudexError;
fn try_from(file: File) -> Result<Self> {
let options = LoadFileOptions::read_method();
openfile_simple(FileInput::File(file), &options)
}
}
impl<F> FileThing<F> {
pub fn new(fileobj: F, filename: Option<PathBuf>, name: PathBuf) -> Self {
Self {
fileobj,
filename,
name,
}
}
pub fn display_name(&self) -> &Path {
&self.name
}
pub fn from_filename(&self) -> bool {
self.filename.is_some()
}
}
impl<F: Read> Read for FileThing<F> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.fileobj.read(buf)
}
}
impl<F: Write> Write for FileThing<F> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.fileobj.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.fileobj.flush()
}
}
impl<F: Seek> Seek for FileThing<F> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.fileobj.seek(pos)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FileOrPath {
File(File),
Path(PathBuf),
PathRef(PathBuf),
}
#[derive(Debug, Clone)]
pub struct LoadFileOptions {
pub method: bool,
pub writable: bool,
pub create: bool,
}
impl LoadFileOptions {
pub fn new(method: bool, writable: bool, create: bool) -> Self {
Self {
method,
writable,
create,
}
}
pub fn read_method() -> Self {
Self::new(true, false, false)
}
pub fn write_method() -> Self {
Self::new(true, true, false)
}
pub fn create_method() -> Self {
Self::new(true, true, true)
}
pub fn read_function() -> Self {
Self::new(false, false, false)
}
pub fn write_function() -> Self {
Self::new(false, true, false)
}
pub fn create_function() -> Self {
Self::new(false, true, true)
}
pub fn needs_write(&self) -> bool {
self.writable
}
pub fn allows_create(&self) -> bool {
self.create
}
pub fn is_method(&self) -> bool {
self.method
}
}
pub fn process_file_arg<P: AsRef<Path>>(
file_arg: FileOrPath,
options: &LoadFileOptions,
) -> Result<AnyFileThing> {
match file_arg {
FileOrPath::File(file) => {
let display_name = PathBuf::from("<file-like>");
let file_thing = FileThing::new(file, None, display_name);
Ok(AnyFileThing::File(file_thing))
}
FileOrPath::Path(path) | FileOrPath::PathRef(path) => process_file_path(&path, options),
}
}
fn process_file_path(path: &Path, options: &LoadFileOptions) -> Result<AnyFileThing> {
let path_buf = path.to_path_buf();
let display_name = path_buf.clone();
if cfg!(debug_assertions)
&& options.writable
&& std::env::var_os("AUDEX_FORCE_MEMORY_FALLBACK").is_some()
{
return create_memory_fallback(path, options);
}
match try_open_file(path, options) {
Ok(file) => {
let file_thing = FileThing::new(file, Some(path_buf), display_name);
Ok(AnyFileThing::File(file_thing))
}
Err(err) => {
if should_use_memory_fallback(&err, options) {
create_memory_fallback(path, options)
} else {
Err(err)
}
}
}
}
fn try_open_file(path: &Path, options: &LoadFileOptions) -> Result<File> {
let mut open_options = OpenOptions::new();
if options.writable {
open_options.write(true);
if options.create {
open_options.create(true);
}
open_options.read(true); } else {
open_options.read(true);
}
match open_options.open(path) {
Ok(file) => Ok(file),
Err(io_err) => {
let operation = if options.writable {
if options.create {
"open for write (create)"
} else {
"open for write"
}
} else {
"open for read"
};
Err(io_error_to_audex_error(io_err, path, operation))
}
}
}
fn should_use_memory_fallback(err: &AudexError, options: &LoadFileOptions) -> bool {
if !options.writable {
return false; }
if let AudexError::InvalidData(msg) = err {
msg.contains("Operation not supported") ||
msg.contains("Not supported") ||
msg.contains("Read-only file system") ||
msg.contains("Function not implemented") ||
msg.contains("Network is unreachable") ||
msg.contains("Remote I/O error") ||
msg.contains("Stale file handle") ||
msg.contains("Invalid argument") && msg.contains("mount") ||
(msg.contains("/proc") || msg.contains("/sys")) && msg.contains("Permission denied")
} else {
false
}
}
fn io_error_to_audex_error(io_err: std::io::Error, path: &Path, operation: &str) -> AudexError {
use std::io::ErrorKind;
let path_str = path.display().to_string();
let base_msg = format!("{}: {} ({})", path_str, io_err, operation);
match io_err.kind() {
ErrorKind::NotFound => AudexError::InvalidData(format!(
"[Errno 2] No such file or directory: '{}'",
path_str
)),
ErrorKind::PermissionDenied => {
AudexError::InvalidData(format!("[Errno 13] Permission denied: '{}'", path_str))
}
ErrorKind::AlreadyExists => {
AudexError::InvalidData(format!("[Errno 17] File exists: '{}'", path_str))
}
ErrorKind::InvalidInput => {
AudexError::InvalidData(format!("[Errno 22] Invalid argument: '{}'", path_str))
}
ErrorKind::WriteZero => AudexError::InvalidData(format!(
"[Errno 28] No space left on device: '{}'",
path_str
)),
ErrorKind::Interrupted => {
AudexError::InvalidData(format!("[Errno 4] Interrupted system call: '{}'", path_str))
}
ErrorKind::UnexpectedEof => {
AudexError::InvalidData(format!("Unexpected end of file: '{}'", path_str))
}
_ => {
AudexError::InvalidData(base_msg)
}
}
}
fn create_memory_fallback(path: &Path, options: &LoadFileOptions) -> Result<AnyFileThing> {
const MAX_MEMORY_FALLBACK: u64 = 100 * 1024 * 1024;
let path_buf = path.to_path_buf();
let display_name = path_buf.clone();
let initial_data = if path.exists() {
let file = File::open(path).map_err(|e| {
AudexError::InvalidData(format!(
"Failed to open '{}' for memory fallback: {}",
path.display(),
e
))
})?;
let mut reader = file.take(MAX_MEMORY_FALLBACK + 1);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).map_err(|e| {
AudexError::InvalidData(format!(
"I/O error reading '{}' for memory fallback: {}",
path.display(),
e
))
})?;
if buf.len() as u64 > MAX_MEMORY_FALLBACK {
return Err(AudexError::InvalidData(format!(
"File '{}' too large for memory fallback: exceeds {} byte cap",
path.display(),
MAX_MEMORY_FALLBACK
)));
}
buf
} else if options.create {
Vec::new() } else {
return Err(AudexError::InvalidData(format!(
"{}: No such file or directory (memory fallback)",
path.display()
)));
};
let cursor = Cursor::new(initial_data);
let memory_thing = MemoryFileThing::new(cursor, Some(path_buf), display_name);
Ok(AnyFileThing::Memory(memory_thing))
}
pub fn write_back_memory(memory_thing: &MemoryFileThing) -> Result<()> {
if let Some(ref path) = memory_thing.filename {
let data = memory_thing.fileobj.get_ref();
match std::fs::write(path, data) {
Ok(()) => Ok(()),
Err(io_err) => {
let msg = format!("Failed to write back to {}: {}", path.display(), io_err);
Err(AudexError::InvalidData(msg))
}
}
} else {
Ok(()) }
}
pub fn loadfile_process<P: AsRef<Path>>(
file_arg: P,
options: &LoadFileOptions,
) -> Result<AnyFileThing> {
let path = file_arg.as_ref();
trace_event!(
path = %path.display(),
writable = options.writable,
"loading file for processing"
);
let file_or_path = FileOrPath::Path(path.to_path_buf());
process_file_arg::<PathBuf>(file_or_path, options)
}
pub fn loadfile_read<P: AsRef<Path>>(file_path: P) -> Result<AnyFileThing> {
loadfile_process(file_path, &LoadFileOptions::read_function())
}
pub fn loadfile_write<P: AsRef<Path>>(file_path: P) -> Result<AnyFileThing> {
loadfile_process(file_path, &LoadFileOptions::write_function())
}
pub fn get_size(fileobj: &mut File) -> Result<u64> {
let old_pos = fileobj.stream_position()?;
let size = fileobj.seek(SeekFrom::End(0))?;
fileobj.seek(SeekFrom::Start(old_pos))?;
Ok(size)
}
pub fn startswith(text: &str, prefix: &str) -> bool {
text.starts_with(prefix)
}
pub fn crc32(data: &[u8]) -> u32 {
const CRC32_TABLE: [u32; 256] = [
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
];
let mut crc = 0xffffffff;
for &byte in data {
let table_index = ((crc ^ byte as u32) & 0xff) as usize;
crc = CRC32_TABLE[table_index] ^ (crc >> 8);
}
crc ^ 0xffffffff
}
#[cfg(feature = "async")]
use tokio::fs::{File as TokioFile, OpenOptions as TokioOpenOptions};
#[cfg(feature = "async")]
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
#[cfg(feature = "async")]
pub async fn read_full_async(file: &mut TokioFile, size: usize) -> Result<Vec<u8>> {
let current_pos = file
.stream_position()
.await
.map_err(|e| AudexError::InvalidData(format!("Cannot get file position: {}", e)))?;
let end_pos = file
.seek(SeekFrom::End(0))
.await
.map_err(|e| AudexError::InvalidData(format!("Cannot seek to end: {}", e)))?;
file.seek(SeekFrom::Start(current_pos))
.await
.map_err(|e| AudexError::InvalidData(format!("Cannot restore file position: {}", e)))?;
let remaining = end_pos.saturating_sub(current_pos);
if (size as u64) > remaining {
return Err(AudexError::InvalidData(format!(
"Cannot read {} bytes: only {} bytes remaining in file",
size, remaining
)));
}
let mut buffer = vec![0u8; size];
file.read_exact(&mut buffer)
.await
.map_err(|e| AudexError::InvalidData(format!("Cannot read {} bytes: {}", size, e)))?;
Ok(buffer)
}
#[cfg(feature = "async")]
pub async fn loadfile_read_async<P: AsRef<Path>>(path: P) -> Result<TokioFile> {
TokioFile::open(path.as_ref()).await.map_err(AudexError::Io)
}
#[cfg(feature = "async")]
pub async fn loadfile_write_async<P: AsRef<Path>>(path: P) -> Result<TokioFile> {
TokioOpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())
.await
.map_err(AudexError::Io)
}
#[cfg(feature = "async")]
pub async fn move_bytes_async(
file: &mut TokioFile,
dest: u64,
src: u64,
count: u64,
buffer_size: Option<usize>,
) -> Result<()> {
if count == 0 || src == dest {
return Ok(());
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_pos = file.stream_position().await?;
let file_size = file.seek(SeekFrom::End(0)).await?;
file.seek(SeekFrom::Start(current_pos)).await?;
if src.saturating_add(count) > file_size {
return Err(AudexError::InvalidData(
"Area outside of file: source range exceeds file size".to_string(),
));
}
if dest > file_size || dest.saturating_add(count) > file_size {
return Err(AudexError::InvalidData(
"Area outside of file: destination range exceeds file size".to_string(),
));
}
let mut buffer = vec![0u8; buffer_size];
if dest < src {
let mut bytes_moved = 0u64;
while bytes_moved < count {
let chunk_size = std::cmp::min(buffer_size as u64, count - bytes_moved) as usize;
file.seek(SeekFrom::Start(src + bytes_moved)).await?;
file.read_exact(&mut buffer[..chunk_size]).await?;
let bytes_read = chunk_size;
file.seek(SeekFrom::Start(dest + bytes_moved)).await?;
file.write_all(&buffer[..bytes_read]).await?;
bytes_moved += bytes_read as u64;
}
} else {
let mut bytes_remaining = count;
while bytes_remaining > 0 {
let chunk_size = std::cmp::min(buffer_size as u64, bytes_remaining) as usize;
let chunk_offset = bytes_remaining - chunk_size as u64;
file.seek(SeekFrom::Start(src + chunk_offset)).await?;
file.read_exact(&mut buffer[..chunk_size]).await?;
let bytes_read = chunk_size;
file.seek(SeekFrom::Start(dest + chunk_offset)).await?;
file.write_all(&buffer[..bytes_read]).await?;
bytes_remaining -= bytes_read as u64;
}
}
file.flush().await?;
Ok(())
}
#[cfg(feature = "async")]
pub async fn resize_file_async(
file: &mut TokioFile,
diff: i64,
buffer_size: Option<usize>,
) -> Result<()> {
if diff == 0 {
return Ok(());
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_size = file.seek(SeekFrom::End(0)).await?;
if diff > 0 {
let new_size = current_size.checked_add(diff as u64).ok_or_else(|| {
AudexError::InvalidData(format!(
"Cannot extend file: new size would overflow u64 (current {} + diff {})",
current_size, diff
))
})?;
file.set_len(new_size).await?;
} else {
let shrink_amount = diff.unsigned_abs();
if shrink_amount > current_size {
return Err(AudexError::InvalidData(format!(
"Cannot shrink file by {} bytes, file size is only {} bytes",
shrink_amount, current_size
)));
}
let new_size = current_size - shrink_amount;
file.set_len(new_size).await?;
}
Ok(())
}
#[cfg(feature = "async")]
pub async fn insert_bytes_async(
file: &mut TokioFile,
size: u64,
offset: u64,
buffer_size: Option<usize>,
) -> Result<()> {
if size == 0 {
return Ok(());
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_pos = file.stream_position().await?;
let file_size = file.seek(SeekFrom::End(0)).await?;
if offset > file_size {
return Err(AudexError::InvalidData(format!(
"Offset beyond file size: {} > {}",
offset, file_size
)));
}
let size_i64 = i64::try_from(size)
.map_err(|_| AudexError::InvalidData("insert size exceeds i64 range".to_string()))?;
if offset == file_size {
resize_file_async(file, size_i64, Some(buffer_size)).await?;
file.seek(SeekFrom::Start(current_pos)).await?;
return Ok(());
}
resize_file_async(file, size_i64, Some(buffer_size)).await?;
let dest_offset = offset.checked_add(size).ok_or_else(|| {
AudexError::InvalidData(format!(
"Destination offset overflow: {} + {} exceeds u64 range",
offset, size
))
})?;
let bytes_to_move = file_size - offset;
if bytes_to_move > 0 {
move_bytes_async(file, dest_offset, offset, bytes_to_move, Some(buffer_size)).await?;
}
file.seek(SeekFrom::Start(offset)).await?;
let mut remaining = size;
let mut buffer = vec![0u8; std::cmp::min(buffer_size, remaining as usize)];
while remaining > 0 {
let chunk_size = std::cmp::min(buffer_size as u64, remaining) as usize;
buffer.resize(chunk_size, 0);
file.write_all(&buffer).await?;
remaining -= chunk_size as u64;
}
file.flush().await?;
file.seek(SeekFrom::Start(current_pos)).await?;
Ok(())
}
#[cfg(feature = "async")]
pub async fn delete_bytes_async(
file: &mut TokioFile,
size: u64,
offset: u64,
buffer_size: Option<usize>,
) -> Result<()> {
if size == 0 {
return Ok(());
}
let buffer_size = buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE);
if buffer_size == 0 {
return Err(AudexError::InvalidData(
"Buffer size cannot be zero".to_string(),
));
}
let current_pos = file.stream_position().await?;
let file_size = file.seek(SeekFrom::End(0)).await?;
if offset > file_size {
return Err(AudexError::InvalidData(format!(
"Delete offset ({}) exceeds file size ({})",
offset, file_size
)));
}
if size > file_size - offset {
return Err(AudexError::InvalidData("Area beyond file size".to_string()));
}
let actual_size = std::cmp::min(size, file_size - offset);
if actual_size == 0 {
file.seek(SeekFrom::Start(current_pos)).await?;
return Ok(());
}
let delete_end = offset + actual_size;
let bytes_after_deletion = file_size - delete_end;
if bytes_after_deletion > 0 {
move_bytes_async(
file,
offset,
delete_end,
bytes_after_deletion,
Some(buffer_size),
)
.await?;
}
let actual_size_i64 = i64::try_from(actual_size)
.map_err(|_| AudexError::InvalidData("file size exceeds i64 range".to_string()))?;
let new_size = file_size - actual_size;
resize_file_async(file, -actual_size_i64, Some(buffer_size)).await?;
let new_pos = std::cmp::min(current_pos, new_size);
file.seek(SeekFrom::Start(new_pos)).await?;
Ok(())
}
#[cfg(feature = "async")]
pub async fn resize_bytes_async(
file: &mut TokioFile,
old_size: u64,
new_size: u64,
offset: u64,
) -> Result<()> {
if old_size == new_size {
return Ok(());
}
let buffer_size = DEFAULT_BUFFER_SIZE;
let current_pos = file.stream_position().await?;
let file_size = file.seek(SeekFrom::End(0)).await?;
file.seek(SeekFrom::Start(current_pos)).await?;
if offset > file_size || old_size > file_size - offset {
return Err(AudexError::InvalidData(
"Region extends beyond file size".to_string(),
));
}
if new_size > old_size {
let size_diff = new_size - old_size;
let insert_offset = offset + old_size;
insert_bytes_async(file, size_diff, insert_offset, Some(buffer_size)).await
} else {
let size_diff = old_size - new_size;
let delete_offset = offset + new_size;
delete_bytes_async(file, size_diff, delete_offset, Some(buffer_size)).await
}
}