@@ -17,15 +17,18 @@ use super::txn::WriteTxn;
1717pub struct ScratchOffset ( u64 ) ;
1818
1919/// Metadata for one ciphertext chunk in the per-txn spill scratch file.
20+ /// Length of the AEAD tag appended to every spill frame.
21+ pub ( crate ) const SPILL_TAG_LEN : usize = 16 ;
22+
2023/// Stored in memory only; the tmp file is discarded at commit/abort.
2124#[ derive( Clone ) ]
2225pub ( crate ) struct SpillSegmentMeta {
2326 /// Byte offset of this chunk (ciphertext body start) in the tmp file.
2427 pub offset : u64 ,
2528 /// Length of the original plaintext in bytes.
2629 pub plaintext_len : u32 ,
27- /// Length of ciphertext body (without the 16-byte tag) in bytes.
28- /// Total on-disk size for this chunk = `ciphertext_len + 16 `.
30+ /// Length of ciphertext body (without the AEAD tag) in bytes. Total
31+ /// on-disk size for this chunk = `ciphertext_len + SPILL_TAG_LEN `.
2932 pub ciphertext_len : u32 ,
3033 /// The 12-byte nonce used to encrypt this chunk, stored verbatim so we
3134 /// can reconstruct a `Nonce` on read without an additional lookup.
@@ -106,8 +109,8 @@ impl<V: Vfs + Clone> SpillScope<'_, '_, V> {
106109 . expect ( "derived above" )
107110 . encrypt ( & nonce, & aad, & mut body) ?;
108111
109- // ciphertext body + 16-byte tag.
110- let pers_len = body. len ( ) as u64 + 16 ;
112+ // ciphertext body + AEAD tag.
113+ let pers_len = body. len ( ) as u64 + SPILL_TAG_LEN as u64 ;
111114 let new_total = self . txn . spill_bytes_used . saturating_add ( pers_len) ;
112115 if new_total > limit {
113116 return Err ( PagedbError :: quota (
@@ -124,6 +127,8 @@ impl<V: Vfs + Clone> SpillScope<'_, '_, V> {
124127 let mut file = self . txn . db . vfs . open ( & path, OpenMode :: CreateOrOpen ) . await ?;
125128 let body_offset = self . txn . spill_bytes_used ;
126129 let ciphertext_len = body. len ( ) ;
130+ // Body and tag are one contiguous frame: a handle must never be
131+ // returned for a file holding only half of it.
127132 body. extend_from_slice ( & tag) ;
128133 write_all_at ( & mut file, body_offset, & body) . await ?;
129134 file. sync ( ) . await ?;
@@ -162,15 +167,17 @@ impl<V: Vfs + Clone> SpillScope<'_, '_, V> {
162167 let mut file = self . txn . db . vfs . open ( path, OpenMode :: Read ) . await ?;
163168
164169 let body_len = meta. ciphertext_len as usize ;
170+ // `usize` is 32-bit on wasm32, where a maximal `ciphertext_len` plus
171+ // the tag genuinely overflows it.
165172 let frame_len = body_len
166- . checked_add ( 16 )
173+ . checked_add ( SPILL_TAG_LEN )
167174 . ok_or_else ( || PagedbError :: arithmetic_overflow ( "spill frame length" ) ) ?;
168175 let mut frame = vec ! [ 0u8 ; frame_len] ;
169176 read_exact_at ( & mut file, meta. offset , & mut frame) . await ?;
177+ // The split is exact by construction, so the tail is the whole tag.
170178 let ( body, tag_bytes) = frame. split_at_mut ( body_len) ;
171- let tag: & [ u8 ; 16 ] = ( & * tag_bytes)
172- . try_into ( )
173- . map_err ( |_| PagedbError :: ChecksumFailure ) ?;
179+ let mut tag = [ 0u8 ; SPILL_TAG_LEN ] ;
180+ tag. copy_from_slice ( tag_bytes) ;
174181
175182 let cipher = self . txn . spill_cipher_readonly ( ) ?;
176183 let nonce = Nonce :: from_bytes ( meta. nonce_bytes ) ;
@@ -187,7 +194,7 @@ impl<V: Vfs + Clone> SpillScope<'_, '_, V> {
187194 segment_id : self . txn . db . file_id ,
188195 } ) ;
189196
190- cipher. decrypt ( & nonce, & aad, body, tag) ?;
197+ cipher. decrypt ( & nonce, & aad, body, & tag) ?;
191198 frame. truncate ( meta. plaintext_len as usize ) ;
192199 Ok ( frame)
193200 }
0 commit comments