zeroshade commented on code in PR #911:
URL: https://github.com/apache/arrow-go/pull/911#discussion_r3554173550


##########
parquet/file/page_reader.go:
##########
@@ -518,53 +518,85 @@ func (p *serializedPageReader) decompress(rd io.Reader, 
lenCompressed int, buf [
        return p.codec.Decode(buf, data), nil
 }
 
-func (p *serializedPageReader) readV2Encrypted(rd io.Reader, lenCompressed 
int, levelsBytelen int, compressed bool, buf []byte) error {
+func (p *serializedPageReader) readV2Encrypted(rd io.Reader, lenCompressed 
int, lenUncompressed int, levelsBytelen int, compressed bool, buf []byte) (int, 
error) {
        // if encrypted, we need to decrypt before decompressing
        p.decompressBuffer.ResizeNoShrink(lenCompressed)
 
        n, err := io.ReadFull(rd, p.decompressBuffer.Bytes()[:lenCompressed])
        if err != nil {
-               return err
+               return n, err
        }
        if n != lenCompressed {
-               return fmt.Errorf("parquet: expected to read %d compressed 
bytes, got %d", lenCompressed, n)
+               return n, fmt.Errorf("parquet: expected to read %d compressed 
bytes, got %d", lenCompressed, n)
        }
 
        data := 
p.cryptoCtx.DataDecryptor.Decrypt(p.decompressBuffer.Bytes()[:lenCompressed])
+
        // encrypted + uncompressed -> just copy the decrypted data to output 
buffer
        if !compressed {
-               copy(buf, data)
-               return nil
+               if len(data) != lenUncompressed {
+                       return n, fmt.Errorf("parquet: metadata said %d bytes 
uncompressed data page, got %d bytes", lenUncompressed, len(data))
+               }
+
+               copied := copy(buf, data)
+               if copied != lenUncompressed {
+                       return copied, fmt.Errorf("parquet: expected to read %d 
uncompressed encrypted bytes, got %d", lenUncompressed, copied)
+               }
+               return copied, nil
        }
 
        // definition + repetition levels are always uncompressed
        if levelsBytelen > 0 {
-               copy(buf, data[:levelsBytelen])
+               if len(data) < levelsBytelen {
+                       return n, fmt.Errorf("parquet: expected to read %d 
bytes for levels, got %d", levelsBytelen, len(data))
+               }
+               copied := copy(buf, data[:levelsBytelen])
+               if copied != levelsBytelen {
+                       return copied, fmt.Errorf("parquet: expected to read %d 
bytes for levels, got %d", levelsBytelen, copied)
+               }
                data = data[levelsBytelen:]
        }
-       p.codec.Decode(buf[levelsBytelen:], data)
-       return nil
+       decoded := p.codec.Decode(buf[levelsBytelen:], data)
+       if len(decoded) != lenUncompressed-levelsBytelen {
+               return levelsBytelen + len(decoded), fmt.Errorf("parquet: 
metadata said %d bytes uncompressed data page, got %d bytes", lenUncompressed, 
levelsBytelen+len(decoded))
+       }
+       return levelsBytelen + len(decoded), nil
 }
 
-func (p *serializedPageReader) readV2Unencrypted(rd io.Reader, lenCompressed 
int, levelsBytelen int, compressed bool, buf []byte) error {
+func (p *serializedPageReader) readV2Unencrypted(rd io.Reader, lenCompressed 
int, lenUncompressed int, levelsBytelen int, compressed bool, buf []byte) (int, 
error) {
        if !compressed {
                // uncompressed, just read into the buffer
-               if _, err := io.ReadFull(rd, buf); err != nil {
-                       return err
+               n, err := io.ReadFull(rd, buf)
+               if err != nil {
+                       return n, err
+               }
+               if n != lenUncompressed {
+                       return n, fmt.Errorf("parquet: expected to read %d 
uncompressed bytes, got %d", lenUncompressed, n)
                }
-               return nil
+               return n, nil
        }
 
+       decodedLen := 0
        // definition + repetition levels are always uncompressed
        if levelsBytelen > 0 {
-               if _, err := io.ReadFull(rd, buf[:levelsBytelen]); err != nil {
-                       return err
+               n, err := io.ReadFull(rd, buf[:levelsBytelen])
+               if err != nil {
+                       return n, err
+               }
+               decodedLen += n
+               if n != levelsBytelen {
+                       return n, fmt.Errorf("parquet: expected to read %d 
bytes for levels, got %d", levelsBytelen, n)
                }
        }
-       if _, err := p.decompress(p.r, lenCompressed-levelsBytelen, 
buf[levelsBytelen:]); err != nil {
-               return err
+       values, err := p.decompress(p.r, lenCompressed-levelsBytelen, 
buf[levelsBytelen:])
+       if err != nil {
+               return decodedLen, err
        }
-       return nil
+       decodedLen += len(values)
+       if len(values) != lenUncompressed-decodedLen {

Review Comment:
   This is the blocking issue. `decodedLen += len(values)` on the line above 
already makes `decodedLen == levelsBytelen + len(values)`, so `lenUncompressed 
- decodedLen` here reduces to `lenUncompressed - levelsBytelen - len(values)` — 
which is `0` for any valid page. That turns this into `len(values) != 0`, i.e. 
it rejects **every** non-empty compressed DataPageV2. (The encrypted sibling 
gets it right: `len(decoded) != lenUncompressed-levelsBytelen`.)
   
   Suggested fix:
   ```go
   decodedLen += len(values)
   if decodedLen != lenUncompressed {
       return decodedLen, fmt.Errorf("parquet: metadata said %d bytes 
uncompressed data page, got %d bytes", lenUncompressed, decodedLen)
   }
   ```
   
   I verified on this branch with a valid compressed V2 page (Snappy, no 
levels): `Next()` returns false with the self-contradictory `parquet: metadata 
said 128 bytes uncompressed data page, got 128 bytes`. The new tests here don't 
catch it because they only exercise the mismatched and encrypted paths — please 
also add a happy-path test for a valid *compressed* DataPageV2, since the 
existing `TestDataPageV2` is uncompressed and `TestCompression` is V1-only.
   



-- 
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]

Reply via email to