Skip to main content

moonpool_sim/storage/
memory.rs

1//! In-memory storage simulation with deterministic fault injection.
2//!
3//! This module provides `InMemoryStorage`, a low-level backing store for simulated
4//! files that follows TigerBeetle's pristine memory + fault bitmap pattern.
5//!
6//! ## Key Design Insight
7//!
8//! Pristine data stays clean. Faults are applied on READ, not stored in data.
9//! This allows toggling faults without data loss, making it easy to simulate
10//! various storage failure scenarios.
11//!
12//! ## TigerBeetle References
13//!
14//! - Fault bitmap pattern: storage simulation
15//! - Misdirected writes: lines 476-480
16//! - Overlay system for read-time fault injection
17
18use rand::{RngExt, SeedableRng};
19use rand_chacha::ChaCha8Rng;
20use std::io;
21
22/// Size of a disk sector in bytes.
23///
24/// Most storage devices operate in 512-byte sectors. Operations that don't
25/// align to sector boundaries may exhibit different behavior under faults.
26pub const SECTOR_SIZE: usize = 512;
27
28/// Maximum number of overlays for misdirected write simulation.
29///
30/// TigerBeetle uses 2 overlays per misdirected write:
31/// 1. Original data at intended target (so reads see old data)
32/// 2. New data at mistaken target (so reads see wrong data there)
33const MAX_OVERLAYS: usize = 2;
34
35/// A simple bitset for tracking sector states.
36///
37/// Used to track which sectors have been written and which have faults.
38/// Implements a compact representation using u64 words.
39#[derive(Debug, Clone)]
40pub struct SectorBitSet {
41    bits: Vec<u64>,
42    len: usize,
43}
44
45impl SectorBitSet {
46    /// Create a new bitset with capacity for the given number of sectors.
47    ///
48    /// All bits are initially unset (false).
49    pub fn new(num_sectors: usize) -> Self {
50        let num_words = num_sectors.div_ceil(64);
51        Self {
52            bits: vec![0; num_words],
53            len: num_sectors,
54        }
55    }
56
57    /// Calculate word and bit indices for a sector.
58    ///
59    /// # Panics
60    ///
61    /// Panics if `sector` is out of bounds.
62    fn indices(&self, sector: usize) -> (usize, usize) {
63        assert!(sector < self.len, "sector index out of bounds");
64        (sector / 64, sector % 64)
65    }
66
67    /// Set the bit for the given sector.
68    ///
69    /// # Panics
70    ///
71    /// Panics if `sector` is out of bounds.
72    pub fn set(&mut self, sector: usize) {
73        let (word, bit) = self.indices(sector);
74        self.bits[word] |= 1 << bit;
75    }
76
77    /// Clear the bit for the given sector.
78    ///
79    /// # Panics
80    ///
81    /// Panics if `sector` is out of bounds.
82    pub fn clear(&mut self, sector: usize) {
83        let (word, bit) = self.indices(sector);
84        self.bits[word] &= !(1 << bit);
85    }
86
87    /// Check if the bit for the given sector is set.
88    ///
89    /// # Panics
90    ///
91    /// Panics if `sector` is out of bounds.
92    pub fn is_set(&self, sector: usize) -> bool {
93        let (word, bit) = self.indices(sector);
94        (self.bits[word] & (1 << bit)) != 0
95    }
96
97    /// Return the number of sectors this bitset can track.
98    pub fn len(&self) -> usize {
99        self.len
100    }
101
102    /// Check if the bitset is empty (has zero capacity).
103    pub fn is_empty(&self) -> bool {
104        self.len == 0
105    }
106
107    /// Create a new bitset by copying set bits from another bitset.
108    ///
109    /// Only copies bits up to the minimum of both bitsets' lengths.
110    pub fn resize_copy(other: &Self, new_len: usize) -> Self {
111        let mut new_bitset = Self::new(new_len);
112        let copy_len = other.len.min(new_len);
113        for sector in 0..copy_len {
114            if other.is_set(sector) {
115                new_bitset.set(sector);
116            }
117        }
118        new_bitset
119    }
120}
121
122/// Overlay for misdirected write simulation.
123///
124/// When a misdirected write occurs, we need to show different data at
125/// specific offsets during reads without corrupting the pristine data.
126#[derive(Debug, Clone)]
127struct WriteOverlay {
128    /// Starting offset for this overlay
129    offset: u64,
130    /// Size of the overlay data
131    size: u32,
132    /// The data to show instead of pristine data
133    data: Vec<u8>,
134    /// Whether this overlay is currently active
135    active: bool,
136}
137
138/// Pending write waiting to be synced.
139///
140/// Used for crash simulation - pending writes may be lost or partially
141/// written if a crash occurs before sync.
142#[derive(Debug, Clone)]
143struct PendingWrite {
144    /// Starting offset of the write
145    offset: u64,
146    /// Data that was written
147    data: Vec<u8>,
148    /// If true, this write will be lost on crash (phantom write)
149    is_phantom: bool,
150}
151
152/// In-memory storage with deterministic fault injection.
153///
154/// This struct represents a simulated storage device that can inject
155/// various faults at read time while keeping pristine data intact.
156///
157/// # Design
158///
159/// - `data`: Pristine storage contents
160/// - `written`: Tracks which sectors have been written (unwritten sectors return random data)
161/// - `faults`: Tracks which sectors have faults (faulted sectors return corrupted data)
162/// - `overlays`: Temporary data overlays for misdirected write simulation
163/// - `pending_writes`: Writes that haven't been synced yet (may be lost on crash)
164///
165/// # Example
166///
167/// ```ignore
168/// use moonpool_sim::storage::memory::{InMemoryStorage, SECTOR_SIZE};
169///
170/// let mut storage = InMemoryStorage::new(4096, 42);
171/// storage.write(0, b"Hello, World!", true)?;
172///
173/// let mut buf = vec![0u8; 13];
174/// storage.read(0, &mut buf)?;
175/// assert_eq!(&buf, b"Hello, World!");
176/// ```
177#[derive(Debug)]
178pub struct InMemoryStorage {
179    /// Pristine data - faults are applied on read, not stored here
180    data: Vec<u8>,
181    /// Which sectors have been written
182    written: SectorBitSet,
183    /// Which sectors have faults (corruption applied on read)
184    faults: SectorBitSet,
185    /// Overlays for misdirected write simulation
186    overlays: [Option<WriteOverlay>; MAX_OVERLAYS],
187    /// Pending writes that haven't been synced
188    pending_writes: Vec<PendingWrite>,
189    /// Total size of the storage in bytes
190    size: u64,
191    /// Seed for deterministic random generation
192    seed: u64,
193}
194
195impl InMemoryStorage {
196    /// Create a new in-memory storage with the given size and seed.
197    ///
198    /// # Arguments
199    ///
200    /// * `size` - Total size of the storage in bytes
201    /// * `seed` - Seed for deterministic random generation (used for unwritten sector fill)
202    pub fn new(size: u64, seed: u64) -> Self {
203        let num_sectors = (size as usize).div_ceil(SECTOR_SIZE);
204        Self {
205            data: vec![0; size as usize],
206            written: SectorBitSet::new(num_sectors),
207            faults: SectorBitSet::new(num_sectors),
208            overlays: [const { None }; MAX_OVERLAYS],
209            pending_writes: Vec::new(),
210            size,
211            seed,
212        }
213    }
214
215    /// Get the total size of the storage in bytes.
216    pub fn size(&self) -> u64 {
217        self.size
218    }
219
220    /// Get the number of sectors in this storage.
221    pub fn num_sectors(&self) -> usize {
222        self.written.len()
223    }
224
225    /// Resize the storage to a new size.
226    ///
227    /// If the new size is larger, the storage is extended with zeros.
228    /// If the new size is smaller, the storage is truncated.
229    pub fn resize(&mut self, new_size: u64) {
230        let old_size = self.size;
231        self.size = new_size;
232
233        // Resize data buffer
234        self.data.resize(new_size as usize, 0);
235
236        // Resize sector bitmaps if needed
237        let new_num_sectors = (new_size as usize).div_ceil(SECTOR_SIZE);
238        let old_num_sectors = self.written.len();
239
240        if new_num_sectors != old_num_sectors {
241            self.written = SectorBitSet::resize_copy(&self.written, new_num_sectors);
242            self.faults = SectorBitSet::resize_copy(&self.faults, new_num_sectors);
243        }
244
245        // Log resize for debugging
246        tracing::trace!(
247            "InMemoryStorage resized from {} to {} bytes ({} to {} sectors)",
248            old_size,
249            new_size,
250            old_num_sectors,
251            new_num_sectors
252        );
253    }
254
255    /// Read data from storage, applying faults as needed.
256    ///
257    /// # Fault Application
258    ///
259    /// 1. Unwritten sectors are filled with deterministic random data
260    /// 2. Faulted sectors have deterministic corruption applied
261    /// 3. Active overlays are applied on top
262    ///
263    /// # Arguments
264    ///
265    /// * `offset` - Starting byte offset
266    /// * `buf` - Buffer to read into
267    ///
268    /// # Errors
269    ///
270    /// Returns an error if the read would go past the end of storage.
271    pub fn read(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
272        // Bounds check
273        let end = offset
274            .checked_add(buf.len() as u64)
275            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "offset overflow"))?;
276
277        if end > self.size {
278            return Err(io::Error::new(
279                io::ErrorKind::InvalidInput,
280                format!(
281                    "read past end of storage: offset={}, len={}, size={}",
282                    offset,
283                    buf.len(),
284                    self.size
285                ),
286            ));
287        }
288
289        // Copy pristine data to buffer
290        let offset_usize = offset as usize;
291        buf.copy_from_slice(&self.data[offset_usize..offset_usize + buf.len()]);
292
293        // Apply sector-level effects
294        let start_sector = offset_usize / SECTOR_SIZE;
295        let end_sector = (offset_usize + buf.len()).div_ceil(SECTOR_SIZE);
296
297        for sector in start_sector..end_sector {
298            if sector >= self.written.len() {
299                break;
300            }
301
302            // Calculate which part of the buffer corresponds to this sector
303            let sector_start = sector * SECTOR_SIZE;
304            let sector_end = sector_start + SECTOR_SIZE;
305
306            let buf_start = sector_start.saturating_sub(offset_usize);
307            let buf_end = (sector_end.saturating_sub(offset_usize)).min(buf.len());
308
309            if buf_start >= buf_end {
310                continue;
311            }
312
313            let sector_buf = &mut buf[buf_start..buf_end];
314
315            // If sector not written, fill with deterministic random
316            if !self.written.is_set(sector) {
317                self.fill_unwritten_sector(sector, sector_buf, sector_start, offset_usize);
318            }
319
320            // If sector has fault, apply corruption
321            if self.faults.is_set(sector) {
322                self.apply_corruption(sector, sector_buf);
323            }
324        }
325
326        // Apply active overlays
327        self.apply_overlays(offset, buf);
328
329        Ok(())
330    }
331
332    /// Fill unwritten sector data with deterministic random bytes.
333    fn fill_unwritten_sector(
334        &self,
335        sector: usize,
336        buf: &mut [u8],
337        sector_start: usize,
338        read_offset: usize,
339    ) {
340        // Use seed + sector as RNG seed for deterministic fill
341        let mut rng = ChaCha8Rng::seed_from_u64(self.seed.wrapping_add(sector as u64));
342
343        // Generate full sector of random data
344        let mut sector_data = [0u8; SECTOR_SIZE];
345        rng.fill(&mut sector_data);
346
347        // Copy relevant portion to buffer
348        let offset_in_sector = read_offset.saturating_sub(sector_start);
349        let copy_start = offset_in_sector.min(SECTOR_SIZE);
350        let copy_len = buf.len().min(SECTOR_SIZE - copy_start);
351
352        buf[..copy_len].copy_from_slice(&sector_data[copy_start..copy_start + copy_len]);
353    }
354
355    /// Apply deterministic corruption to a sector.
356    ///
357    /// Uses the pristine bytes as seed so retries don't help - the same
358    /// corruption will occur each time.
359    ///
360    /// TigerBeetle reference: lines 476-480
361    fn apply_corruption(&self, sector: usize, buf: &mut [u8]) {
362        if buf.is_empty() {
363            return;
364        }
365
366        // Use pristine bytes as seed so retries don't help
367        let sector_start = sector * SECTOR_SIZE;
368        let mut seed_bytes = [0u8; 8];
369        if sector_start + 8 <= self.data.len() {
370            seed_bytes.copy_from_slice(&self.data[sector_start..sector_start + 8]);
371        }
372        let seed = u64::from_le_bytes(seed_bytes);
373
374        let mut rng = ChaCha8Rng::seed_from_u64(seed);
375        let byte_idx = rng.random_range(0..buf.len());
376        let bit_idx = rng.random_range(0..8u8);
377        buf[byte_idx] ^= 1 << bit_idx;
378    }
379
380    /// Apply active overlays to the read buffer.
381    fn apply_overlays(&self, offset: u64, buf: &mut [u8]) {
382        for overlay in self.overlays.iter().flatten() {
383            if !overlay.active {
384                continue;
385            }
386
387            // Check if overlay intersects with read range
388            let overlay_end = overlay.offset + overlay.size as u64;
389            let read_end = offset + buf.len() as u64;
390
391            if overlay.offset >= read_end || overlay_end <= offset {
392                continue;
393            }
394
395            // Calculate intersection
396            let intersect_start = overlay.offset.max(offset);
397            let intersect_end = overlay_end.min(read_end);
398
399            let buf_offset = (intersect_start - offset) as usize;
400            let overlay_offset = (intersect_start - overlay.offset) as usize;
401            let copy_len = (intersect_end - intersect_start) as usize;
402
403            buf[buf_offset..buf_offset + copy_len]
404                .copy_from_slice(&overlay.data[overlay_offset..overlay_offset + copy_len]);
405        }
406    }
407
408    /// Write data to storage.
409    ///
410    /// The storage automatically extends to accommodate writes past the current size,
411    /// similar to how real file systems work.
412    ///
413    /// # Arguments
414    ///
415    /// * `offset` - Starting byte offset
416    /// * `data` - Data to write
417    /// * `is_synced` - If false, write is added to pending writes (may be lost on crash)
418    ///
419    /// # Errors
420    ///
421    /// Returns an error on overflow.
422    pub fn write(&mut self, offset: u64, data: &[u8], is_synced: bool) -> io::Result<()> {
423        // Bounds check (only for overflow)
424        let end = offset
425            .checked_add(data.len() as u64)
426            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "offset overflow"))?;
427
428        // Auto-extend storage if writing past current size
429        if end > self.size {
430            self.resize(end);
431        }
432
433        let offset_usize = offset as usize;
434
435        // Mark sectors as written and clear faults
436        let start_sector = offset_usize / SECTOR_SIZE;
437        let end_sector = (offset_usize + data.len()).div_ceil(SECTOR_SIZE);
438
439        for sector in start_sector..end_sector {
440            if sector < self.written.len() {
441                self.written.set(sector);
442                self.faults.clear(sector);
443            }
444        }
445
446        // Copy data to pristine storage
447        self.data[offset_usize..offset_usize + data.len()].copy_from_slice(data);
448
449        // If not synced, add to pending writes
450        if !is_synced {
451            self.pending_writes.push(PendingWrite {
452                offset,
453                data: data.to_vec(),
454                is_phantom: false,
455            });
456        }
457
458        Ok(())
459    }
460
461    /// Sync all pending writes, making them durable.
462    ///
463    /// After sync, pending writes are cleared and won't be affected by crash simulation.
464    pub fn sync(&mut self) {
465        self.pending_writes.clear();
466    }
467
468    /// Apply a misdirected write.
469    ///
470    /// Simulates a write that lands at the wrong location. Uses the TigerBeetle
471    /// 2-overlay pattern:
472    /// 1. Overlay 1: intended target shows old data on read
473    /// 2. Overlay 2: mistaken target shows new data on read
474    /// 3. Pristine memory is updated at intended target
475    ///
476    /// # Arguments
477    ///
478    /// * `intended_offset` - Where the write should have gone
479    /// * `mistaken_offset` - Where the write actually went
480    /// * `data` - The data that was written
481    ///
482    /// # Errors
483    ///
484    /// Returns an error on overflow.
485    pub fn apply_misdirected_write(
486        &mut self,
487        intended_offset: u64,
488        mistaken_offset: u64,
489        data: &[u8],
490    ) -> io::Result<()> {
491        // Bounds checks (only for overflow)
492        let intended_end = intended_offset
493            .checked_add(data.len() as u64)
494            .ok_or_else(|| {
495                io::Error::new(io::ErrorKind::InvalidInput, "intended offset overflow")
496            })?;
497
498        let mistaken_end = mistaken_offset
499            .checked_add(data.len() as u64)
500            .ok_or_else(|| {
501                io::Error::new(io::ErrorKind::InvalidInput, "mistaken offset overflow")
502            })?;
503
504        // Auto-extend storage to fit both intended and mistaken locations
505        let required_size = intended_end.max(mistaken_end);
506        if required_size > self.size {
507            self.resize(required_size);
508        }
509
510        // Save old data at intended target
511        let intended_usize = intended_offset as usize;
512        let old_data = self.data[intended_usize..intended_usize + data.len()].to_vec();
513
514        // Overlay 1: intended target shows old data on read
515        self.overlays[0] = Some(WriteOverlay {
516            offset: intended_offset,
517            size: data.len() as u32,
518            data: old_data,
519            active: true,
520        });
521
522        // Overlay 2: mistaken target shows new data on read
523        self.overlays[1] = Some(WriteOverlay {
524            offset: mistaken_offset,
525            size: data.len() as u32,
526            data: data.to_vec(),
527            active: true,
528        });
529
530        // Update pristine memory at intended target (the physical write happened)
531        self.data[intended_usize..intended_usize + data.len()].copy_from_slice(data);
532
533        // Mark sectors as written at intended target
534        let start_sector = intended_usize / SECTOR_SIZE;
535        let end_sector = (intended_usize + data.len()).div_ceil(SECTOR_SIZE);
536        for sector in start_sector..end_sector {
537            if sector < self.written.len() {
538                self.written.set(sector);
539            }
540        }
541
542        Ok(())
543    }
544
545    /// Clear all active overlays.
546    ///
547    /// Call this to reset the misdirection state.
548    pub fn clear_overlays(&mut self) {
549        for overlay in &mut self.overlays {
550            *overlay = None;
551        }
552    }
553
554    /// Read data from a misdirected location.
555    ///
556    /// Instead of reading from `offset`, reads from a different location
557    /// chosen deterministically based on the seed. The caller is responsible
558    /// for deciding when to call this vs regular `read()`.
559    ///
560    /// # Arguments
561    ///
562    /// * `offset` - The intended read offset (will read from somewhere else)
563    /// * `buf` - Buffer to read into
564    ///
565    /// # Errors
566    ///
567    /// Returns an error if the read would go past the end of storage.
568    pub fn read_misdirected(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
569        if buf.is_empty() {
570            return Ok(());
571        }
572
573        // Pick a random different offset using the original offset as seed
574        let mut rng = ChaCha8Rng::seed_from_u64(self.seed.wrapping_add(offset));
575
576        // Calculate valid range for misdirected read
577        let max_offset = self.size.saturating_sub(buf.len() as u64);
578        if max_offset == 0 {
579            // Buffer is larger than storage, just read from 0
580            return self.read(0, buf);
581        }
582
583        let mut misdirected_offset = rng.random_range(0..max_offset);
584
585        // Ensure we don't accidentally read from the intended location
586        if misdirected_offset == offset {
587            misdirected_offset = (misdirected_offset + SECTOR_SIZE as u64) % max_offset;
588        }
589
590        self.read(misdirected_offset, buf)
591    }
592
593    /// Record a phantom write.
594    ///
595    /// A phantom write appears to succeed but the data is never actually
596    /// persisted. The data is added to pending writes with `is_phantom: true`,
597    /// meaning it will be lost on crash without corrupting other data.
598    ///
599    /// # Arguments
600    ///
601    /// * `offset` - Starting byte offset
602    /// * `data` - Data that "appeared" to be written
603    pub fn record_phantom_write(&mut self, offset: u64, data: &[u8]) {
604        self.pending_writes.push(PendingWrite {
605            offset,
606            data: data.to_vec(),
607            is_phantom: true,
608        });
609        // Note: We do NOT update pristine data - the write didn't actually happen
610    }
611
612    /// Apply crash simulation to pending writes.
613    ///
614    /// For each pending non-phantom write, there's a chance that a sector
615    /// in the write range gets marked as faulted (simulating a torn write).
616    /// Phantom writes simply disappear.
617    ///
618    /// # Arguments
619    ///
620    /// * `crash_fault_probability` - Probability [0.0, 1.0] that each pending
621    ///   write experiences a crash fault
622    pub fn apply_crash(&mut self, crash_fault_probability: f64) {
623        let mut rng = ChaCha8Rng::seed_from_u64(self.seed);
624
625        for pending in &self.pending_writes {
626            if pending.is_phantom {
627                // Phantom writes just disappear - nothing to do
628                continue;
629            }
630
631            // Check if this write experiences a crash fault
632            if rng.random::<f64>() >= crash_fault_probability {
633                continue;
634            }
635
636            // Pick a random sector in the write range to fault
637            let offset_usize = pending.offset as usize;
638            let start_sector = offset_usize / SECTOR_SIZE;
639            let end_sector = (offset_usize + pending.data.len()).div_ceil(SECTOR_SIZE);
640
641            if start_sector < end_sector && end_sector <= self.faults.len() {
642                let faulted_sector = rng.random_range(start_sector..end_sector);
643                self.faults.set(faulted_sector);
644            }
645        }
646
647        // Clear all pending writes
648        self.pending_writes.clear();
649    }
650
651    /// Manually set a sector as faulted.
652    ///
653    /// Useful for testing specific fault scenarios.
654    pub fn set_fault(&mut self, sector: usize) {
655        if sector < self.faults.len() {
656            self.faults.set(sector);
657        }
658    }
659
660    /// Manually clear a sector's fault.
661    pub fn clear_fault(&mut self, sector: usize) {
662        if sector < self.faults.len() {
663            self.faults.clear(sector);
664        }
665    }
666
667    /// Check if a sector has a fault.
668    pub fn has_fault(&self, sector: usize) -> bool {
669        sector < self.faults.len() && self.faults.is_set(sector)
670    }
671
672    /// Check if a sector has been written.
673    pub fn is_written(&self, sector: usize) -> bool {
674        sector < self.written.len() && self.written.is_set(sector)
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use super::*;
681
682    #[test]
683    fn test_basic_write_read() {
684        let mut storage = InMemoryStorage::new(4096, 42);
685
686        // Write data
687        let data = b"Hello, World!";
688        storage.write(0, data, true).expect("write failed");
689
690        // Read it back
691        let mut buf = vec![0u8; data.len()];
692        storage.read(0, &mut buf).expect("read failed");
693
694        assert_eq!(&buf, data);
695    }
696
697    #[test]
698    fn test_unwritten_sector_deterministic() {
699        let storage1 = InMemoryStorage::new(4096, 42);
700        let storage2 = InMemoryStorage::new(4096, 42);
701
702        // Read from unwritten sector
703        let mut buf1 = vec![0u8; SECTOR_SIZE];
704        let mut buf2 = vec![0u8; SECTOR_SIZE];
705
706        storage1.read(0, &mut buf1).expect("read1 failed");
707        storage2.read(0, &mut buf2).expect("read2 failed");
708
709        // Same seed should produce same random fill
710        assert_eq!(buf1, buf2);
711
712        // Different seed should produce different fill
713        let storage3 = InMemoryStorage::new(4096, 99);
714        let mut buf3 = vec![0u8; SECTOR_SIZE];
715        storage3.read(0, &mut buf3).expect("read3 failed");
716
717        assert_ne!(buf1, buf3);
718    }
719
720    #[test]
721    fn test_fault_corruption() {
722        let mut storage = InMemoryStorage::new(4096, 42);
723
724        // Write data
725        let data = vec![0xAA; SECTOR_SIZE];
726        storage.write(0, &data, true).expect("write failed");
727
728        // Read without fault
729        let mut buf_clean = vec![0u8; SECTOR_SIZE];
730        storage.read(0, &mut buf_clean).expect("read failed");
731        assert_eq!(buf_clean, data);
732
733        // Set fault and read again
734        storage.set_fault(0);
735        let mut buf_faulted = vec![0u8; SECTOR_SIZE];
736        storage.read(0, &mut buf_faulted).expect("read failed");
737
738        // Data should be corrupted (exactly one bit flipped)
739        assert_ne!(buf_faulted, data);
740
741        // Count bit differences
742        let bit_diffs: u32 = buf_clean
743            .iter()
744            .zip(buf_faulted.iter())
745            .map(|(a, b)| (*a ^ *b).count_ones())
746            .sum();
747        assert_eq!(bit_diffs, 1, "Expected exactly one bit flip");
748    }
749
750    #[test]
751    fn test_corruption_determinism() {
752        let mut storage = InMemoryStorage::new(4096, 42);
753
754        // Write data
755        let data = vec![0xAA; SECTOR_SIZE];
756        storage.write(0, &data, true).expect("write failed");
757        storage.set_fault(0);
758
759        // Read multiple times
760        let mut buf1 = vec![0u8; SECTOR_SIZE];
761        let mut buf2 = vec![0u8; SECTOR_SIZE];
762        storage.read(0, &mut buf1).expect("read1 failed");
763        storage.read(0, &mut buf2).expect("read2 failed");
764
765        // Same fault should produce same corruption
766        assert_eq!(buf1, buf2);
767    }
768
769    #[test]
770    fn test_misdirected_write() {
771        let mut storage = InMemoryStorage::new(4096, 42);
772
773        // Write initial data at both locations
774        let original_intended = vec![0x11; SECTOR_SIZE];
775        let original_mistaken = vec![0x22; SECTOR_SIZE];
776        storage
777            .write(0, &original_intended, true)
778            .expect("write1 failed");
779        storage
780            .write(SECTOR_SIZE as u64, &original_mistaken, true)
781            .expect("write2 failed");
782
783        // Apply misdirected write: intended=0, mistaken=SECTOR_SIZE
784        let new_data = vec![0xFF; SECTOR_SIZE];
785        storage
786            .apply_misdirected_write(0, SECTOR_SIZE as u64, &new_data)
787            .expect("misdirect failed");
788
789        // Read from intended location - should see old data (overlay)
790        let mut buf_intended = vec![0u8; SECTOR_SIZE];
791        storage.read(0, &mut buf_intended).expect("read failed");
792        assert_eq!(buf_intended, original_intended);
793
794        // Read from mistaken location - should see new data (overlay)
795        let mut buf_mistaken = vec![0u8; SECTOR_SIZE];
796        storage
797            .read(SECTOR_SIZE as u64, &mut buf_mistaken)
798            .expect("read failed");
799        assert_eq!(buf_mistaken, new_data);
800
801        // Clear overlays and verify pristine state
802        storage.clear_overlays();
803        storage.read(0, &mut buf_intended).expect("read failed");
804        assert_eq!(buf_intended, new_data); // Pristine was updated
805    }
806
807    #[test]
808    fn test_phantom_write_lost_on_crash() {
809        let mut storage = InMemoryStorage::new(4096, 42);
810
811        // Write real data
812        let real_data = vec![0x11; SECTOR_SIZE];
813        storage.write(0, &real_data, true).expect("write failed");
814
815        // Record phantom write (different data)
816        let phantom_data = vec![0xFF; SECTOR_SIZE];
817        storage.record_phantom_write(0, &phantom_data);
818
819        // Read before crash - should see real data (phantom wasn't persisted)
820        let mut buf = vec![0u8; SECTOR_SIZE];
821        storage.read(0, &mut buf).expect("read failed");
822        assert_eq!(buf, real_data);
823
824        // Apply crash - phantom write just disappears
825        storage.apply_crash(1.0);
826
827        // Read after crash - should still see real data
828        storage.read(0, &mut buf).expect("read failed");
829        assert_eq!(buf, real_data);
830    }
831
832    #[test]
833    fn test_crash_faults_pending_writes() {
834        let mut storage = InMemoryStorage::new(4096, 42);
835
836        // Write data without sync
837        let data = vec![0xAA; SECTOR_SIZE];
838        storage.write(0, &data, false).expect("write failed");
839
840        // Apply crash with 100% fault probability
841        storage.apply_crash(1.0);
842
843        // The sector should now be faulted
844        assert!(storage.has_fault(0));
845
846        // Reading should return corrupted data
847        let mut buf = vec![0u8; SECTOR_SIZE];
848        storage.read(0, &mut buf).expect("read failed");
849        assert_ne!(buf, data);
850    }
851
852    #[test]
853    fn test_sync_clears_pending() {
854        let mut storage = InMemoryStorage::new(4096, 42);
855
856        // Write data without sync
857        let data = vec![0xAA; SECTOR_SIZE];
858        storage.write(0, &data, false).expect("write failed");
859
860        // Sync
861        storage.sync();
862
863        // Apply crash - should not affect synced write
864        storage.apply_crash(1.0);
865
866        // Sector should not be faulted
867        assert!(!storage.has_fault(0));
868
869        // Data should be intact
870        let mut buf = vec![0u8; SECTOR_SIZE];
871        storage.read(0, &mut buf).expect("read failed");
872        assert_eq!(buf, data);
873    }
874
875    #[test]
876    fn test_sector_bitset() {
877        let mut bitset = SectorBitSet::new(100);
878
879        assert!(!bitset.is_set(0));
880        assert!(!bitset.is_set(50));
881        assert!(!bitset.is_set(99));
882
883        bitset.set(0);
884        bitset.set(50);
885        bitset.set(99);
886
887        assert!(bitset.is_set(0));
888        assert!(bitset.is_set(50));
889        assert!(bitset.is_set(99));
890        assert!(!bitset.is_set(1));
891
892        bitset.clear(50);
893        assert!(!bitset.is_set(50));
894
895        assert_eq!(bitset.len(), 100);
896    }
897
898    #[test]
899    fn test_read_past_end() {
900        let storage = InMemoryStorage::new(1024, 42);
901
902        let mut buf = vec![0u8; 100];
903        let result = storage.read(1000, &mut buf);
904
905        assert!(result.is_err());
906    }
907
908    #[test]
909    fn test_write_past_end_auto_extends() {
910        let mut storage = InMemoryStorage::new(1024, 42);
911
912        // Writing past end should auto-extend the storage (like real file systems)
913        let data = vec![0xAB; 100];
914        let result = storage.write(1000, &data, true);
915
916        assert!(result.is_ok());
917        assert_eq!(storage.size(), 1100); // Extended to fit write
918
919        // Verify the data was written
920        let mut read_buf = vec![0u8; 100];
921        storage.read(1000, &mut read_buf).expect("read failed");
922        assert_eq!(read_buf, data);
923    }
924
925    #[test]
926    fn test_read_misdirected() {
927        let mut storage = InMemoryStorage::new(4096, 42);
928
929        // Write distinct data at different locations
930        let data0 = vec![0x11; SECTOR_SIZE];
931        let data1 = vec![0x22; SECTOR_SIZE];
932        let data2 = vec![0x33; SECTOR_SIZE];
933
934        storage.write(0, &data0, true).expect("write failed");
935        storage
936            .write(SECTOR_SIZE as u64, &data1, true)
937            .expect("write failed");
938        storage
939            .write(2 * SECTOR_SIZE as u64, &data2, true)
940            .expect("write failed");
941
942        // Misdirected read from offset 0 should return data from somewhere else
943        let mut buf = vec![0u8; SECTOR_SIZE];
944        storage.read_misdirected(0, &mut buf).expect("read failed");
945
946        // Should NOT be the data at offset 0
947        assert_ne!(buf, data0);
948    }
949
950    #[test]
951    fn test_partial_sector_read() {
952        let mut storage = InMemoryStorage::new(4096, 42);
953
954        // Write a full sector with repeating pattern
955        let data: Vec<u8> = (0..SECTOR_SIZE).map(|i| (i % 256) as u8).collect();
956        storage.write(0, &data, true).expect("write failed");
957
958        // Read partial sector
959        let mut buf = vec![0u8; 100];
960        storage.read(50, &mut buf).expect("read failed");
961
962        assert_eq!(buf, &data[50..150]);
963    }
964
965    #[test]
966    fn test_multi_sector_read() {
967        let mut storage = InMemoryStorage::new(4096, 42);
968
969        // Write across multiple sectors
970        let data = vec![0xAB; SECTOR_SIZE * 3];
971        storage.write(0, &data, true).expect("write failed");
972
973        // Read it all back
974        let mut buf = vec![0u8; SECTOR_SIZE * 3];
975        storage.read(0, &mut buf).expect("read failed");
976
977        assert_eq!(buf, data);
978    }
979}