@@ -5,12 +5,15 @@ use bincode::{deserialize_from, serialize_into};
55use defs:: { DenseVector , Payload , Point , PointId } ;
66use std:: collections:: BTreeMap ;
77use std:: fs:: File ;
8+ use std:: io:: { BufReader , BufWriter , Read , Write } ;
89use std:: ops:: Bound :: { Excluded , Unbounded } ;
910use std:: path:: Path ;
1011use std:: sync:: RwLock ;
1112
1213pub const INMEMORY_CHECKPOINT_FILENAME_MARKER : & str = "inmemory" ;
1314const INMEMORY_CHECKPOINT_EXTENSION : & str = "bin" ;
15+ const INMEMORY_CHECKPOINT_MAGIC : & [ u8 ; 8 ] = b"VDBIMCP\0 " ;
16+ const INMEMORY_CHECKPOINT_VERSION : u16 = 1 ;
1417
1518pub struct MemoryStorage {
1619 points : RwLock < BTreeMap < PointId , Point > > ,
@@ -123,14 +126,33 @@ impl StorageEngine for MemoryStorage {
123126 source,
124127 }
125128 } ) ?;
129+ let mut writer = BufWriter :: new ( file) ;
126130 let points = self
127131 . points
128132 . read ( )
129133 . map_err ( |_| StorageError :: InMemoryLock { } ) ?;
130- serialize_into ( file, & * points) . map_err ( |source| StorageError :: Serialization {
131- id : PointId :: nil ( ) ,
132- source,
133- } ) ?;
134+ writer
135+ . write_all ( INMEMORY_CHECKPOINT_MAGIC )
136+ . and_then ( |_| writer. write_all ( & INMEMORY_CHECKPOINT_VERSION . to_le_bytes ( ) ) )
137+ . and_then ( |_| writer. write_all ( & ( points. len ( ) as u64 ) . to_le_bytes ( ) ) )
138+ . map_err ( |source| StorageError :: InMemoryCheckpointIo {
139+ msg : "Couldn't write in-memory checkpoint header" . to_string ( ) ,
140+ source,
141+ } ) ?;
142+
143+ for point in points. values ( ) {
144+ serialize_into ( & mut writer, point) . map_err ( |source| StorageError :: Serialization {
145+ id : point. id ,
146+ source,
147+ } ) ?;
148+ }
149+
150+ writer
151+ . flush ( )
152+ . map_err ( |source| StorageError :: InMemoryCheckpointIo {
153+ msg : "Couldn't flush in-memory checkpoint" . to_string ( ) ,
154+ source,
155+ } ) ?;
134156
135157 Ok ( StorageCheckpoint {
136158 path : checkpoint_path,
@@ -169,11 +191,52 @@ impl StorageEngine for MemoryStorage {
169191 msg : "Couldn't open in-memory checkpoint" . to_string ( ) ,
170192 source,
171193 } ) ?;
172- let restored_points: BTreeMap < PointId , Point > =
173- deserialize_from ( file) . map_err ( |source| StorageError :: Deserialization {
174- id : PointId :: nil ( ) ,
194+ let mut reader = BufReader :: new ( file) ;
195+ let mut magic = [ 0u8 ; INMEMORY_CHECKPOINT_MAGIC . len ( ) ] ;
196+ reader
197+ . read_exact ( & mut magic)
198+ . map_err ( |source| StorageError :: InMemoryCheckpointIo {
199+ msg : "Couldn't read in-memory checkpoint magic" . to_string ( ) ,
175200 source,
176201 } ) ?;
202+ if & magic != INMEMORY_CHECKPOINT_MAGIC {
203+ return Err ( StorageError :: InMemoryCheckpoint {
204+ msg : "Invalid checkpoint magic" . to_string ( ) ,
205+ } ) ;
206+ }
207+
208+ let mut version_bytes = [ 0u8 ; size_of :: < u16 > ( ) ] ;
209+ reader. read_exact ( & mut version_bytes) . map_err ( |source| {
210+ StorageError :: InMemoryCheckpointIo {
211+ msg : "Couldn't read in-memory checkpoint version" . to_string ( ) ,
212+ source,
213+ }
214+ } ) ?;
215+ let version = u16:: from_le_bytes ( version_bytes) ;
216+ if version != INMEMORY_CHECKPOINT_VERSION {
217+ return Err ( StorageError :: InMemoryCheckpoint {
218+ msg : format ! ( "Unsupported checkpoint version: {version}" ) ,
219+ } ) ;
220+ }
221+
222+ let mut count_bytes = [ 0u8 ; size_of :: < u64 > ( ) ] ;
223+ reader. read_exact ( & mut count_bytes) . map_err ( |source| {
224+ StorageError :: InMemoryCheckpointIo {
225+ msg : "Couldn't read in-memory checkpoint point count" . to_string ( ) ,
226+ source,
227+ }
228+ } ) ?;
229+ let point_count = u64:: from_le_bytes ( count_bytes) ;
230+ let mut restored_points = BTreeMap :: new ( ) ;
231+ for _ in 0 ..point_count {
232+ let point: Point =
233+ deserialize_from ( & mut reader) . map_err ( |source| StorageError :: Deserialization {
234+ id : PointId :: nil ( ) ,
235+ source,
236+ } ) ?;
237+ restored_points. insert ( point. id , point) ;
238+ }
239+
177240 let mut points = self
178241 . points
179242 . write ( )
@@ -187,6 +250,7 @@ impl StorageEngine for MemoryStorage {
187250mod tests {
188251 use super :: * ;
189252 use defs:: ContentType ;
253+ use std:: io:: { Read , Write } ;
190254 use tempfile:: { TempDir , tempdir} ;
191255 use uuid:: Uuid ;
192256
@@ -317,4 +381,52 @@ mod tests {
317381 Some ( test_payload( "before" ) )
318382 ) ;
319383 }
384+
385+ #[ test]
386+ fn test_checkpoint_writes_header ( ) {
387+ let storage = create_test_storage ( ) ;
388+ let temp_dir = tempdir ( ) . unwrap ( ) ;
389+ let id = Uuid :: new_v4 ( ) ;
390+
391+ storage
392+ . insert_point ( id, Some ( vec ! [ 0.1 , 0.2 , 0.3 ] ) , Some ( test_payload ( "point" ) ) )
393+ . unwrap ( ) ;
394+ let checkpoint = storage. checkpoint_at ( temp_dir. path ( ) ) . unwrap ( ) ;
395+
396+ let mut file = File :: open ( checkpoint. path ) . unwrap ( ) ;
397+ let mut magic = [ 0u8 ; INMEMORY_CHECKPOINT_MAGIC . len ( ) ] ;
398+ file. read_exact ( & mut magic) . unwrap ( ) ;
399+ assert_eq ! ( & magic, INMEMORY_CHECKPOINT_MAGIC ) ;
400+
401+ let mut version_bytes = [ 0u8 ; size_of :: < u16 > ( ) ] ;
402+ file. read_exact ( & mut version_bytes) . unwrap ( ) ;
403+ assert_eq ! (
404+ u16 :: from_le_bytes( version_bytes) ,
405+ INMEMORY_CHECKPOINT_VERSION
406+ ) ;
407+
408+ let mut count_bytes = [ 0u8 ; size_of :: < u64 > ( ) ] ;
409+ file. read_exact ( & mut count_bytes) . unwrap ( ) ;
410+ assert_eq ! ( u64 :: from_le_bytes( count_bytes) , 1 ) ;
411+ }
412+
413+ #[ test]
414+ fn test_restore_rejects_invalid_checkpoint_magic ( ) {
415+ let mut storage = create_test_storage ( ) ;
416+ let temp_dir = tempdir ( ) . unwrap ( ) ;
417+ let checkpoint_path = temp_dir. path ( ) . join ( "inmemory-invalid.bin" ) ;
418+ let mut file = File :: create ( & checkpoint_path) . unwrap ( ) ;
419+ file. write_all ( b"BADMAGIC" ) . unwrap ( ) ;
420+ file. write_all ( & INMEMORY_CHECKPOINT_VERSION . to_le_bytes ( ) )
421+ . unwrap ( ) ;
422+ file. write_all ( & 0u64 . to_le_bytes ( ) ) . unwrap ( ) ;
423+
424+ let checkpoint = StorageCheckpoint {
425+ path : checkpoint_path,
426+ storage_type : StorageType :: InMemory ,
427+ } ;
428+
429+ let error = storage. restore_checkpoint ( & checkpoint) . unwrap_err ( ) ;
430+ assert ! ( matches!( error, StorageError :: InMemoryCheckpoint { .. } ) ) ;
431+ }
320432}
0 commit comments