blob: 90831a4a2eb7b4ceb90e2b8ae20c3334f4ce2c8f [file] [log] [blame]
William Kurkianea869482019-04-09 15:16:11 -04001package lz4
2
3import (
4 "encoding/binary"
Abhilash S.L3b494632019-07-16 15:51:09 +05305 "math/bits"
William Kurkianea869482019-04-09 15:16:11 -04006)
7
8// blockHash hashes 4 bytes into a value < winSize.
9func blockHash(x uint32) uint32 {
10 const hasher uint32 = 2654435761 // Knuth multiplicative hash.
11 return x * hasher >> hashShift
12}
13
14// CompressBlockBound returns the maximum size of a given buffer of size n, when not compressible.
15func CompressBlockBound(n int) int {
16 return n + n/255 + 16
17}
18
19// UncompressBlock uncompresses the source buffer into the destination one,
20// and returns the uncompressed size.
21//
22// The destination buffer must be sized appropriately.
23//
24// An error is returned if the source data is invalid or the destination buffer is too small.
Abhilash S.L3b494632019-07-16 15:51:09 +053025func UncompressBlock(src, dst []byte) (int, error) {
26 if len(src) == 0 {
William Kurkianea869482019-04-09 15:16:11 -040027 return 0, nil
28 }
Abhilash S.L3b494632019-07-16 15:51:09 +053029 if di := decodeBlock(dst, src); di >= 0 {
30 return di, nil
William Kurkianea869482019-04-09 15:16:11 -040031 }
Abhilash S.L3b494632019-07-16 15:51:09 +053032 return 0, ErrInvalidSourceShortBuffer
William Kurkianea869482019-04-09 15:16:11 -040033}
34
35// CompressBlock compresses the source buffer into the destination one.
36// This is the fast version of LZ4 compression and also the default one.
37// The size of hashTable must be at least 64Kb.
38//
39// The size of the compressed data is returned. If it is 0 and no error, then the data is incompressible.
40//
41// An error is returned if the destination buffer is too small.
42func CompressBlock(src, dst []byte, hashTable []int) (di int, err error) {
Abhilash S.L3b494632019-07-16 15:51:09 +053043 defer recoverBlock(&err)
44
45 // adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
46 // This significantly speeds up incompressible data and usually has very small impact on compresssion.
47 // bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
48 const adaptSkipLog = 7
William Kurkianea869482019-04-09 15:16:11 -040049
50 sn, dn := len(src)-mfLimit, len(dst)
51 if sn <= 0 || dn == 0 {
52 return 0, nil
53 }
54 var si int
55
56 // Fast scan strategy: the hash table only stores the last 4 bytes sequences.
William Kurkianea869482019-04-09 15:16:11 -040057
58 anchor := si // Position of the current literals.
William Kurkianea869482019-04-09 15:16:11 -040059
60 for si < sn {
61 // Hash the next 4 bytes (sequence)...
62 match := binary.LittleEndian.Uint32(src[si:])
63 h := blockHash(match)
64
65 ref := hashTable[h]
66 hashTable[h] = si
67 if ref >= sn { // Invalid reference (dirty hashtable).
Abhilash S.L3b494632019-07-16 15:51:09 +053068 si += 1 + (si-anchor)>>adaptSkipLog
William Kurkianea869482019-04-09 15:16:11 -040069 continue
70 }
71 offset := si - ref
72 if offset <= 0 || offset >= winSize || // Out of window.
73 match != binary.LittleEndian.Uint32(src[ref:]) { // Hash collision on different matches.
Abhilash S.L3b494632019-07-16 15:51:09 +053074 si += 1 + (si-anchor)>>adaptSkipLog
William Kurkianea869482019-04-09 15:16:11 -040075 continue
76 }
77
78 // Match found.
79 // acc = accInit
80 lLen := si - anchor // Literal length.
81
82 // Encode match length part 1.
83 si += minMatch
84 mLen := si // Match length has minMatch already.
85 // Find the longest match, first looking by batches of 8 bytes.
Abhilash S.L3b494632019-07-16 15:51:09 +053086 for si < sn {
87 x := binary.LittleEndian.Uint64(src[si:]) ^ binary.LittleEndian.Uint64(src[si-offset:])
88 if x == 0 {
89 si += 8
90 } else {
91 // Stop is first non-zero byte.
92 si += bits.TrailingZeros64(x) >> 3
93 break
94 }
William Kurkianea869482019-04-09 15:16:11 -040095 }
96
97 mLen = si - mLen
98 if mLen < 0xF {
99 dst[di] = byte(mLen)
100 } else {
101 dst[di] = 0xF
102 }
103
104 // Encode literals length.
105 if lLen < 0xF {
106 dst[di] |= byte(lLen << 4)
107 } else {
108 dst[di] |= 0xF0
109 di++
110 l := lLen - 0xF
111 for ; l >= 0xFF; l -= 0xFF {
112 dst[di] = 0xFF
113 di++
114 }
115 dst[di] = byte(l)
116 }
117 di++
118
119 // Literals.
120 copy(dst[di:di+lLen], src[anchor:anchor+lLen])
121 di += lLen + 2
122 anchor = si
123
124 // Encode offset.
125 _ = dst[di] // Bound check elimination.
126 dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
127
128 // Encode match length part 2.
129 if mLen >= 0xF {
130 for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
131 dst[di] = 0xFF
132 di++
133 }
134 dst[di] = byte(mLen)
135 di++
136 }
137 }
138
139 if anchor == 0 {
140 // Incompressible.
141 return 0, nil
142 }
143
144 // Last literals.
145 lLen := len(src) - anchor
146 if lLen < 0xF {
147 dst[di] = byte(lLen << 4)
148 } else {
149 dst[di] = 0xF0
150 di++
151 for lLen -= 0xF; lLen >= 0xFF; lLen -= 0xFF {
152 dst[di] = 0xFF
153 di++
154 }
155 dst[di] = byte(lLen)
156 }
157 di++
158
159 // Write the last literals.
160 if di >= anchor {
161 // Incompressible.
162 return 0, nil
163 }
164 di += copy(dst[di:di+len(src)-anchor], src[anchor:])
165 return di, nil
166}
167
168// CompressBlockHC compresses the source buffer src into the destination dst
169// with max search depth (use 0 or negative value for no max).
170//
171// CompressBlockHC compression ratio is better than CompressBlock but it is also slower.
172//
173// The size of the compressed data is returned. If it is 0 and no error, then the data is not compressible.
174//
175// An error is returned if the destination buffer is too small.
176func CompressBlockHC(src, dst []byte, depth int) (di int, err error) {
Abhilash S.L3b494632019-07-16 15:51:09 +0530177 defer recoverBlock(&err)
178
179 // adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
180 // This significantly speeds up incompressible data and usually has very small impact on compresssion.
181 // bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
182 const adaptSkipLog = 7
William Kurkianea869482019-04-09 15:16:11 -0400183
184 sn, dn := len(src)-mfLimit, len(dst)
185 if sn <= 0 || dn == 0 {
186 return 0, nil
187 }
188 var si int
189
190 // hashTable: stores the last position found for a given hash
Abhilash S.L3b494632019-07-16 15:51:09 +0530191 // chainTable: stores previous positions for a given hash
William Kurkianea869482019-04-09 15:16:11 -0400192 var hashTable, chainTable [winSize]int
193
194 if depth <= 0 {
195 depth = winSize
196 }
197
198 anchor := si
199 for si < sn {
200 // Hash the next 4 bytes (sequence).
201 match := binary.LittleEndian.Uint32(src[si:])
202 h := blockHash(match)
203
204 // Follow the chain until out of window and give the longest match.
205 mLen := 0
206 offset := 0
207 for next, try := hashTable[h], depth; try > 0 && next > 0 && si-next < winSize; next = chainTable[next&winMask] {
208 // The first (mLen==0) or next byte (mLen>=minMatch) at current match length
209 // must match to improve on the match length.
210 if src[next+mLen] != src[si+mLen] {
211 continue
212 }
213 ml := 0
214 // Compare the current position with a previous with the same hash.
Abhilash S.L3b494632019-07-16 15:51:09 +0530215 for ml < sn-si {
216 x := binary.LittleEndian.Uint64(src[next+ml:]) ^ binary.LittleEndian.Uint64(src[si+ml:])
217 if x == 0 {
218 ml += 8
219 } else {
220 // Stop is first non-zero byte.
221 ml += bits.TrailingZeros64(x) >> 3
222 break
223 }
William Kurkianea869482019-04-09 15:16:11 -0400224 }
225 if ml < minMatch || ml <= mLen {
226 // Match too small (<minMath) or smaller than the current match.
227 continue
228 }
229 // Found a longer match, keep its position and length.
230 mLen = ml
231 offset = si - next
232 // Try another previous position with the same hash.
233 try--
234 }
235 chainTable[si&winMask] = hashTable[h]
236 hashTable[h] = si
237
238 // No match found.
239 if mLen == 0 {
Abhilash S.L3b494632019-07-16 15:51:09 +0530240 si += 1 + (si-anchor)>>adaptSkipLog
William Kurkianea869482019-04-09 15:16:11 -0400241 continue
242 }
243
244 // Match found.
245 // Update hash/chain tables with overlapping bytes:
246 // si already hashed, add everything from si+1 up to the match length.
247 winStart := si + 1
248 if ws := si + mLen - winSize; ws > winStart {
249 winStart = ws
250 }
251 for si, ml := winStart, si+mLen; si < ml; {
252 match >>= 8
253 match |= uint32(src[si+3]) << 24
254 h := blockHash(match)
255 chainTable[si&winMask] = hashTable[h]
256 hashTable[h] = si
257 si++
258 }
259
260 lLen := si - anchor
261 si += mLen
262 mLen -= minMatch // Match length does not include minMatch.
263
264 if mLen < 0xF {
265 dst[di] = byte(mLen)
266 } else {
267 dst[di] = 0xF
268 }
269
270 // Encode literals length.
271 if lLen < 0xF {
272 dst[di] |= byte(lLen << 4)
273 } else {
274 dst[di] |= 0xF0
275 di++
276 l := lLen - 0xF
277 for ; l >= 0xFF; l -= 0xFF {
278 dst[di] = 0xFF
279 di++
280 }
281 dst[di] = byte(l)
282 }
283 di++
284
285 // Literals.
286 copy(dst[di:di+lLen], src[anchor:anchor+lLen])
287 di += lLen
288 anchor = si
289
290 // Encode offset.
291 di += 2
292 dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
293
294 // Encode match length part 2.
295 if mLen >= 0xF {
296 for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
297 dst[di] = 0xFF
298 di++
299 }
300 dst[di] = byte(mLen)
301 di++
302 }
303 }
304
305 if anchor == 0 {
306 // Incompressible.
307 return 0, nil
308 }
309
310 // Last literals.
311 lLen := len(src) - anchor
312 if lLen < 0xF {
313 dst[di] = byte(lLen << 4)
314 } else {
315 dst[di] = 0xF0
316 di++
317 lLen -= 0xF
318 for ; lLen >= 0xFF; lLen -= 0xFF {
319 dst[di] = 0xFF
320 di++
321 }
322 dst[di] = byte(lLen)
323 }
324 di++
325
326 // Write the last literals.
327 if di >= anchor {
328 // Incompressible.
329 return 0, nil
330 }
331 di += copy(dst[di:di+len(src)-anchor], src[anchor:])
332 return di, nil
333}