diff --git a/src/main.rs b/src/main.rs index aca3fa2..adeea78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + #[derive(PartialEq, Debug, Clone, Copy)] struct Range { // todo: consider just using a map instead @@ -15,11 +17,11 @@ enum RangeLocation { } impl Range { - pub fn new(start: usize, end: usize) -> Self { + fn new(start: usize, end: usize) -> Self { Self { start, end } } - pub fn where_in_range(&self, index: usize) -> RangeLocation { + fn where_in_range(&self, index: usize) -> RangeLocation { if index == self.start { return RangeLocation::LeftEnd; } @@ -32,25 +34,30 @@ impl Range { RangeLocation::Middle } - pub fn is_empty(&self) -> bool { + fn contains(&self, range: &Range) -> bool { + range.start >= self.start && range.end <= self.end + } + + fn is_empty(&self) -> bool { self.start >= self.end } } pub struct Blob { + // todo: consider fallible_vec to detect OOM on append bytes: Vec, valid_ranges: Vec, } impl Blob { - pub fn new() -> Self { + fn new() -> Self { Self { bytes: Vec::new(), valid_ranges: Vec::new(), } } - pub fn append(&mut self, input: &[u8]) { + fn append(&mut self, input: &[u8]) { let new_range = Range { start: self.bytes.len(), end: self.bytes.len() + input.len(), @@ -67,8 +74,33 @@ impl Blob { } // todo: better way to do the return type? - pub fn read(&mut self, start: usize, len: usize, output: &mut [u8]) { + fn read(&mut self, start: usize, len: usize, output: &mut [u8]) -> Result<(), BlobError> { assert!(len == output.len()); // todo: add error handling + + let read_range = Range::new(start, start + len); + + // check read within bytes len + match self.valid_ranges.last() { + Some(last) if read_range.end <= last.end => {} + _ => return Err(BlobError::InvalidRange), + } + + // check bytes are not already read + let mut contains = false; + for valid_range in self.valid_ranges.iter() { + if valid_range.contains(&read_range) { + contains = true; + break; + } + if read_range.start >= valid_range.end { + return Err(BlobError::BytesAlreadyRead); + } + } + if !contains { + // Invalid range already caught above + return Err(BlobError::BytesAlreadyRead); + } + let slice = &mut self.bytes[start..start + len]; slice.swap_with_slice(output); @@ -106,11 +138,65 @@ impl Blob { i += 1; } self.valid_ranges = new_ranges; + Ok(()) + } +} + +// combine all errors into one, since there aren't that many +#[derive(Debug, PartialEq)] +enum BlobError { + BlobDNE, + BlobExists, + InvalidRange, + BytesAlreadyRead, +} + +struct BlobManager { + // fine if we assume: + // * IDs are sequentially generated + // * and IDs can be re-used + blobs: Vec>, +} + +impl BlobManager { + pub fn new() -> Self { + Self { blobs: Vec::new() } + } + + // todo: is usize appropriate datatype for ID? + pub fn create_blob(&mut self, id: usize) -> Result<(), BlobError> { + if let Some(Some(_)) = self.blobs.get_mut(id) { + return Err(BlobError::BlobExists); + } else if id >= self.blobs.len() { + // todo: better strategy possible? + self.blobs.resize_with(id + 1, || None); + } + self.blobs[id] = Some(Blob::new()); + Ok(()) + } + + pub fn append(&mut self, id: usize, input: &[u8]) -> Result<(), BlobError> { + if let Some(Some(b)) = self.blobs.get_mut(id) { + b.append(input); + return Ok(()); + } + return Err(BlobError::BlobDNE); + } + + pub fn read(&mut self, id: usize, start: usize, len: usize) -> Result, BlobError> { + if let Some(Some(b)) = self.blobs.get_mut(id) { + let mut read = vec![0; len]; + b.read(start, len, &mut read)?; // propagate error to caller + // todo: if read clears a blob entirely should the blob be removed? + Ok(read) + } else { + Err(BlobError::BlobDNE) + } } } fn main() { - println!("run cargo test instead") + println!("run cargo test instead"); } #[cfg(test)] @@ -129,6 +215,21 @@ mod tests { assert_eq!(range.where_in_range(1), RangeLocation::Middle); assert_eq!(range.where_in_range(2), RangeLocation::RightEnd); assert_eq!(range.where_in_range(3), RangeLocation::No); + + assert!(range.contains(&range2)); + assert!(range2.contains(&range)); + + let range3 = Range::new(0, 1); + assert!(range.contains(&range3)); + assert!(!range3.contains(&range)); + + let range4 = Range::new(2, 3); + assert!(range.contains(&range4)); + assert!(!range4.contains(&range)); + + let range5 = Range::new(2, 4); + assert!(!range.contains(&range5)); + assert!(!range5.contains(&range)); } #[test] @@ -154,6 +255,26 @@ mod tests { let mut read = vec![0; 1]; b.read(4, 1, &mut read); // middle read assert_eq!(b.valid_ranges, &[Range::new(3, 4), Range::new(5, 6)]); - println!("blob: {:?}", b.bytes); + } + + #[test] + fn test_blob_manager() { + let mut bm = BlobManager::new(); + bm.create_blob(0); + bm.append(0, &[b'a', b'b', b'c']); + let v = Vec::from([b'a', b'b']); + assert_eq!(bm.read(0, 0, 2).unwrap(), v); + + bm.create_blob(1); + bm.append(1, &[b'a', b'b', b'c']); + assert_eq!(bm.read(1, 0, 4).unwrap_err(), BlobError::InvalidRange); + + let read = bm.read(1, 0, 2).unwrap(); + assert_eq!(read, v); + assert_eq!(bm.read(1, 0, 2).unwrap_err(), BlobError::BytesAlreadyRead); + assert_eq!(bm.read(1, 1, 2).unwrap_err(), BlobError::BytesAlreadyRead); + + assert_eq!(bm.read(2, 0, 2).unwrap_err(), BlobError::BlobDNE); + assert_eq!(bm.create_blob(1).unwrap_err(), BlobError::BlobExists); } }