Write a segment file with optional encryption. Layout (encrypted): `[preamble(16B plaintext)] [ciphertext(data_len + 16B auth_tag)] [footer(58B plaintext)]` Layout (unencrypted): `[data] [footer(58B plaintext)]` The preamble contains a freshly-generated random epoch. The nonce used for AES-256-GCM is `(preamble.epoch, min_lsn)`. The preamble bytes and a fixed AAD tag are included as Additional
(
path: &Path,
data: &[u8],
footer: &SegmentFooter,
key: Option<&nodedb_wal::crypto::WalEncryptionKey>,
)
| 145 | /// Two segments that share `min_lsn` (e.g. LSN=0 at bootstrap) will have |
| 146 | /// different epochs and therefore non-colliding nonces. |
| 147 | pub fn write_encrypted_segment( |
| 148 | path: &Path, |
| 149 | data: &[u8], |
| 150 | footer: &SegmentFooter, |
| 151 | key: Option<&nodedb_wal::crypto::WalEncryptionKey>, |
| 152 | ) -> crate::Result<()> { |
| 153 | let mut file = std::fs::File::create(path)?; |
| 154 | |
| 155 | if let Some(key) = key { |
| 156 | // Create a fresh-epoch key for this segment. Each segment write gets |
| 157 | // its own epoch so even two segments with the same min_lsn (e.g. |
| 158 | // both starting at LSN 0 at bootstrap) have non-colliding nonces. |
| 159 | // `with_fresh_epoch` re-uses the same AES key bytes but generates a |
| 160 | // new random epoch — the nonce for encryption will use this epoch, |
| 161 | // and we record the same epoch in the preamble. |
| 162 | let fresh_key = key.with_fresh_epoch().map_err(crate::Error::Wal)?; |
| 163 | let epoch = *fresh_key.epoch(); |
| 164 | let preamble = SegmentPreamble::new_seg(epoch); |
| 165 | let preamble_bytes = preamble.to_bytes(); |
| 166 | |
| 167 | // AAD = preamble_bytes (binds ciphertext to this segment's preamble). |
| 168 | let ciphertext = fresh_key |
| 169 | .encrypt_aad(footer.min_lsn.as_u64(), &preamble_bytes, data) |
| 170 | .map_err(|e| crate::Error::Storage { |
| 171 | engine: "segment".into(), |
| 172 | detail: format!("segment encryption failed: {e}"), |
| 173 | })?; |
| 174 | |
| 175 | file.write_all(&preamble_bytes)?; |
| 176 | file.write_all(&ciphertext)?; |
| 177 | } else { |
| 178 | file.write_all(data)?; |
| 179 | } |
| 180 | |
| 181 | file.write_all(&footer.to_bytes())?; |
| 182 | file.flush()?; |
| 183 | Ok(()) |
| 184 | } |
| 185 | |
| 186 | /// Encrypt segment data into a self-describing byte envelope (no file I/O). |
| 187 | /// |