Browse Source

Fixes #71.

Pierre.Curto 5 years ago
parent
commit
08cb7fb602
2 changed files with 62 additions and 17 deletions
  1. 30 17
      block.go
  2. 32 0
      writer_test.go

+ 30 - 17
block.go

@@ -40,21 +40,22 @@ func UncompressBlock(src, dst []byte) (int, error) {
 // compressor. If provided, it should have length at least 1<<16. If it is
 // compressor. If provided, it should have length at least 1<<16. If it is
 // shorter (or nil), CompressBlock allocates its own hash table.
 // shorter (or nil), CompressBlock allocates its own hash table.
 //
 //
-// The size of the compressed data is returned. If it is 0 and no error, then the data is incompressible.
+// The size of the compressed data is returned.
+//
+// If the destination buffer size is lower than CompressBlockBound and
+// the compressed size is 0 and no error, then the data is incompressible.
 //
 //
 // An error is returned if the destination buffer is too small.
 // An error is returned if the destination buffer is too small.
 func CompressBlock(src, dst []byte, hashTable []int) (_ int, err error) {
 func CompressBlock(src, dst []byte, hashTable []int) (_ int, err error) {
 	defer recoverBlock(&err)
 	defer recoverBlock(&err)
 
 
+	// Return 0, nil only if the destination buffer size is < CompressBlockBound.
+	isNotCompressible := len(dst) < CompressBlockBound(len(src))
+
 	// adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
 	// adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
 	// This significantly speeds up incompressible data and usually has very small impact on compression.
 	// This significantly speeds up incompressible data and usually has very small impact on compression.
 	// bytes to skip =  1 + (bytes since last match >> adaptSkipLog)
 	// bytes to skip =  1 + (bytes since last match >> adaptSkipLog)
 	const adaptSkipLog = 7
 	const adaptSkipLog = 7
-	sn, dn := len(src)-mfLimit, len(dst)
-	if sn <= 0 || dn == 0 {
-		return 0, nil
-	}
-
 	if len(hashTable) < htSize {
 	if len(hashTable) < htSize {
 		htIface := htPool.Get()
 		htIface := htPool.Get()
 		defer htPool.Put(htIface)
 		defer htPool.Put(htIface)
@@ -67,6 +68,10 @@ func CompressBlock(src, dst []byte, hashTable []int) (_ int, err error) {
 	// si: Current position of the search.
 	// si: Current position of the search.
 	// anchor: Position of the current literals.
 	// anchor: Position of the current literals.
 	var si, di, anchor int
 	var si, di, anchor int
+	sn := len(src) - mfLimit
+	if sn <= 0 {
+		goto lastLiterals
+	}
 
 
 	// Fast scan strategy: the hash table only stores the last 4 bytes sequences.
 	// Fast scan strategy: the hash table only stores the last 4 bytes sequences.
 	for si < sn {
 	for si < sn {
@@ -190,12 +195,13 @@ func CompressBlock(src, dst []byte, hashTable []int) (_ int, err error) {
 		hashTable[h] = si - 2
 		hashTable[h] = si - 2
 	}
 	}
 
 
-	if anchor == 0 {
+	if isNotCompressible && anchor == 0 {
 		// Incompressible.
 		// Incompressible.
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
 	// Last literals.
 	// Last literals.
+lastLiterals:
 	lLen := len(src) - anchor
 	lLen := len(src) - anchor
 	if lLen < 0xF {
 	if lLen < 0xF {
 		dst[di] = byte(lLen << 4)
 		dst[di] = byte(lLen << 4)
@@ -211,7 +217,7 @@ func CompressBlock(src, dst []byte, hashTable []int) (_ int, err error) {
 	di++
 	di++
 
 
 	// Write the last literals.
 	// Write the last literals.
-	if di >= anchor {
+	if isNotCompressible && di >= anchor {
 		// Incompressible.
 		// Incompressible.
 		return 0, nil
 		return 0, nil
 	}
 	}
@@ -237,22 +243,24 @@ func blockHashHC(x uint32) uint32 {
 //
 //
 // CompressBlockHC compression ratio is better than CompressBlock but it is also slower.
 // CompressBlockHC compression ratio is better than CompressBlock but it is also slower.
 //
 //
-// The size of the compressed data is returned. If it is 0 and no error, then the data is not compressible.
+// The size of the compressed data is returned.
+//
+// If the destination buffer size is lower than CompressBlockBound and
+// the compressed size is 0 and no error, then the data is incompressible.
 //
 //
 // An error is returned if the destination buffer is too small.
 // An error is returned if the destination buffer is too small.
 func CompressBlockHC(src, dst []byte, depth int) (_ int, err error) {
 func CompressBlockHC(src, dst []byte, depth int) (_ int, err error) {
 	defer recoverBlock(&err)
 	defer recoverBlock(&err)
 
 
+	// Return 0, nil only if the destination buffer size is < CompressBlockBound.
+	isNotCompressible := len(dst) < CompressBlockBound(len(src))
+
 	// adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
 	// adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
 	// This significantly speeds up incompressible data and usually has very small impact on compression.
 	// This significantly speeds up incompressible data and usually has very small impact on compression.
 	// bytes to skip =  1 + (bytes since last match >> adaptSkipLog)
 	// bytes to skip =  1 + (bytes since last match >> adaptSkipLog)
 	const adaptSkipLog = 7
 	const adaptSkipLog = 7
 
 
-	sn, dn := len(src)-mfLimit, len(dst)
-	if sn <= 0 || dn == 0 {
-		return 0, nil
-	}
-	var si, di int
+	var si, di, anchor int
 
 
 	// hashTable: stores the last position found for a given hash
 	// hashTable: stores the last position found for a given hash
 	// chainTable: stores previous positions for a given hash
 	// chainTable: stores previous positions for a given hash
@@ -262,7 +270,11 @@ func CompressBlockHC(src, dst []byte, depth int) (_ int, err error) {
 		depth = winSize
 		depth = winSize
 	}
 	}
 
 
-	anchor := si
+	sn := len(src) - mfLimit
+	if sn <= 0 {
+		goto lastLiterals
+	}
+
 	for si < sn {
 	for si < sn {
 		// Hash the next 4 bytes (sequence).
 		// Hash the next 4 bytes (sequence).
 		match := binary.LittleEndian.Uint32(src[si:])
 		match := binary.LittleEndian.Uint32(src[si:])
@@ -369,12 +381,13 @@ func CompressBlockHC(src, dst []byte, depth int) (_ int, err error) {
 		}
 		}
 	}
 	}
 
 
-	if anchor == 0 {
+	if isNotCompressible && anchor == 0 {
 		// Incompressible.
 		// Incompressible.
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
 	// Last literals.
 	// Last literals.
+lastLiterals:
 	lLen := len(src) - anchor
 	lLen := len(src) - anchor
 	if lLen < 0xF {
 	if lLen < 0xF {
 		dst[di] = byte(lLen << 4)
 		dst[di] = byte(lLen << 4)
@@ -391,7 +404,7 @@ func CompressBlockHC(src, dst []byte, depth int) (_ int, err error) {
 	di++
 	di++
 
 
 	// Write the last literals.
 	// Write the last literals.
-	if di >= anchor {
+	if isNotCompressible && di >= anchor {
 		// Incompressible.
 		// Incompressible.
 		return 0, nil
 		return 0, nil
 	}
 	}

+ 32 - 0
writer_test.go

@@ -151,3 +151,35 @@ func TestIssue51(t *testing.T) {
 		t.Fatal("processed data does not match input")
 		t.Fatal("processed data does not match input")
 	}
 	}
 }
 }
+
+func TestIssue71(t *testing.T) {
+	for _, tc := range []string{
+		"abc",               // < mfLimit
+		"abcdefghijklmnopq", // > mfLimit
+	} {
+		t.Run(tc, func(t *testing.T) {
+			src := []byte(tc)
+			bound := lz4.CompressBlockBound(len(tc))
+
+			// Small buffer.
+			zSmall := make([]byte, bound-1)
+			n, err := lz4.CompressBlock(src, zSmall, nil)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if n != 0 {
+				t.Fatal("should be incompressible")
+			}
+
+			// Large enough buffer.
+			zLarge := make([]byte, bound)
+			n, err = lz4.CompressBlock(src, zLarge, nil)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if n == 0 {
+				t.Fatal("should be compressible")
+			}
+		})
+	}
+}