laskoviymishka commented on code in PR #3236:
URL: https://github.com/apache/iceberg-rust/pull/3236#discussion_r4044955201


##########
crates/iceberg/src/transaction/snapshot.rs:
##########
@@ -492,7 +492,20 @@ impl<'a> SnapshotProducer<'a> {
 
         manifest_list_writer.add_manifests(new_manifests.into_iter())?;
         let writer_next_row_id = manifest_list_writer.next_row_id();
-        manifest_list_writer.close().await?;
+        let file_metadata = manifest_list_writer.close().await?;
+        let encryption_key_id = if let Some(key_metadata) = key_metadata {
+            Some(
+                self.table
+                    .encryption_manager()
+                    .expect("Encryption manager must be present when key 
metadata exists")

Review Comment:
   This is the `.expect()` from round one — still a panic on the commit path. 
The invariant holds today (we only set `key_metadata` in the `Some(em)` arm), 
but a second `encryption_manager()` lookup after the `await` is exactly the 
kind of thing a later refactor quietly breaks, and then a commit panics instead 
of erroring.
   
   Rather than re-look-it-up, I'd capture the manager in the match arm up top 
and reuse it here — hold `Some((em.clone(), 
encrypted_output.key_metadata().clone()))` — then 
`em.encrypt_manifest_list_key_metadata(&key_metadata.with_file_length(file_metadata.size)).await?`.
 That drops the `.expect()` and the redundant second call in one go.



##########
crates/iceberg/src/writer/file_writer/parquet_writer.rs:
##########
@@ -746,6 +746,7 @@ impl ArrowAsyncFileWriter for AsyncFileWriter {
             self.0
                 .close()
                 .await
+                .map(|_| ())

Review Comment:
   This `.map(|_| ())` is where encrypted data files fall out of the new 
scheme: `close()` now hands back the ciphertext size, but the Arrow adaptor 
drops it, so a data file's `StandardKeyMetadata` never gets `file_length` and 
the truncation check can't fire on read the way it now does for manifests.
   
   I don't think we need to solve it here — the `ArrowAsyncFileWriter` surface 
makes it awkward — but I'd leave a `// TODO(encryption): wire file_length once 
ArrowAsyncFileWriter can surface it` or open a tracking issue so the obligation 
doesn't get lost. wdyt?



##########
crates/iceberg/src/encryption/io.rs:
##########
@@ -138,8 +153,8 @@ impl EncryptedOutputFile {
         )))
     }
 
-    /// Write bytes to file (transparently encrypted).
-    pub async fn write(&self, bs: Bytes) -> Result<()> {
+    /// Write bytes to the file and return its encrypted size.
+    pub async fn write(&self, bs: Bytes) -> Result<FileMetadata> {

Review Comment:
   Thanks for having `write()` return the size — that's the piece I wanted. The 
sharp edge from last round is still here though: `key_metadata()` keeps 
returning length-less metadata, so every call site has to remember the 
`output.key_metadata().clone().with_file_length(file_metadata.size).encode()` 
dance, and the one that forgets writes a file that only fails at read time.
   
   All four call sites do it right today, so this isn't blocking. But I'd like 
the type to make it hard to get wrong — either `write()` returns 
`(FileMetadata, StandardKeyMetadata)` with the length already stamped, or a 
small `WriteSummary` that vends `into_key_metadata()`. wdyt?



##########
crates/iceberg/src/encryption/key_metadata.rs:
##########
@@ -90,7 +90,7 @@ impl StandardKeyMetadata {
         self.aad_prefix.as_deref()
     }
 
-    /// Returns the optional file length.
+    /// Returns the encrypted file length in bytes, required for AGS1 files.

Review Comment:
   Small thing: the doc now says "required for AGS1 files" but the return is 
still `Option<u64>`, which reads as a contradiction. The field is genuinely 
optional in the spec schema and absent on anything written before this PR — 
it's the AGS1 read path that now hard-requires it.
   
   Maybe "Returns the encrypted file length. Must be set before opening an AGS1 
reader; absent on files written before this change." Keeps the `Option` honest.



##########
crates/iceberg/src/encryption/stream.rs:
##########
@@ -71,8 +71,7 @@ pub const GCM_STREAM_MAGIC: [u8; 4] = *b"AGS1";
 pub const GCM_STREAM_HEADER_LENGTH: u32 = 8;
 
 /// Minimum valid AGS1 stream length (header + one empty block).
-#[cfg(test)]
-pub const MIN_STREAM_LENGTH: u32 = GCM_STREAM_HEADER_LENGTH + NONCE_LENGTH + 
GCM_TAG_LENGTH;
+pub(crate) const MIN_STREAM_LENGTH: u32 = GCM_STREAM_HEADER_LENGTH + 
NONCE_LENGTH + GCM_TAG_LENGTH;

Review Comment:
   Now that this is `pub(crate)`, one gap worth closing while we're here: 
`EncryptedInputFile::encrypted_length()` rejects anything below 
`MIN_STREAM_LENGTH`, but `AesGcmFileRead::new()` is public and takes the length 
directly with no such check. Construct it with `stream_length == 
GCM_STREAM_HEADER_LENGTH` and `num_blocks` is 0, so reads return empty bytes 
with no GCM verification at all.
   
   I'd push the `< MIN_STREAM_LENGTH` check down into `new()` so `num_blocks >= 
1` holds by construction — then the guard inside `read()` becomes unreachable 
rather than a live bypass for direct callers. Not blocking, but it closes the 
gap for the public entry point.



##########
crates/iceberg/src/encryption/stream.rs:
##########
@@ -640,6 +651,68 @@ mod tests {
         assert!(result.is_empty());
     }
 
+    #[tokio::test]
+    async fn test_short_ciphertext_read_is_rejected() {
+        struct ShortRead;
+
+        #[async_trait::async_trait]
+        impl FileRead for ShortRead {
+            async fn read(&self, range: Range<u64>) -> Result<Bytes> {
+                Ok(Bytes::from(vec![0; (range.end - range.start - 1) as 
usize]))

Review Comment:
   Tiny thing in the helper: `range.end - range.start - 1` is unsaturated 
`u64`, so a zero-length range panics in debug and wraps to a giant allocation 
in release. It doesn't fire with today's ranges, but since it's shaped like a 
real `FileRead`, `let len = (range.end - range.start).saturating_sub(1) as 
usize;` avoids the trap if it's ever reused.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to