Read and decrypt a segment file's data portion. For encrypted segments, reads the 16-byte `SEGP` preamble at the start of the file to recover the epoch, then uses it for nonce reconstruction. Returns the plaintext data (footer is stripped and validated separately).
(
path: &Path,
key: Option<&nodedb_wal::crypto::WalEncryptionKey>,
)
| 269 | /// the file to recover the epoch, then uses it for nonce reconstruction. |
| 270 | /// Returns the plaintext data (footer is stripped and validated separately). |
| 271 | pub fn read_encrypted_segment( |
| 272 | path: &Path, |
| 273 | key: Option<&nodedb_wal::crypto::WalEncryptionKey>, |
| 274 | ) -> crate::Result<Vec<u8>> { |
| 275 | let raw = std::fs::read(path)?; |
| 276 | |
| 277 | if let Some(key) = key { |
| 278 | // Encrypted layout: [preamble(16)] [ciphertext] [footer(58)] |
| 279 | let min_len = PREAMBLE_SIZE + nodedb_wal::crypto::AUTH_TAG_SIZE + FOOTER_SIZE; |
| 280 | if raw.len() < min_len { |
| 281 | return Err(crate::Error::SegmentCorrupted { |
| 282 | detail: "encrypted segment file too small".into(), |
| 283 | }); |
| 284 | } |
| 285 | |
| 286 | // Read and validate the preamble. |
| 287 | let preamble_bytes: [u8; PREAMBLE_SIZE] = raw[..PREAMBLE_SIZE] |
| 288 | .try_into() |
| 289 | .expect("slice is PREAMBLE_SIZE bytes"); |
| 290 | let preamble = |
| 291 | SegmentPreamble::from_bytes(&preamble_bytes, &SEG_PREAMBLE_MAGIC).map_err(|e| { |
| 292 | crate::Error::SegmentCorrupted { |
| 293 | detail: format!("invalid segment preamble: {e}"), |
| 294 | } |
| 295 | })?; |
| 296 | |
| 297 | // Footer is at the end. |
| 298 | let footer_bytes: [u8; FOOTER_SIZE] = raw[raw.len() - FOOTER_SIZE..] |
| 299 | .try_into() |
| 300 | .expect("slice is FOOTER_SIZE bytes"); |
| 301 | let footer = SegmentFooter::from_bytes(&footer_bytes)?; |
| 302 | |
| 303 | // Ciphertext is between preamble and footer. |
| 304 | let ciphertext = &raw[PREAMBLE_SIZE..raw.len() - FOOTER_SIZE]; |
| 305 | |
| 306 | // Decrypt: epoch from preamble, nonce input is min_lsn. |
| 307 | key.decrypt_aad( |
| 308 | preamble.epoch(), |
| 309 | footer.min_lsn.as_u64(), |
| 310 | &preamble_bytes, |
| 311 | ciphertext, |
| 312 | ) |
| 313 | .map_err(|e| crate::Error::Storage { |
| 314 | engine: "segment".into(), |
| 315 | detail: format!("segment decryption failed: {e}"), |
| 316 | }) |
| 317 | } else { |
| 318 | // Unencrypted layout: [data] [footer(58)] |
| 319 | if raw.len() < FOOTER_SIZE { |
| 320 | return Err(crate::Error::SegmentCorrupted { |
| 321 | detail: "file too small".into(), |
| 322 | }); |
| 323 | } |
| 324 | Ok(raw[..raw.len() - FOOTER_SIZE].to_vec()) |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | #[cfg(test)] |