use std::convert::{TryFrom, TryInto};
use std::fs::{File, Metadata, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use crate::dat_error::DATError;
use crate::dat_type::*;
pub const HEADER_SIZE: u32 = 0x11;
const MAX_SIZE_OFFSET: u32 = 32;
const INDEX_FILE_TYPE: usize = 0x00;
const INDEX_MAX_SIZE: usize = 0x04;
const INDEX_CONTENT_SIZE: usize = 0x08;
#[derive(Debug)]
pub struct DATFile {
content_size: u32,
file_type: DATType,
header_end_byte: u8,
max_size: u32,
raw_file: File,
}
impl Read for DATFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let cur_pos = self.stream_position()? as u32;
let max_end = self.content_size - 1;
let read_end = match u32::try_from(buf.len()) {
Ok(safe_buf_len) if cur_pos + safe_buf_len < max_end => cur_pos + safe_buf_len,
_ => max_end,
};
let read_len = (read_end - cur_pos) as usize;
if read_len < 1 {
return Ok(0);
}
let mut internal_buf = vec![0u8; read_len];
let count = self.raw_file.read(&mut internal_buf)?;
if let Some(mask_val) = get_mask_for_type(&self.file_type) {
for byte in internal_buf.iter_mut() {
*byte ^= mask_val;
}
}
buf[..read_len].clone_from_slice(&internal_buf);
Ok(count)
}
}
impl Seek for DATFile {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, std::io::Error> {
let cursor = match pos {
SeekFrom::Current(offset) => {
if self.raw_file.stream_position()? as i64 + offset < HEADER_SIZE as i64 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid argument",
));
} else {
self.raw_file.seek(pos)?
}
}
SeekFrom::End(offset) => self.raw_file.seek(SeekFrom::End(
offset
- (self.max_size as i64 - self.content_size as i64)
- (MAX_SIZE_OFFSET as i64 - HEADER_SIZE as i64)
- 1,
))?,
SeekFrom::Start(offset) => self.raw_file.seek(SeekFrom::Start(HEADER_SIZE as u64 + offset))?,
};
Ok(cursor - HEADER_SIZE as u64)
}
}
impl Write for DATFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let content_cursor = self.stream_position()? as u32;
let buf_len = match u32::try_from(buf.len()) {
Ok(len) => len,
Err(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
DATError::Overflow("Content too long to write."),
))
}
};
match content_cursor.checked_add(buf_len + 1) {
Some(new_content_size) if new_content_size > self.content_size => {
if new_content_size > self.max_size {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
DATError::Overflow("Content size would exdeed maximum size after write."),
));
}
self.write_content_size_header(new_content_size)?;
}
Some(_) => (),
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
DATError::Overflow("Content size would exceed maximum possible size (u32::MAX) after write."),
))
}
};
match get_mask_for_type(&self.file_type) {
Some(mask_val) => {
let mut masked_bytes = vec![0u8; buf.len()];
masked_bytes.copy_from_slice(buf);
for byte in masked_bytes.iter_mut() {
*byte ^= mask_val;
}
Ok(self.raw_file.write(&masked_bytes)?)
}
None => Ok(self.raw_file.write(buf)?),
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.raw_file.flush()
}
}
impl DATFile {
pub fn content_size(&self) -> u32 {
self.content_size
}
pub fn create<P: AsRef<Path>>(path: P, dat_type: DATType) -> Result<Self, DATError> {
let max_size = get_default_max_size_for_type(&dat_type).unwrap_or(0);
let end_byte = get_default_end_byte_for_type(&dat_type).unwrap_or(0);
Self::create_unsafe(path, dat_type, 1, max_size, end_byte)
}
pub fn create_unsafe<P: AsRef<Path>>(
path: P, dat_type: DATType, content_size: u32, max_size: u32, end_byte: u8,
) -> Result<Self, DATError> {
{
let mut raw_file = File::create(&path)?;
raw_file.set_len((max_size + MAX_SIZE_OFFSET) as u64)?;
raw_file.seek(SeekFrom::Start(INDEX_FILE_TYPE as u64))?;
raw_file.write_all(&(dat_type as i32).to_le_bytes())?;
raw_file.seek(SeekFrom::Start(INDEX_MAX_SIZE as u64))?;
raw_file.write_all(&max_size.to_le_bytes())?;
raw_file.seek(SeekFrom::Start(INDEX_CONTENT_SIZE as u64))?;
raw_file.write_all(&1u32.to_le_bytes())?;
raw_file.seek(SeekFrom::Start(HEADER_SIZE as u64 - 1))?;
raw_file.write_all(&[end_byte])?;
}
let mut dat_file = DATFile::open_options(path, OpenOptions::new().read(true).write(true).create(true))?;
dat_file.set_content_size(content_size)?;
Ok(dat_file)
}
pub fn create_with_content<P: AsRef<Path>>(path: P, dat_type: DATType, content: &[u8]) -> Result<Self, DATError> {
let max_size = get_default_max_size_for_type(&dat_type).unwrap_or(0);
let end_byte = get_default_end_byte_for_type(&dat_type).unwrap_or(0);
let mut dat_file = Self::create_unsafe(path, dat_type, 1, max_size, end_byte)?;
dat_file.write_all(&content)?;
dat_file.seek(SeekFrom::Start(0))?;
Ok(dat_file)
}
pub fn file_type(&self) -> DATType {
self.file_type
}
pub fn header_end_byte(&self) -> u8 {
self.header_end_byte
}
pub fn max_size(&self) -> u32 {
self.max_size
}
pub fn metadata(&self) -> Result<Metadata, DATError> {
Ok(self.raw_file.metadata()?)
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, DATError> {
let mut raw_file = File::open(path)?;
let mut header_bytes = [0u8; HEADER_SIZE as usize];
raw_file.read_exact(&mut header_bytes)?;
let (file_type, max_size, content_size, header_end_byte) = get_header_contents(&header_bytes)?;
Ok(DATFile {
content_size,
file_type,
header_end_byte,
max_size,
raw_file,
})
}
pub fn open_options<P: AsRef<Path>>(path: P, options: &mut OpenOptions) -> Result<Self, DATError> {
let mut raw_file = options.open(path)?;
let mut header_bytes = [0u8; HEADER_SIZE as usize];
raw_file.read_exact(&mut header_bytes)?;
let (file_type, max_size, content_size, header_end_byte) = get_header_contents(&header_bytes)?;
Ok(DATFile {
content_size,
file_type,
header_end_byte,
max_size,
raw_file,
})
}
pub fn set_content_size(&mut self, new_size: u32) -> Result<(), DATError> {
if new_size == self.content_size {
return Ok(());
}
if new_size == 0 {
return Err(DATError::InvalidInput("Content size must be > 0."));
}
if new_size > self.max_size {
return Err(DATError::Overflow("Content size would exceed maximum size."));
}
let pre_cursor = self.raw_file.seek(SeekFrom::Current(0))?;
let (padding_byte, write_size) = if new_size > self.content_size {
self.seek(SeekFrom::End(0))?;
(
get_mask_for_type(&self.file_type).unwrap_or(0),
new_size - self.content_size,
)
} else {
self.seek(SeekFrom::Start(new_size as u64))?;
(0, self.content_size - new_size)
};
match usize::try_from(write_size) {
Ok(safe_write_size) => {
self.raw_file.write_all(&vec![padding_byte; safe_write_size])?;
}
Err(_) => {
let mut remaining_bytes = write_size;
loop {
match usize::try_from(remaining_bytes) {
Ok(safe_write_size) => {
self.raw_file.write_all(&vec![padding_byte; safe_write_size])?;
break;
}
Err(_) => {
self.raw_file.write_all(&vec![padding_byte; usize::MAX])?;
remaining_bytes -= usize::MAX as u32;
}
};
}
}
}
self.write_content_size_header(new_size)?;
self.raw_file.seek(SeekFrom::Start(pre_cursor))?;
Ok(())
}
pub fn set_max_size(&mut self, new_size: u32) -> Result<(), DATError> {
if new_size == self.max_size {
return Ok(());
}
if new_size == 0 {
return Err(DATError::InvalidInput("Content size must be > 0."));
}
if new_size < self.content_size {
return Err(DATError::Overflow("Content size would exceed maximum size."));
}
self.raw_file.set_len((new_size + MAX_SIZE_OFFSET) as u64)?;
self.write_max_size_header(new_size)?;
Ok(())
}
pub fn sync_all(&self) -> Result<(), DATError> {
Ok(self.raw_file.sync_all()?)
}
pub fn sync_data(&self) -> Result<(), DATError> {
Ok(self.raw_file.sync_data()?)
}
fn write_content_size_header(&mut self, size: u32) -> Result<(), std::io::Error> {
let pre_cursor = self.raw_file.seek(SeekFrom::Current(0))?;
self.raw_file.seek(SeekFrom::Start(INDEX_CONTENT_SIZE as u64))?;
self.raw_file.write_all(&size.to_le_bytes())?;
self.raw_file.seek(SeekFrom::Start(pre_cursor))?;
self.content_size = size;
Ok(())
}
fn write_max_size_header(&mut self, size: u32) -> Result<(), std::io::Error> {
let pre_cursor = self.raw_file.seek(SeekFrom::Current(0))?;
self.raw_file.seek(SeekFrom::Start(INDEX_MAX_SIZE as u64))?;
self.raw_file.write_all(&size.to_le_bytes())?;
self.raw_file.seek(SeekFrom::Start(pre_cursor))?;
self.max_size = size;
Ok(())
}
}
pub fn check_type<P: AsRef<Path>>(path: P) -> Result<DATType, DATError> {
let dat_file = DATFile::open(path)?;
Ok(dat_file.file_type())
}
pub fn get_header_contents(header: &[u8; HEADER_SIZE as usize]) -> Result<(DATType, u32, u32, u8), DATError> {
let file_type_id = u32::from_le_bytes(header[INDEX_FILE_TYPE..INDEX_MAX_SIZE].try_into()?);
let max_size = u32::from_le_bytes(header[INDEX_MAX_SIZE..INDEX_CONTENT_SIZE].try_into()?);
let content_size = u32::from_le_bytes(header[INDEX_CONTENT_SIZE..INDEX_CONTENT_SIZE + 4].try_into()?);
let end_byte = header[HEADER_SIZE as usize - 1];
if 0xff00ff00 & file_type_id > 0 {
return Err(DATError::BadHeader("File type ID bytes are absent."));
}
if content_size > max_size {
return Err(DATError::BadHeader("Content size exceeds max size in header."));
}
Ok((DATType::from(file_type_id), max_size, content_size, end_byte))
}
pub fn read_content<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, DATError> {
let mut dat_file = DATFile::open(path)?;
let safe_content_size = usize::try_from(dat_file.content_size - 1)?;
let mut buf = vec![0u8; safe_content_size];
dat_file.read_exact(&mut buf)?;
Ok(buf)
}
pub fn write_content<P: AsRef<Path>>(path: P, buf: &[u8]) -> Result<usize, DATError> {
let mut dat_file = DATFile::open_options(path, OpenOptions::new().read(true).write(true))?;
if let Ok(safe_content_size) = u32::try_from(buf.len() + 1) {
if safe_content_size != dat_file.content_size() {
dat_file.set_content_size(safe_content_size)?;
}
Ok(dat_file.write(&buf)?)
} else {
Err(DATError::Overflow(
"Content size would exceed maximum possible size (u32::MAX).",
))
}
}
#[cfg(test)]
mod tests {
extern crate tempfile;
use tempfile::tempdir;
use super::*;
use std::fs::copy;
const TEST_PATH: &str = "./resources/TEST.DAT";
const TEST_XOR_PATH: &str = "./resources/TEST_XOR.DAT";
const TEST_EMPTY_PATH: &str = "./resources/TEST_EMPTY.DAT";
const TEST_CONTENTS: &[u8; 5] = b"Boop!";
const TEST_XOR_CONTENTS: &[u8; 6] = b"Macro!";
#[test]
fn test_check_type() -> Result<(), String> {
match check_type(TEST_XOR_PATH) {
Ok(dat_type) => Ok(assert_eq!(dat_type, DATType::Macro)),
Err(err) => Err(format!("Read error: {}", err)),
}
}
#[test]
fn test_get_header_contents() -> Result<(), String> {
let header_bytes = [
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
];
let (dat_type, max_size, content_size, end_byte) = match get_header_contents(&header_bytes) {
Ok(res) => (res.0, res.1, res.2, res.3),
Err(err) => return Err(format!("{}", err)),
};
assert_eq!(dat_type, DATType::Unknown);
assert_eq!(max_size, 2);
assert_eq!(content_size, 2);
assert_eq!(end_byte, 0xFF);
Ok(())
}
#[test]
fn test_get_header_contents_error_bad_type() -> Result<(), String> {
let header_bytes = [
0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
];
match get_header_contents(&header_bytes) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err {
DATError::BadHeader(_) => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_get_header_contents_error_sizes() -> Result<(), String> {
let header_bytes = [
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
];
match get_header_contents(&header_bytes) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err {
DATError::BadHeader(_) => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_read_content() -> Result<(), String> {
match read_content(TEST_PATH) {
Ok(content_bytes) => Ok(assert_eq!(&content_bytes, TEST_CONTENTS)),
Err(err) => Err(format!("Read error: {}", err)),
}
}
#[test]
fn test_read_content_with_mask() -> Result<(), String> {
match read_content(TEST_XOR_PATH) {
Ok(content_bytes) => Ok(assert_eq!(&content_bytes, TEST_XOR_CONTENTS)),
Err(err) => Err(format!("Read error: {}", err)),
}
}
#[test]
fn test_write_content() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
match copy(TEST_PATH, &tmp_path) {
Ok(_) => (),
Err(err) => return Err(format!("Could not create temp file for testing: {}", err)),
};
let new_content = b"Hi!";
match write_content(&tmp_path, new_content) {
Ok(_) => (),
Err(err) => return Err(format!("Error writing content: {}", err)),
};
match read_content(&tmp_path) {
Ok(content_bytes) => Ok(assert_eq!(&content_bytes, new_content)),
Err(err) => Err(format!("Error reading file after write: {}", err)),
}
}
#[test]
fn test_datfile_open() -> Result<(), String> {
match DATFile::open(TEST_PATH) {
Ok(dat_file) => {
assert_eq!(dat_file.content_size(), 6);
assert_eq!(dat_file.max_size, 7);
assert_eq!(dat_file.header_end_byte(), 0xFF);
assert_eq!(dat_file.file_type(), DATType::Unknown);
Ok(())
}
Err(err) => Err(format!("{}", err)),
}
}
#[test]
fn test_datfile_open_detect_type() -> Result<(), String> {
match DATFile::open(TEST_XOR_PATH) {
Ok(dat_file) => {
assert_eq!(dat_file.content_size(), 7);
assert_eq!(dat_file.max_size, 8);
assert_eq!(dat_file.header_end_byte(), 0xFF);
assert_eq!(dat_file.file_type(), DATType::Macro);
Ok(())
}
Err(err) => Err(format!("{}", err)),
}
}
#[test]
fn test_datfile_open_options() -> Result<(), String> {
let mut opts = std::fs::OpenOptions::new();
opts.read(true).write(true);
match DATFile::open_options(TEST_PATH, &mut opts) {
Ok(dat_file) => {
assert_eq!(dat_file.content_size(), 6);
assert_eq!(dat_file.max_size, 7);
assert_eq!(dat_file.header_end_byte(), 0xFF);
assert_eq!(dat_file.file_type(), DATType::Unknown);
Ok(())
}
Err(err) => Err(format!("{}", err)),
}
}
#[test]
fn test_datfile_create() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
match DATFile::create(&tmp_path, DATType::Macro) {
Ok(dat_file) => {
assert_eq!(dat_file.content_size(), 1);
assert_eq!(
dat_file.max_size,
get_default_max_size_for_type(&DATType::Macro).unwrap()
);
assert_eq!(
dat_file.header_end_byte(),
get_default_end_byte_for_type(&DATType::Macro).unwrap()
);
assert_eq!(dat_file.file_type(), DATType::Macro);
Ok(())
}
Err(err) => Err(format!("{}", err)),
}
}
#[test]
fn test_datfile_create_with_content() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let content = b"Content!";
match DATFile::create_with_content(&tmp_path, DATType::Macro, content) {
Ok(mut dat_file) => {
assert_eq!(dat_file.content_size(), content.len() as u32 + 1);
assert_eq!(
dat_file.max_size,
get_default_max_size_for_type(&DATType::Macro).unwrap()
);
assert_eq!(
dat_file.header_end_byte(),
get_default_end_byte_for_type(&DATType::Macro).unwrap()
);
assert_eq!(dat_file.file_type(), DATType::Macro);
let mut buf = [0u8; 8];
match dat_file.read(&mut buf) {
Ok(_) => (),
Err(err) => return Err(format!("Could not read back content: {}", err)),
}
assert_eq!(&buf, content);
Ok(())
}
Err(err) => Err(format!("{}", err)),
}
}
#[test]
fn test_datfile_create_unsafe() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
match DATFile::create_unsafe(&tmp_path, DATType::Macro, 256, 512, 0) {
Ok(dat_file) => {
assert_eq!(dat_file.content_size(), 256);
assert_eq!(dat_file.max_size, 512);
assert_eq!(dat_file.header_end_byte(), 0);
assert_eq!(dat_file.file_type(), DATType::Macro);
Ok(())
}
Err(err) => Err(format!("{}", err)),
}
}
#[test]
fn test_datfile_set_content_size_grow() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_with_content(&tmp_path, DATType::Macro, &[34u8; 8]) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_content_size(17) {
Ok(_) => {
assert_eq!(dat_file.content_size(), 17);
let mut buf = [0u8; 16];
match dat_file.read(&mut buf) {
Ok(_) => (),
Err(err) => return Err(format!("Could not read back content: {}", err)),
}
assert_eq!(&buf[..8], &[34u8; 8]);
assert_eq!(&buf[8..], &[0u8; 8]);
Ok(())
}
Err(err) => return Err(format!("Error setting content size: {}", err)),
}
}
#[test]
fn test_datfile_set_content_size_shrink() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_with_content(&tmp_path, DATType::Macro, &[34u8; 8]) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_content_size(5) {
Ok(_) => {
assert_eq!(dat_file.content_size(), 5);
let mut buf = [1u8; 8];
match dat_file.read(&mut buf) {
Ok(_) => (),
Err(err) => return Err(format!("Could not read back content: {}", err)),
}
assert_eq!(&buf[..4], &[34u8; 4]);
assert_eq!(&buf[4..], &[1u8; 4]);
Ok(())
}
Err(err) => return Err(format!("Error setting content size: {}", err)),
}
}
#[test]
fn test_datfile_set_content_size_error_zero_size() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_with_content(&tmp_path, DATType::Macro, &[34u8; 8]) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_content_size(0) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err {
DATError::InvalidInput(_) => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_datfile_set_content_size_error_over_max() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_unsafe(&tmp_path, DATType::Unknown, 1, 4, 0) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_content_size(8) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err {
DATError::Overflow(_) => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_datfile_set_max_size_grow() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_unsafe(&tmp_path, DATType::Macro, 4, 8, 0) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_max_size(16) {
Ok(_) => {
assert_eq!(dat_file.max_size(), 16);
let meta = match dat_file.metadata() {
Ok(meta) => meta,
Err(err) => return Err(format!("Could not read back content: {}", err)),
};
assert_eq!(meta.len(), 16 + MAX_SIZE_OFFSET as u64);
Ok(())
}
Err(err) => return Err(format!("Error setting content size: {}", err)),
}
}
#[test]
fn test_datfile_set_max_size_shrink() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_unsafe(&tmp_path, DATType::Macro, 4, 8, 0) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_max_size(6) {
Ok(_) => {
assert_eq!(dat_file.max_size(), 6);
let meta = match dat_file.metadata() {
Ok(meta) => meta,
Err(err) => return Err(format!("Could not read back content: {}", err)),
};
assert_eq!(meta.len(), 6 + MAX_SIZE_OFFSET as u64);
Ok(())
}
Err(err) => return Err(format!("Error setting content size: {}", err)),
}
}
#[test]
fn test_datfile_set_max_size_error_zero_size() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_unsafe(&tmp_path, DATType::Macro, 4, 8, 0) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_max_size(0) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err {
DATError::InvalidInput(_) => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_datfile_set_max_size_error_under_content() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
let mut dat_file = match DATFile::create_unsafe(&tmp_path, DATType::Unknown, 4, 8, 0) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error creating temp file: {}", err)),
};
match dat_file.set_max_size(2) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err {
DATError::Overflow(_) => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_datfile_read() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
let mut buf = [0u8; 1];
match dat_file.read(&mut buf) {
Ok(_) => Ok(assert_eq!(buf, TEST_CONTENTS[0..1])),
Err(err) => Err(format!("Read error: {}", err)),
}
}
#[test]
fn test_datfile_read_with_mask() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_XOR_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
let mut buf = [0u8; 1];
match dat_file.read(&mut buf) {
Ok(_) => Ok(assert_eq!(buf, TEST_XOR_CONTENTS[0..1])),
Err(err) => Err(format!("Read error: {}", err)),
}
}
#[test]
fn test_datfile_read_past_end() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
let mut buf = [1u8; 8];
match dat_file.read(&mut buf) {
Ok(_) => {
assert_eq!(&buf[0..5], TEST_CONTENTS);
assert_eq!(buf[5..], [1u8; 3]);
Ok(())
}
Err(err) => Err(format!("Read error: {}", err)),
}
}
#[test]
fn test_datfile_seek_current() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
match dat_file.seek(SeekFrom::Current(1)) {
Ok(_) => Ok(assert_eq!(
dat_file.raw_file.stream_position().unwrap(),
HEADER_SIZE as u64 + 1
)),
Err(err) => Err(format!("Seek error: {}", err)),
}
}
#[test]
fn test_datfile_seek_start() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
match dat_file.seek(SeekFrom::Start(1)) {
Ok(_) => Ok(assert_eq!(
dat_file.raw_file.stream_position().unwrap(),
HEADER_SIZE as u64 + 1
)),
Err(err) => Err(format!("Seek error: {}", err)),
}
}
#[test]
fn test_datfile_seek_end() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
match dat_file.seek(SeekFrom::End(-1)) {
Ok(_) => Ok(assert_eq!(
dat_file.raw_file.stream_position().unwrap(),
HEADER_SIZE as u64 + dat_file.content_size() as u64 - 2
)),
Err(err) => Err(format!("Seek error: {}", err)),
}
}
#[test]
fn test_datfile_seek_current_error_negative() -> Result<(), String> {
let mut dat_file = match DATFile::open(TEST_PATH) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Open error: {}", err)),
};
match dat_file.seek(SeekFrom::Current(-10)) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err.kind() {
std::io::ErrorKind::InvalidInput => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
#[test]
fn test_datfile_write() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
match copy(TEST_PATH, &tmp_path) {
Ok(_) => (),
Err(err) => return Err(format!("Could not create temp file for testing: {}", err)),
};
let mut opts = OpenOptions::new();
opts.read(true).write(true);
let mut dat_file = match DATFile::open_options(&tmp_path, &mut opts) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error opening temp file: {}", err)),
};
let new_content = b"Hi!";
match dat_file.write(new_content) {
Ok(_) => (),
Err(err) => return Err(format!("Error writing content: {}", err)),
};
match dat_file.seek(SeekFrom::Start(0)) {
Ok(_) => (),
Err(err) => return Err(format!("Error seeking in file: {}", err)),
}
match read_content(&tmp_path) {
Ok(content_bytes) => Ok(assert_eq!(&content_bytes, b"Hi!p!")),
Err(err) => Err(format!("Error reading file after write: {}", err)),
}
}
#[test]
fn test_datfile_write_extend_content_size() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
match copy(TEST_EMPTY_PATH, &tmp_path) {
Ok(_) => (),
Err(err) => return Err(format!("Could not create temp file for testing: {}", err)),
};
let mut opts = OpenOptions::new();
opts.read(true).write(true);
let mut dat_file = match DATFile::open_options(&tmp_path, &mut opts) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error opening temp file: {}", err)),
};
let new_content = b"Long!";
match dat_file.write(new_content) {
Ok(_) => (),
Err(err) => return Err(format!("Error writing content: {}", err)),
};
match dat_file.seek(SeekFrom::Start(0)) {
Ok(_) => (),
Err(err) => return Err(format!("Error seeking in file: {}", err)),
}
match read_content(&tmp_path) {
Ok(content_bytes) => {
assert_eq!(&content_bytes, new_content);
assert_eq!(dat_file.content_size(), new_content.len() as u32 + 1);
Ok(())
}
Err(err) => Err(format!("Error reading file after write: {}", err)),
}
}
#[test]
fn test_datfile_write_error_over_max_size() -> Result<(), String> {
let tmp_dir = match tempdir() {
Ok(tmp_dir) => tmp_dir,
Err(err) => return Err(format!("Error creating temp dir: {}", err)),
};
let tmp_path = tmp_dir.path().join("TEST.DAT");
match copy(TEST_EMPTY_PATH, &tmp_path) {
Ok(_) => (),
Err(err) => return Err(format!("Could not create temp file for testing: {}", err)),
};
let mut opts = OpenOptions::new();
opts.read(true).write(true);
let mut dat_file = match DATFile::open_options(&tmp_path, &mut opts) {
Ok(dat_file) => dat_file,
Err(err) => return Err(format!("Error opening temp file: {}", err)),
};
let new_content = b"Looooooooooooooooooong!";
match dat_file.write(new_content) {
Ok(_) => Err("No error returned.".to_owned()),
Err(err) => match err.kind() {
std::io::ErrorKind::InvalidInput => Ok(()),
_ => Err(format!("Incorrect error: {}", err)),
},
}
}
}