Skip to main content

qualia_core_db/
storage.rs

1//! SuperBlock Storage Engine
2//! Handles flushing 850 48-byte Quins into perfectly aligned 40,960-byte
3//! QualiaSuperBlock structures onto the NVMe disk format (`.qla`).
4
5pub mod mmap;
6
7use crate::{NQuin, QualiaSuperBlock, BLOCK_MULTIPLIER_SIZE, QUINS_PER_BLOCK};
8use std::fs::{File, OpenOptions};
9use std::io::{self, Seek, SeekFrom};
10use std::path::Path;
11
12/// The Physical I/O Persistence Writer.
13pub struct SuperBlockWriter {
14    file: File,
15    current_offset: u64,
16}
17
18impl SuperBlockWriter {
19    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
20        let mut file = OpenOptions::new()
21            .create(true)
22            .write(true)
23            .read(true)
24            .open(path)?;
25
26        let current_offset = file.seek(SeekFrom::End(0))?;
27
28        Ok(Self {
29            file,
30            current_offset,
31        })
32    }
33
34    /// Flushes an array of exact 850 Quins into a hardware-aligned block on disk.
35    pub fn flush_block(
36        &mut self,
37        sequence_id: u64,
38        owner_did: u64,
39        quins: &[NQuin; QUINS_PER_BLOCK],
40    ) -> io::Result<()> {
41        let mut block = Box::new(unsafe { std::mem::zeroed::<QualiaSuperBlock>() });
42
43        block.block_sequence_id = sequence_id;
44        block.storage_owner_did = owner_did;
45        block.active_quin_count = QUINS_PER_BLOCK as u64;
46        block.hardware_profile_flags = 0x01; // Edge device default profile
47
48        // Copy the array iteratively to avoid unaligned access panics
49        for i in 0..QUINS_PER_BLOCK {
50            block.quin_ledger[i] = quins[i];
51        }
52
53        // Real integrity checksum over the quin payload — CRC-32C, the same
54        // routine the .10d/.hmc containers use (replaces a former mock
55        // `0xABCD`). Computed over the ledger only (not the header), so a
56        // reader can recompute it over the same region. Complementary to the
57        // per-quin XOR-parity ECC: this covers the whole ledger in one word.
58        block.validation_checksum = {
59            let ledger_bytes = unsafe {
60                std::slice::from_raw_parts(
61                    block.quin_ledger.as_ptr() as *const u8,
62                    QUINS_PER_BLOCK * core::mem::size_of::<NQuin>(),
63                )
64            };
65            crate::container_10d::crc32c::crc32c(ledger_bytes)
66        };
67
68        // Convert the aligned struct directly into a byte slice
69        let bytes = unsafe {
70            std::slice::from_raw_parts(
71                (block.as_ref() as *const QualiaSuperBlock) as *const u8,
72                BLOCK_MULTIPLIER_SIZE,
73            )
74        };
75
76        #[cfg(target_family = "unix")]
77        {
78            use std::os::unix::fs::FileExt;
79            self.file.write_all_at(bytes, self.current_offset)?;
80        }
81
82        #[cfg(target_family = "windows")]
83        {
84            use std::os::windows::fs::FileExt;
85            let mut written = 0;
86            while written < BLOCK_MULTIPLIER_SIZE {
87                let n = self
88                    .file
89                    .seek_write(&bytes[written..], self.current_offset + written as u64)?;
90                if n == 0 {
91                    return Err(io::Error::new(
92                        io::ErrorKind::WriteZero,
93                        "Failed to write whole block",
94                    ));
95                }
96                written += n;
97            }
98        }
99
100        #[cfg(not(any(target_family = "unix", target_family = "windows")))]
101        {
102            use std::io::Write;
103            self.file.write_all(bytes)?;
104        }
105
106        // Sync to guarantee physical sector write
107        self.file.sync_data()?;
108        self.current_offset += BLOCK_MULTIPLIER_SIZE as u64;
109
110        crate::telemetry::SUPERBLOCK_IO_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
111
112        Ok(())
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use tempfile::NamedTempFile;
120
121    #[test]
122    fn test_flush_superblock() {
123        let temp_file = NamedTempFile::new().unwrap();
124        let mut writer = SuperBlockWriter::open(temp_file.path()).unwrap();
125
126        // Create an array of exactly 850 mock quins
127        let quins = [NQuin {
128            subject: 0,
129            predicate: 0,
130            object: 0,
131            context: 0,
132            metadata: 0,
133            parity: 0,
134        }; QUINS_PER_BLOCK];
135
136        let result = writer.flush_block(1, 42, &quins);
137        assert!(result.is_ok(), "Failed to flush SuperBlock");
138
139        // Verify the file size is exactly 40,960 bytes
140        let metadata = temp_file.as_file().metadata().unwrap();
141        assert_eq!(
142            metadata.len(),
143            BLOCK_MULTIPLIER_SIZE as u64,
144            "File size is not page aligned to 40,960 bytes"
145        );
146    }
147}
148
149// -----------------------------------------------------------------------------
150// Phase 5: Virtual File System (VFS) Abstraction
151// -----------------------------------------------------------------------------
152// Provides a unified storage interface for `.q42.bidx` offline index sync.
153// - Uses standard `std::fs` on native/Tauri targets.
154// - Uses Origin Private File System (OPFS) on `wasm32-unknown-unknown` targets.
155
156use std::future::Future;
157use std::pin::Pin;
158
159pub trait VirtualFileSystem {
160    /// Reads a chunk of data from the local storage hierarchy
161    #[cfg(not(target_arch = "wasm32"))]
162    fn read_chunk(
163        &self,
164        path: &str,
165    ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send>>;
166    #[cfg(target_arch = "wasm32")]
167    fn read_chunk(&self, path: &str) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>>>>;
168
169    /// Writes a chunk of data to the local storage hierarchy
170    #[cfg(not(target_arch = "wasm32"))]
171    fn write_chunk(
172        &self,
173        path: &str,
174        data: &[u8],
175    ) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send>>;
176    #[cfg(target_arch = "wasm32")]
177    fn write_chunk(
178        &self,
179        path: &str,
180        data: &[u8],
181    ) -> Pin<Box<dyn Future<Output = Result<(), String>>>>;
182}
183
184#[cfg(not(target_arch = "wasm32"))]
185pub struct NativeVfs;
186
187#[cfg(not(target_arch = "wasm32"))]
188impl VirtualFileSystem for NativeVfs {
189    fn read_chunk(
190        &self,
191        path: &str,
192    ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send>> {
193        let path = path.to_string();
194        Box::pin(async move { std::fs::read(&path).map_err(|e| e.to_string()) })
195    }
196
197    fn write_chunk(
198        &self,
199        path: &str,
200        data: &[u8],
201    ) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send>> {
202        let path = path.to_string();
203        let data = data.to_vec();
204        Box::pin(async move { std::fs::write(&path, data).map_err(|e| e.to_string()) })
205    }
206}
207
208#[cfg(target_arch = "wasm32")]
209pub struct OpfsVfs;
210
211#[cfg(target_arch = "wasm32")]
212impl VirtualFileSystem for OpfsVfs {
213    fn read_chunk(&self, path: &str) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>>>> {
214        let path = path.to_string();
215        Box::pin(async move {
216            use wasm_bindgen::JsCast;
217            use wasm_bindgen_futures::JsFuture;
218
219            let window = web_sys::window().ok_or("No global window")?;
220            let navigator = window.navigator();
221            let storage = navigator.storage();
222
223            let dir_handle_val = JsFuture::from(storage.get_directory())
224                .await
225                .map_err(|e| format!("{:?}", e))?;
226            let dir_handle: web_sys::FileSystemDirectoryHandle = dir_handle_val.unchecked_into();
227
228            let file_handle_val = JsFuture::from(dir_handle.get_file_handle(&path))
229                .await
230                .map_err(|e| format!("{:?}", e))?;
231            let file_handle: web_sys::FileSystemFileHandle = file_handle_val.unchecked_into();
232
233            let file_val = JsFuture::from(file_handle.get_file())
234                .await
235                .map_err(|e| format!("{:?}", e))?;
236            let file: web_sys::File = file_val.unchecked_into();
237
238            let array_buffer_val = JsFuture::from(file.array_buffer())
239                .await
240                .map_err(|e| format!("{:?}", e))?;
241            let array_buffer: js_sys::ArrayBuffer = array_buffer_val.unchecked_into();
242
243            let uint8_array = js_sys::Uint8Array::new(&array_buffer);
244            Ok(uint8_array.to_vec())
245        })
246    }
247
248    fn write_chunk(
249        &self,
250        path: &str,
251        data: &[u8],
252    ) -> Pin<Box<dyn Future<Output = Result<(), String>>>> {
253        let path = path.to_string();
254        let data = data.to_vec();
255        Box::pin(async move {
256            use wasm_bindgen::JsCast;
257            use wasm_bindgen_futures::JsFuture;
258
259            let window = web_sys::window().ok_or("No global window")?;
260            let navigator = window.navigator();
261            let storage = navigator.storage();
262
263            let dir_handle_val = JsFuture::from(storage.get_directory())
264                .await
265                .map_err(|e| format!("{:?}", e))?;
266            let dir_handle: web_sys::FileSystemDirectoryHandle = dir_handle_val.unchecked_into();
267
268            let options = web_sys::FileSystemGetFileOptions::new();
269            options.set_create(true);
270            let file_handle_val =
271                JsFuture::from(dir_handle.get_file_handle_with_options(&path, &options))
272                    .await
273                    .map_err(|e| format!("{:?}", e))?;
274            let file_handle: web_sys::FileSystemFileHandle = file_handle_val.unchecked_into();
275
276            let writable_val = JsFuture::from(file_handle.create_writable())
277                .await
278                .map_err(|e| format!("{:?}", e))?;
279            let writable: web_sys::FileSystemWritableFileStream = writable_val.unchecked_into();
280
281            let uint8_array = js_sys::Uint8Array::from(data.as_slice());
282            JsFuture::from(
283                writable
284                    .write_with_buffer_source(&uint8_array)
285                    .map_err(|e| format!("{:?}", e))?,
286            )
287            .await
288            .map_err(|e| format!("{:?}", e))?;
289            JsFuture::from(writable.close())
290                .await
291                .map_err(|e| format!("{:?}", e))?;
292
293            Ok(())
294        })
295    }
296}