216 lines
6.2 KiB
Rust
216 lines
6.2 KiB
Rust
use std::{
|
|
fs,
|
|
io::{self, Read, Seek, SeekFrom},
|
|
path::Path,
|
|
};
|
|
|
|
pub fn read_bytes_at(path: &Path, offset: u64, len: usize) -> io::Result<Vec<u8>> {
|
|
let mut f = fs::File::open(path)?;
|
|
f.seek(SeekFrom::Start(offset))?;
|
|
let mut buf = vec![0u8; len];
|
|
let n = f.read(&mut buf)?;
|
|
buf.truncate(n);
|
|
|
|
return Ok(buf);
|
|
}
|
|
|
|
fn assemble_virtual_read(
|
|
reader: &dyn Fn(u64, usize) -> io::Result<Vec<u8>>,
|
|
header: &[u8],
|
|
pic_hdrs: &[Vec<u8>],
|
|
pic_ranges: &[(u64, u64)],
|
|
real_audio_start: u64,
|
|
offset: u64,
|
|
size: u32,
|
|
) -> io::Result<Vec<u8>> {
|
|
let end = offset + size as u64;
|
|
let header_end = header.len() as u64;
|
|
|
|
if end <= header_end {
|
|
return Ok(header[offset as usize..end as usize].to_vec());
|
|
}
|
|
|
|
let mut buf = Vec::with_capacity(size as usize);
|
|
if offset < header_end {
|
|
buf.extend_from_slice(&header[offset as usize..]);
|
|
}
|
|
|
|
let mut virt_pos = header_end;
|
|
for (pic_hdr, (data_real_offset, data_len)) in pic_hdrs.iter().zip(pic_ranges.iter()) {
|
|
let pic_hdr_len = pic_hdr.len() as u64;
|
|
let pic_hdr_end = virt_pos + pic_hdr_len;
|
|
let pic_end = pic_hdr_end + data_len;
|
|
|
|
if end <= virt_pos {
|
|
break;
|
|
}
|
|
if offset >= pic_end {
|
|
virt_pos = pic_end;
|
|
continue;
|
|
}
|
|
|
|
let hdr_from = (offset.max(virt_pos) - virt_pos) as usize;
|
|
let hdr_to = ((end.min(pic_hdr_end)) - virt_pos) as usize;
|
|
if hdr_from < hdr_to {
|
|
buf.extend_from_slice(&pic_hdr[hdr_from..hdr_to.min(pic_hdr.len())]);
|
|
}
|
|
|
|
let data_start = offset.max(pic_hdr_end);
|
|
let data_end = end.min(pic_end);
|
|
if data_start < data_end {
|
|
let real_off = data_real_offset + (data_start - pic_hdr_end);
|
|
let bytes = reader(real_off, (data_end - data_start) as usize)?;
|
|
buf.extend_from_slice(&bytes);
|
|
}
|
|
|
|
virt_pos = pic_end;
|
|
}
|
|
|
|
let audio_start = offset.max(virt_pos);
|
|
if audio_start < end {
|
|
let real_off = real_audio_start + (audio_start - virt_pos);
|
|
let bytes = reader(real_off, (end - audio_start) as usize)?;
|
|
buf.extend_from_slice(&bytes);
|
|
}
|
|
|
|
return Ok(buf);
|
|
}
|
|
|
|
pub fn assemble_flac_read(
|
|
reader: &dyn Fn(u64, usize) -> io::Result<Vec<u8>>,
|
|
header: &[u8],
|
|
pic_hdrs: &[Vec<u8>],
|
|
pic_ranges: &[(u64, u64)],
|
|
real_audio_start: u64,
|
|
offset: u64,
|
|
size: u32,
|
|
) -> io::Result<Vec<u8>> {
|
|
assemble_virtual_read(
|
|
reader,
|
|
header,
|
|
pic_hdrs,
|
|
pic_ranges,
|
|
real_audio_start,
|
|
offset,
|
|
size,
|
|
)
|
|
}
|
|
|
|
pub fn assemble_mp3_read(
|
|
reader: &dyn Fn(u64, usize) -> io::Result<Vec<u8>>,
|
|
header: &[u8],
|
|
pic_hdrs: &[Vec<u8>],
|
|
pic_ranges: &[(u64, u64)],
|
|
real_audio_start: u64,
|
|
offset: u64,
|
|
size: u32,
|
|
) -> io::Result<Vec<u8>> {
|
|
assemble_virtual_read(
|
|
reader,
|
|
header,
|
|
pic_hdrs,
|
|
pic_ranges,
|
|
real_audio_start,
|
|
offset,
|
|
size,
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
|
|
fn file_reader(path: PathBuf) -> impl Fn(u64, usize) -> io::Result<Vec<u8>> {
|
|
return move |offset, len| read_bytes_at(&path, offset, len);
|
|
}
|
|
|
|
#[test]
|
|
fn read_bytes_at_full_file() {
|
|
let mut f = tempfile::NamedTempFile::new().unwrap();
|
|
f.write_all(b"hello world").unwrap();
|
|
f.flush().unwrap();
|
|
let path = f.path();
|
|
|
|
let result = read_bytes_at(path, 0, 11).unwrap();
|
|
assert_eq!(result, b"hello world");
|
|
}
|
|
|
|
#[test]
|
|
fn read_bytes_at_offset() {
|
|
let mut f = tempfile::NamedTempFile::new().unwrap();
|
|
f.write_all(b"abcdefghij").unwrap();
|
|
f.flush().unwrap();
|
|
let path = f.path();
|
|
|
|
let result = read_bytes_at(path, 3, 4).unwrap();
|
|
assert_eq!(result, b"defg");
|
|
}
|
|
|
|
#[test]
|
|
fn assemble_flac_read_header_only() {
|
|
let header = b"HEADERDATA";
|
|
let noop = |_, _| Ok(vec![]);
|
|
let result = assemble_flac_read(&noop, header, &[], &[], 0, 2, 4).unwrap();
|
|
assert_eq!(result, b"ADER");
|
|
}
|
|
|
|
#[test]
|
|
fn assemble_flac_read_audio_region() {
|
|
let mut f = tempfile::NamedTempFile::new().unwrap();
|
|
let content: Vec<u8> = (0..20).collect();
|
|
f.write_all(&content).unwrap();
|
|
f.flush().unwrap();
|
|
let path = f.path().to_path_buf();
|
|
|
|
let reader = file_reader(path);
|
|
let header = b"HDR";
|
|
let result = assemble_flac_read(&reader, header, &[], &[], 10, 5, 4).unwrap();
|
|
// offset=5, header_len=3, so we read from header[3..] (0 bytes) + audio at real_off=10+(5-3)=12
|
|
// content[12..16] = [12, 13, 14, 15]
|
|
assert_eq!(result, vec![12, 13, 14, 15]);
|
|
}
|
|
|
|
#[test]
|
|
fn assemble_flac_read_header_audio_boundary() {
|
|
let mut f = tempfile::NamedTempFile::new().unwrap();
|
|
let content: Vec<u8> = (0..110).collect();
|
|
f.write_all(&content).unwrap();
|
|
f.flush().unwrap();
|
|
let path = f.path().to_path_buf();
|
|
|
|
let reader = file_reader(path);
|
|
let header = b"ABCD";
|
|
let result = assemble_flac_read(&reader, header, &[], &[], 100, 2, 6).unwrap();
|
|
// offset=2, size=6, header_len=4
|
|
// First 2 bytes from header[2..4] = "CD"
|
|
// Remaining 4 bytes from audio at real_off=100+(2+2-4)=100
|
|
// content[100..104] = [100, 101, 102, 103]
|
|
let expected = [b'C', b'D', 100, 101, 102, 103];
|
|
assert_eq!(result, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn assemble_mp3_read_header_only() {
|
|
let header = b"ID3\x04\x00DATAHERE";
|
|
let noop = |_, _| Ok(vec![]);
|
|
let result = assemble_mp3_read(&noop, header, &[], &[], 0, 4, 4).unwrap();
|
|
assert_eq!(result, &header[4..8]);
|
|
}
|
|
|
|
#[test]
|
|
fn assemble_mp3_read_audio_region() {
|
|
let mut f = tempfile::NamedTempFile::new().unwrap();
|
|
let content: Vec<u8> = (0..20).collect();
|
|
f.write_all(&content).unwrap();
|
|
f.flush().unwrap();
|
|
let path = f.path().to_path_buf();
|
|
|
|
let reader = file_reader(path);
|
|
let header = b"ID3";
|
|
let result = assemble_mp3_read(&reader, header, &[], &[], 10, 5, 4).unwrap();
|
|
assert_eq!(result, vec![12, 13, 14, 15]);
|
|
}
|
|
}
|