From e2c0acf1a7c44218550964412b452e1e95a367fd Mon Sep 17 00:00:00 2001 From: Ed Guloien Date: Thu, 28 May 2026 10:11:07 -0400 Subject: [PATCH] init blob storage: added basic Blob and Range, testing wip --- .gitignore | 1 + Cargo.lock | 7 ++++ Cargo.toml | 6 ++++ src/main.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f826051 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "blob-storage" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..612cd1e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "blob-storage" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f13262b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,95 @@ + +#[derive(PartialEq, Debug)] +struct Range { + start: usize, + end: usize, // exclusive +} + +impl Range { + pub fn contains(&self, index: usize) -> bool { + index >= self.start && index < self.end + } +} + +pub struct Blob { + bytes: Vec, + valid_ranges: Vec, +} + +impl Blob { + pub fn new() -> Self { Self {bytes: Vec::new(), valid_ranges: Vec::new()} } + + pub fn append(&mut self, input: &[u8]) { + self.bytes.extend_from_slice(input); + + // we are appending so only have to check the last range + let new_range = Range {start: self.bytes.len(), end: self.bytes.len() + input.len()}; + match self.valid_ranges.last_mut() { + Some(range) if range.contains(new_range.start) => range.end = new_range.end, + _ => self.valid_ranges.push(new_range), + } + } + + // todo: better way to do the return type? + pub fn read(&mut self, start: usize, len: usize, output: &mut [u8]) { + assert!(len == output.len()); // todo: add error handling + let slice = &mut self.bytes[start..start+len]; + slice.swap_with_slice(output); + + // update the valid array + let clear_range = Range{ start: start, end: start + len }; + for range in self.valid_ranges.iter_mut() { + if range.contains(clear_range.start) { + range.end = clear_range.start; + } + if range.contains(clear_range.end) { + range.start = clear_range.end; + } + } + } +} + +fn main() { + println!("run cargo test instead") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_range() { + let range = Range{start:0, end:3}; + assert_eq!(range, range); // identity + + let range2 = Range{start:0, end:3}; + assert_eq!(range, range2); // similar + + // basic contains + assert!(range.contains(0)); + assert!(range.contains(1)); + assert!(range.contains(2)); + assert!(!range.contains(3)); + assert!(!range.contains(4)); + } + + #[test] + fn test_blob() { + let mut b = Blob::new(); + b.append(&[b'a', b'b', b'c']); + + let mut read = vec![0; 1]; + b.read(0,1, &mut read); + // todo make some asserts + //assert!(&read, Vec::new([b'a'])); + println!("blob (0,1): {}", str::from_utf8(&read).unwrap()); + + let mut read = vec![0; 1]; + b.read(0,1, &mut read); + println!("blob (0,1) again: {}", str::from_utf8(&read).unwrap()); + + let mut read = vec![0; 2]; + b.read(1,2, &mut read); + println!("blob (1,2) again: {}", str::from_utf8(&read).unwrap()); + } +}