package s3api import ( "bytes" "testing" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/util" ) // TestCreateDestinationChunkPreservesEncryption tests that createDestinationChunk preserves CipherKey and IsCompressed func TestCreateDestinationChunkPreservesEncryption(t *testing.T) { s3a := &S3ApiServer{} testCases := []struct { name string sourceChunk *filer_pb.FileChunk expectedOffset int64 expectedSize uint64 shouldPreserveCK bool shouldPreserveIC bool }{ { name: "Encrypted and compressed chunk", sourceChunk: &filer_pb.FileChunk{ Offset: 0, Size: 1024, CipherKey: []byte("test-cipher-key-1234567890123456"), IsCompressed: true, ETag: "test-etag", }, expectedOffset: 0, expectedSize: 1024, shouldPreserveCK: true, shouldPreserveIC: true, }, { name: "Only encrypted chunk", sourceChunk: &filer_pb.FileChunk{ Offset: 1024, Size: 2048, CipherKey: []byte("test-cipher-key-1234567890123456"), IsCompressed: false, ETag: "test-etag-2", }, expectedOffset: 1024, expectedSize: 2048, shouldPreserveCK: true, shouldPreserveIC: false, }, { name: "Only compressed chunk", sourceChunk: &filer_pb.FileChunk{ Offset: 2048, Size: 512, CipherKey: nil, IsCompressed: true, ETag: "test-etag-3", }, expectedOffset: 2048, expectedSize: 512, shouldPreserveCK: false, shouldPreserveIC: true, }, { name: "Unencrypted and uncompressed chunk", sourceChunk: &filer_pb.FileChunk{ Offset: 4096, Size: 1024, CipherKey: nil, IsCompressed: false, ETag: "test-etag-4", }, expectedOffset: 4096, expectedSize: 1024, shouldPreserveCK: false, shouldPreserveIC: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { dstChunk := s3a.createDestinationChunk(tc.sourceChunk, tc.expectedOffset, tc.expectedSize) // Verify offset and size if dstChunk.Offset != tc.expectedOffset { t.Errorf("Expected offset %d, got %d", tc.expectedOffset, dstChunk.Offset) } if dstChunk.Size != tc.expectedSize { t.Errorf("Expected size %d, got %d", tc.expectedSize, dstChunk.Size) } // Verify CipherKey preservation if tc.shouldPreserveCK { if !bytes.Equal(dstChunk.CipherKey, tc.sourceChunk.CipherKey) { t.Errorf("CipherKey not preserved: expected %v, got %v", tc.sourceChunk.CipherKey, dstChunk.CipherKey) } } else { if len(dstChunk.CipherKey) > 0 { t.Errorf("Expected no CipherKey, got %v", dstChunk.CipherKey) } } // Verify IsCompressed preservation if dstChunk.IsCompressed != tc.shouldPreserveIC { t.Errorf("IsCompressed not preserved: expected %v, got %v", tc.shouldPreserveIC, dstChunk.IsCompressed) } // Verify ETag preservation if dstChunk.ETag != tc.sourceChunk.ETag { t.Errorf("ETag not preserved: expected %s, got %s", tc.sourceChunk.ETag, dstChunk.ETag) } }) } } // TestEncryptedVolumeCopyScenario documents the expected behavior for encrypted volumes (issue #7530) func TestEncryptedVolumeCopyScenario(t *testing.T) { t.Run("Scenario: Copy file on encrypted volume with multiple chunks", func(t *testing.T) { // Scenario description for issue #7530: // 1. Volume is started with -filer.encryptVolumeData // 2. File is uploaded via S3 (automatically encrypted, multiple chunks) // 3. File is copied/renamed via S3 CopyObject // 4. Copied file should be readable // // The bug was that IsCompressed flag was not preserved during copy, // causing the upload logic to potentially double-compress the data, // making the copied file unreadable. sourceChunks := []*filer_pb.FileChunk{ { FileId: "1,abc123", Offset: 0, Size: 4194304, CipherKey: util.GenCipherKey(), // Simulates encrypted volume IsCompressed: true, // Simulates compression ETag: "etag1", }, { FileId: "2,def456", Offset: 4194304, Size: 4194304, CipherKey: util.GenCipherKey(), IsCompressed: true, ETag: "etag2", }, } s3a := &S3ApiServer{} // Verify that createDestinationChunk preserves all necessary metadata for i, srcChunk := range sourceChunks { dstChunk := s3a.createDestinationChunk(srcChunk, srcChunk.Offset, srcChunk.Size) // Critical checks for issue #7530 if !dstChunk.IsCompressed { t.Errorf("Chunk %d: IsCompressed flag MUST be preserved to prevent double-compression", i) } if !bytes.Equal(dstChunk.CipherKey, srcChunk.CipherKey) { t.Errorf("Chunk %d: CipherKey MUST be preserved for encrypted volumes", i) } if dstChunk.Offset != srcChunk.Offset { t.Errorf("Chunk %d: Offset must be preserved", i) } if dstChunk.Size != srcChunk.Size { t.Errorf("Chunk %d: Size must be preserved", i) } if dstChunk.ETag != srcChunk.ETag { t.Errorf("Chunk %d: ETag must be preserved", i) } } t.Log("✓ All chunk metadata properly preserved for encrypted volume copy scenario") }) }