use thiserror::Error; use std::io::{Seek, SeekFrom, Write}; use std::fmt::Debug; #[derive(Debug, Default)] /// Stores write and seek operations to be replayed later. /// # Example /// ``` /// use yadon::Yadon; /// use std::io::{Cursor, Write, Seek, SeekFrom}; /// let mut target = vec![0u8; 8]; /// let mut yadon = Yadon::new(Some(0), Some(target.len() as u64)); /// assert_eq!(yadon.seek(SeekFrom::Start(4)).unwrap(), 4); /// assert_eq!(yadon.write(&[1,2,3]).unwrap(), 3); /// assert_eq!(yadon.seek(SeekFrom::Current(0)).unwrap(), 7); /// assert_eq!(yadon.seek(SeekFrom::End(-6)).unwrap(), 2); /// assert_eq!(yadon.write(&[4,5]).unwrap(), 2); /// assert_eq!(yadon.seek(SeekFrom::Current(0)).unwrap(), 4); /// /// let mut target_writer = Cursor::new(&mut target); /// // Apply the stored operations to our target writer. /// // Pass `true` so the return values of the seeks and writes are compared with the simulated values. /// yadon.apply(&mut target_writer, true).unwrap(); /// assert_eq!(target, &[0, 0, 4, 5, 1, 2, 3, 0]); /// ``` /// # Remarks /// * If a start position is set when apply() is called, the target will seek to the start position. /// * Boundary checks are only performed during `write()` if length is specified. pub struct Yadon { /// Stored operations pub operations: Vec, /// Virtual position to use for emulating the return values of another Write + Seek virtual_position: Option, /// If set, used to set the initial virtual cursor position. `apply()` will seek to this position before applying. pub start: Option, /// If set, used to emulate cursor position for SeekFrom::End operations. If not set, seeks involving SeekFrom::End will fail, returning `Err(std::io::ErrorKind::Unsupported)` pub length: Option, } /// Errors that may occur while applying `Yadon`. #[derive(Error, Debug)] pub enum ApplyError { /// IO error while trying to replay operations. #[error("io error while trying to replay operations")] Io(#[from] std::io::Error), /// Seek position diverged while trying to replay operations. #[error("seek position diverged while trying to replay operations")] SeekDiverged(Confusion), /// Number of bytes written diverged while trying to replay operations. #[error("number of bytes written diverged while trying to replay operations")] NumBytesWrittenDiverge(Confusion) } /// During apply, there was divergence between the expected return value of an operation, and its result. #[derive(Debug)] pub struct Confusion where T: Debug { /// The value which we returned when the operation was first simulated. pub expected: T, /// The value which was returned when trying to apply this operation to another Write + Seek. pub actual: T, } #[derive(Debug)] /// Write + Seek operations which were called on Yadon pub enum WriteOperation { /// Write something, and check that the number of bytes written matches. Write(Vec, usize), /// Seek somewhere, and check that the resulting position matches. Seek(SeekFrom, u64) } impl Yadon { /// Constructs an instance of `Yadon` with optional `start` position and `length`, which, if set, should match /// whatever you plan to apply `Yadon` to later. pub fn new(start: Option, length: Option) -> Self { Yadon { operations: vec![], virtual_position: None, start, length, } } /// Applies the stored operations on a target writer. Operations are not consumed, and may be replayed again. /// If a `start` position was specified, this will seek to that position before applying. /// If `check_return_values` is set, the result of each seek / write will be compared to the /// simulated return value, and the apply will fail if it is different. pub fn apply(&self, target: &mut T, check_return_values: bool) -> Result where T: Write + Seek { if let Some(start) = self.start { let seek_pos = target.seek(SeekFrom::Start(start))?; if check_return_values && seek_pos != start { // Something is wrong with the seek. return Err(ApplyError::SeekDiverged(Confusion { expected: start, actual: seek_pos })); } } let mut total_bytes_written: usize = 0; for operation in &self.operations { match operation { WriteOperation::Write(data, expected_bytes_written) => { let bytes_written = target.write(data)?; if check_return_values && *expected_bytes_written != bytes_written { return Err(ApplyError::NumBytesWrittenDiverge(Confusion{ expected: *expected_bytes_written, actual: bytes_written })); } total_bytes_written += bytes_written; }, WriteOperation::Seek(pos, expected_position) => { let new_position = target.seek(*pos)?; if check_return_values && new_position != *expected_position { return Err(ApplyError::SeekDiverged(Confusion{ expected: *expected_position, actual: new_position })); } } } } target.flush()?; Ok(total_bytes_written) } } impl Write for Yadon { fn write(&mut self, buf: &[u8]) -> std::io::Result { if let (None, Some(start), Some(_)) = (self.virtual_position, self.start, self.length) { // If the start position is specified and this is the first operation, and we're doing length // emulation, the virtual position must be initialized. self.virtual_position = Some(start); } let buf = match self.length { Some(max_length) => { // Emulate writing into something with a max length let available_space = match self.virtual_position { Some(current_position) => (max_length - current_position) as usize, None => max_length as usize }; if buf.len() > available_space { &buf[0..available_space] } else { buf } }, None => { buf } }; self.virtual_position = match self.virtual_position { Some(current_position) => Some(current_position + buf.len() as u64), None => Some(buf.len() as u64) }; self.operations.push(WriteOperation::Write(buf.into(), buf.len())); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl Seek for Yadon { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { match (self.virtual_position, pos, self.start, self.length) { (_, SeekFrom::Start(from_start), _, _) => { self.virtual_position = Some(from_start); } (None, SeekFrom::Current(from_current), Some(start_position), _) => { self.virtual_position = Some((start_position as i64 + from_current) as u64); } (_, SeekFrom::End(from_end), _, Some(length)) => { self.virtual_position = Some((length as i64 + from_end) as u64); } (Some(current_pos), SeekFrom::Current(from_current), _, _) => { self.virtual_position = Some((current_pos as i64 + from_current) as u64) } (_, SeekFrom::End(_), _, None) => { self.virtual_position = None; // This will return an ErrorKind::Unsupported. } (None, SeekFrom::Current(from_current), None, _) => { self.virtual_position = Some(from_current as u64); // If a start waas not specified, assume we're at position 0. } } match self.virtual_position { Some(resulting_position) => { self.operations.push(WriteOperation::Seek(pos, resulting_position)); Ok(resulting_position) }, None => Err(std::io::ErrorKind::Unsupported.into()), } } } #[cfg(test)] mod tests { use std::io::{Cursor, Seek, SeekFrom, Write}; use crate::{ApplyError, Yadon}; #[test] fn delayed_write() { let mut yadon = Yadon::new(Some(0), Some(16)); assert_eq!(yadon.seek(SeekFrom::Start(4)).unwrap(), 4); assert_eq!(yadon.write(&[1,2,3]).unwrap(), 3); assert_eq!(yadon.seek(SeekFrom::End(-2)).unwrap(), 14); assert_eq!(yadon.write(&[4,5]).unwrap(), 2); assert_eq!(yadon.seek(SeekFrom::Current(-6)).unwrap(), 10); assert_eq!(yadon.write(&[6,7,8]).unwrap(), 3); let mut target = vec![0u8; 16]; let mut target_writer = Cursor::new(&mut target); yadon.apply(&mut target_writer, true).unwrap(); assert_eq!(target, &[0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 6, 7, 8, 0, 4, 5]); } #[test] fn start_and_end() { let mut yadon = Yadon::new(Some(1), Some(4)); assert_eq!(yadon.seek(SeekFrom::Current(2)).unwrap(), 3); yadon.write(&[1]).unwrap(); assert_eq!(yadon.seek(SeekFrom::End(-3)).unwrap(), 1); yadon.write(&[2]).unwrap(); let mut target = vec![0u8; 4]; let mut target_writer = Cursor::new(&mut target); yadon.apply(&mut target_writer, true).unwrap(); assert_eq!(target, &[0, 2, 0, 1]); } #[test] fn unspecified_length_end_seek_fails() { let mut yadon = Yadon::new(None, None); assert_eq!(yadon.seek(SeekFrom::End(-3)).map_err(|e| e.kind()), Err(std::io::ErrorKind::Unsupported.into())); } #[test] fn unspecified_start_current_seek_assumes_0() { let mut yadon = Yadon::new(None, None); assert_eq!(yadon.seek(SeekFrom::Current(2)).unwrap(), 2); } #[test] fn too_big_write() { let mut now_target = [0u8; 32]; let mut now = Cursor::new(&mut now_target[..]); now.seek(SeekFrom::Start(1)).unwrap(); let mut yadon = Yadon::new(Some(1), Some(32)); // Too big since we start at position 1 assert_eq!(assert_multi_write(&mut now, &mut yadon, &[1u8; 64]).unwrap(), 31); assert_multi_seek(&mut now, &mut yadon, SeekFrom::End(-16)).unwrap(); assert_eq!(assert_multi_write(&mut now, &mut yadon, &[2u8; 17]).unwrap(), 16); assert_multi_seek(&mut now, &mut yadon, SeekFrom::Start(31)).unwrap(); assert_eq!(assert_multi_write(&mut now, &mut yadon, &[3u8; 2]).unwrap(), 1); assert_eq!(assert_multi_write(&mut now, &mut yadon, &[4u8; 2]).unwrap(), 0); assert_multi_seek(&mut now, &mut yadon, SeekFrom::End(0)).unwrap(); assert_eq!(assert_multi_write(&mut now, &mut yadon, &[5u8; 2]).unwrap(), 0); assert_multi_write(&mut now, &mut yadon, &[1]).unwrap(); assert_multi_write(&mut now, &mut yadon, &[1]).unwrap(); println!("{:?}", yadon); let mut later_target = vec![0u8; 32]; // Using a vec here because it can grow if we've made a mistake let mut later_writer = Cursor::new(&mut later_target[..]); yadon.apply(&mut later_writer, true).unwrap(); assert_eq!(&later_target, &now_target); } #[test] fn return_values() { let mut now_target = [0u8; 128]; let mut now = Cursor::new(&mut now_target[..]); now.seek(SeekFrom::Start(27)).unwrap(); let mut yadon = Yadon::new(Some(27), Some(128)); assert_multi_seek(&mut now, &mut yadon, SeekFrom::Current(0)).unwrap(); assert_multi_seek(&mut now, &mut yadon, SeekFrom::Current(4)).unwrap(); assert_multi_write(&mut now, &mut yadon, &[1,2,3,4,5]).unwrap(); assert_multi_seek(&mut now, &mut yadon, SeekFrom::Current(-2)).unwrap(); assert_multi_write(&mut now, &mut yadon, &[1,2,3,4,5]).unwrap(); assert_multi_seek(&mut now, &mut yadon, SeekFrom::Start(27)).unwrap(); assert_multi_seek(&mut now, &mut yadon, SeekFrom::Current(2)).unwrap(); assert_multi_write(&mut now, &mut yadon, &[1,2]).unwrap(); assert_multi_seek(&mut now, &mut yadon, SeekFrom::End(-12)).unwrap(); assert_multi_write(&mut now, &mut yadon, &[12; 14]).unwrap(); let mut later_target = vec![0u8; 128]; // Using a vec here because it can grow if we've made a mistake let mut later_writer = Cursor::new(&mut later_target[..]); yadon.apply(&mut later_writer, true).unwrap(); assert_eq!(&later_target, &now_target); } #[test] fn failed_apply_write_end() { // mismatched sizes between yadon and the target let mut yadon = Yadon::new(Some(1), Some(4)); assert_eq!(yadon.seek(SeekFrom::End(-3)).unwrap(), 1); yadon.write(&[2]).unwrap(); let mut target = vec![0u8; 8]; let mut target_writer = Cursor::new(&mut target); // we should be able to successfully apply this if check_return_values is false. yadon.apply(&mut target_writer, false).unwrap(); // but if it's true, we should get an error match yadon.apply(&mut target_writer, true) { Err(ApplyError::SeekDiverged(diff)) => { assert_eq!(diff.expected, 1); assert_eq!(diff.actual, 5); }, res => { assert!(false, "Apply did not fail with a diverged seek: {:?}", res); } } } #[test] fn apply_smaller_than_target() { let mut yadon = Yadon::new(Some(1), Some(4)); yadon.write(&[0; 6]).unwrap(); let mut target = vec![0u8; 8]; let mut target_writer = Cursor::new(&mut target); assert_eq!(yadon.apply(&mut target_writer, true).unwrap(), 3); } #[test] fn failed_apply_write_too_much() { // mismatched sizes between yadon and the target let mut yadon = Yadon::new(Some(1), Some(8)); yadon.write(&[0; 6]).unwrap(); let mut target = [0u8; 4]; let mut target_writer = Cursor::new(&mut target[..]); // we should be able to successfully write this if check_return_values is false. yadon.apply(&mut target_writer, false).unwrap(); // but if it's true, we should get an error match yadon.apply(&mut target_writer, true) { Err(ApplyError::NumBytesWrittenDiverge(diff)) => { assert_eq!(diff.expected, 6); assert_eq!(diff.actual, 3); }, res => { assert!(false, "Apply did not fail with a diverged write: {:?}", res); } } } #[test] fn cannot_seek_end_without_length() { // mismatched sizes between yadon and the target let mut yadon = Yadon::new(Some(3), None); yadon.write(&[0; 6]).unwrap(); assert_eq!(yadon.seek(SeekFrom::End(-2)).map_err(|e| e.kind()), Err(std::io::ErrorKind::Unsupported.into())); } fn assert_multi_write(a: &mut T1, b: &mut T2, buf: &[u8]) -> std::io::Result where T1: Write + Seek, T2: Write + Seek { let result1 = a.write(buf); let result2 = b.write(buf); match (result1, result2) { (Ok(a_bytes), Ok(b_bytes)) => { println!("{}, {} written", a_bytes, b_bytes); assert_eq!(a_bytes, b_bytes); Ok(a_bytes) }, (a_res, b_res) => { assert!(false, "results differ: {:?} and {:?}", a_res, b_res); a_res } } } fn assert_multi_seek(a: &mut T1, b: &mut T2, pos: SeekFrom) -> std::io::Result where T1: Write + Seek, T2: Write + Seek { let result1 = a.seek(pos); let result2 = b.seek(pos); match (result1, result2) { (Ok(a_pos), Ok(b_pos)) => { println!("{}, {} seeked", a_pos, b_pos); assert_eq!(a_pos, b_pos); Ok(a_pos) }, (a_res, b_res) => { assert!(false, "results differ: {:?} and {:?}", a_res, b_res); a_res } } } }