Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func init() {
initCmd.Flags().StringVar(&hashAlgorithm, "hash", "sha256", "Hash algorithm (sha256, blake3)")

// Compression vars
initCmd.Flags().StringVar(&compressionType, "compression", "none", "Compression type (none, gzip, zstd)")
initCmd.Flags().StringVar(&compressionType, "compression", "none", "Compression type (none, gzip, zstd, lz4)")

// Sync vars
initCmd.Flags().StringVar(&syncMode, "sync-mode", "manual", "Synchronization mode (manual, auto)")
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/libp2p/go-libp2p v0.41.1
github.com/multiformats/go-multiaddr v0.15.0
github.com/pierrec/lz4/v4 v4.1.22
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
golang.org/x/sys v0.36.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
Expand Down
5 changes: 3 additions & 2 deletions internal/chunk/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func PromptCompressionConfig(configuration *config.VaultConfig) error {
// Compression prompt with descriptions
compressionPrompt := promptui.Select{
Label: "Compression algorithm",
Items: []string{"none", "gzip", "zstd"},
Items: []string{"none", "gzip", "zstd", "lz4"},
Templates: &promptui.SelectTemplates{
Selected: "Compression: {{ . }}",
Active: "▸ {{ . }}",
Expand All @@ -108,7 +108,8 @@ func PromptCompressionConfig(configuration *config.VaultConfig) error {
{{ "Details:" | faint }}
{{ if eq . "none" }}No compression (faster but larger files)
{{ else if eq . "gzip" }}Gzip compression (good balance of speed/compression)
{{ else if eq . "zstd" }}Zstandard compression (better compression but slower){{ end }}
{{ else if eq . "zstd" }}Zstandard compression (better compression but slower)
{{ else if eq . "lz4" }}LZ4 compression (very fast but lower compression ratio){{ end }}
`,
},
}
Expand Down
33 changes: 31 additions & 2 deletions internal/compression/Compressor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"

"github.com/klauspost/compress/zstd"
"github.com/pierrec/lz4/v4"
"github.com/substantialcattle5/sietch/internal/constants"
)

Expand All @@ -32,6 +33,16 @@ func CompressData(data []byte, algorithm string) ([]byte, error) {
}
defer encoder.Close()
return encoder.EncodeAll(data, nil), nil
case constants.CompressionTypeLz4:
var buf bytes.Buffer
writer := lz4.NewWriter(&buf)
if _, err := writer.Write(data); err != nil {
return nil, fmt.Errorf("failed to write lz4 data: %w", err)
}
if err := writer.Close(); err != nil {
return nil, fmt.Errorf("failed to close lz4 writer: %w", err)
}
return buf.Bytes(), nil
default:
return nil, fmt.Errorf("unsupported compression algorithm: %s", algorithm)
}
Expand Down Expand Up @@ -59,8 +70,8 @@ func DecompressData(data []byte, algorithm string) ([]byte, error) {
// Check if we hit the limit (potential decompression bomb)
if n == constants.MaxDecompressionSize {
// Try to read one more byte to see if there's more data
var extraByte [1]byte
if _, err := reader.Read(extraByte[:]); err == nil {
var extraByte []byte
if _, err := reader.Read(extraByte); err == nil {
return nil, fmt.Errorf("decompressed data exceeds maximum size limit (%d bytes) - potential decompression bomb", constants.MaxDecompressionSize)
}
}
Expand All @@ -83,6 +94,24 @@ func DecompressData(data []byte, algorithm string) ([]byte, error) {
}

return decompressed, nil

case constants.CompressionTypeLz4:
reader := lz4.NewReader(bytes.NewReader(data))

var buf bytes.Buffer
n, err := io.CopyN(&buf, reader, constants.MaxDecompressionSize)
if err != nil && err != io.EOF {
return nil, fmt.Errorf("failed to decompress lz4 data: %w", err)
}

if n == constants.MaxDecompressionSize {
var extraByte []byte
if _, err := reader.Read(extraByte); err == nil {
return nil, fmt.Errorf("decompressed data exceeds maximum size limit (%d bytes) - potential decompression bomb", constants.MaxDecompressionSize)
}
}
return buf.Bytes(), nil

default:
return nil, fmt.Errorf("unsupported compression algorithm: %s", algorithm)
}
Expand Down
126 changes: 126 additions & 0 deletions internal/compression/Compressor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package compression

import (
"bytes"
"fmt"
"testing"

"github.com/substantialcattle5/sietch/internal/constants"
)

func TestCompressAndDecompressData(t *testing.T) {
input := []byte("Some string test data")
tests := []struct {
name string
algorithm string
isErrorExpected bool
err error
}{
{
name: "With Gzip Algorithm",
algorithm: constants.CompressionTypeGzip,
isErrorExpected: false,
},
{
name: "With Zstd Algorithm",
algorithm: constants.CompressionTypeZstd,
isErrorExpected: false,
},
{
name: "With Lz4 Algorithm",
algorithm: constants.CompressionTypeLz4,
isErrorExpected: false,
},
{
name: "With No Compression Algorithm",
algorithm: constants.CompressionTypeNone,
isErrorExpected: false,
},
{
name: "Unsupported Compression Algorithm",
algorithm: "something else",
isErrorExpected: true,
err: fmt.Errorf("unsupported compression algorithm: something else"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compressed, err := CompressData(input, tt.algorithm)
if tt.isErrorExpected {
if err == nil {
t.Errorf("Expected error but got none")
}
if compressed != nil {
t.Errorf("Expected nil compressed data on error")
}
if err != nil && err.Error() != tt.err.Error() {
t.Errorf("Wrong error received. Expected: %v. Received: %v", tt.err, err)
}
return
}
if err != nil {
t.Errorf("Unexpected error during compression: %v", err)
return
}
if compressed == nil {
t.Errorf("Compressed data is nil")
return
}

decompressed, err := DecompressData(compressed, tt.algorithm)
if err != nil {
t.Errorf("Unexpected error during decompression: %v", err)
return
}
if string(decompressed) != string(input) {
t.Errorf("Decompressed data does not match original. Got: %s", string(decompressed))
}
})
}
}

func TestDecompressBomb(t *testing.T) {
raw := bytes.Repeat([]byte("a"), constants.MaxDecompressionSize+1)

tests := []struct {
name string
algorithm string
}{
{
name: "With Gzip Algorithm",
algorithm: constants.CompressionTypeGzip,
},
{
name: "With LZ4 Algorithm",
algorithm: constants.CompressionTypeLz4,
},
{
name: "With Zstd Algorithm",
algorithm: constants.CompressionTypeLz4,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compressed, err := CompressData(raw, tt.algorithm)
if err != nil {
t.Errorf("Could not setup decompression bomb. Error: %v.", err)
return
}
if compressed == nil {
t.Errorf("Could not setup decompression bomb. Setup resulted in nil")
return
}

decompressed, err := DecompressData(compressed, tt.algorithm)
if decompressed != nil {
t.Errorf("Unexpected response. Expected nil.")
return
}
if err == nil || err.Error() != fmt.Sprintf("decompressed data exceeds maximum size limit (%d bytes) - potential decompression bomb", constants.MaxDecompressionSize) {
t.Errorf("Incorrect error received. Error: %v", err)
}
})
}
}
2 changes: 1 addition & 1 deletion internal/config/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ type ChunkRef struct {
Index int `yaml:"index"` // Position in the file
Deduplicated bool `yaml:"deduplicated,omitempty"` // Whether this chunk was deduplicated
Compressed bool `yaml:"compressed,omitempty"` // Whether this chunk was compressed
CompressionType string `yaml:"compression_type,omitempty"` // Compression algorithm used (e.g., "gzip", "zstd", "none")
CompressionType string `yaml:"compression_type,omitempty"` // Compression algorithm used (e.g., "gzip", "zstd", "lz4", "none")
IV string `yaml:"iv,omitempty"` // Per-chunk IV if used
Integrity string `yaml:"integrity,omitempty"` // Integrity check value (e.g., HMAC)
}
Expand Down
1 change: 1 addition & 0 deletions internal/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const (
//** Constants for compression
CompressionTypeGzip = "gzip"
CompressionTypeZstd = "zstd"
CompressionTypeLz4 = "lz4"
CompressionTypeNone = "none"

// Maximum decompression size to prevent decompression bombs
Expand Down
Loading