blob: ef24f17e57afb7b76ab2b7f4b38d084bec80e838 [file] [log] [blame]
Scott Bakereee8dd82019-09-24 12:52:34 -07001package lz4
2
3import (
4 "encoding/binary"
5 "errors"
6)
7
8var (
9 // ErrInvalidSourceShortBuffer is returned by UncompressBlock or CompressBLock when a compressed
10 // block is corrupted or the destination buffer is not large enough for the uncompressed data.
11 ErrInvalidSourceShortBuffer = errors.New("lz4: invalid source or destination buffer too short")
12 // ErrInvalid is returned when reading an invalid LZ4 archive.
13 ErrInvalid = errors.New("lz4: bad magic number")
14)
15
16// blockHash hashes 4 bytes into a value < winSize.
17func blockHash(x uint32) uint32 {
18 const hasher uint32 = 2654435761 // Knuth multiplicative hash.
19 return x * hasher >> hashShift
20}
21
22// CompressBlockBound returns the maximum size of a given buffer of size n, when not compressible.
23func CompressBlockBound(n int) int {
24 return n + n/255 + 16
25}
26
27// UncompressBlock uncompresses the source buffer into the destination one,
28// and returns the uncompressed size.
29//
30// The destination buffer must be sized appropriately.
31//
32// An error is returned if the source data is invalid or the destination buffer is too small.
Scott Baker70c16092019-09-30 17:08:54 -070033func UncompressBlock(src, dst []byte) (si int, err error) {
34 defer func() {
35 // It is now faster to let the runtime panic and recover on out of bound slice access
36 // than checking indices as we go along.
37 if recover() != nil {
38 err = ErrInvalidSourceShortBuffer
39 }
40 }()
Scott Bakereee8dd82019-09-24 12:52:34 -070041 sn := len(src)
42 if sn == 0 {
43 return 0, nil
44 }
Scott Baker70c16092019-09-30 17:08:54 -070045 var di int
Scott Bakereee8dd82019-09-24 12:52:34 -070046
Scott Baker70c16092019-09-30 17:08:54 -070047 for {
48 // Literals and match lengths (token).
49 b := int(src[si])
50 si++
51
52 // Literals.
53 if lLen := b >> 4; lLen > 0 {
54 if lLen == 0xF {
55 for src[si] == 0xFF {
56 lLen += 0xFF
57 si++
58 }
59 lLen += int(src[si])
60 si++
61 }
62 i := si
63 si += lLen
64 di += copy(dst[di:], src[i:si])
65
66 if si >= sn {
67 return di, nil
68 }
69 }
70
71 si++
72 _ = src[si] // Bound check elimination.
73 offset := int(src[si-1]) | int(src[si])<<8
74 si++
75
76 // Match.
77 mLen := b & 0xF
78 if mLen == 0xF {
79 for src[si] == 0xFF {
80 mLen += 0xFF
81 si++
82 }
83 mLen += int(src[si])
84 si++
85 }
86 mLen += minMatch
87
88 // Copy the match.
89 i := di - offset
90 if offset > 0 && mLen >= offset {
91 // Efficiently copy the match dst[di-offset:di] into the dst slice.
92 bytesToCopy := offset * (mLen / offset)
93 expanded := dst[i:]
94 for n := offset; n <= bytesToCopy+offset; n *= 2 {
95 copy(expanded[n:], expanded[:n])
96 }
97 di += bytesToCopy
98 mLen -= bytesToCopy
99 }
100 di += copy(dst[di:], dst[i:i+mLen])
Scott Bakereee8dd82019-09-24 12:52:34 -0700101 }
Scott Bakereee8dd82019-09-24 12:52:34 -0700102}
103
104// CompressBlock compresses the source buffer into the destination one.
105// This is the fast version of LZ4 compression and also the default one.
106// The size of hashTable must be at least 64Kb.
107//
108// The size of the compressed data is returned. If it is 0 and no error, then the data is incompressible.
109//
110// An error is returned if the destination buffer is too small.
111func CompressBlock(src, dst []byte, hashTable []int) (di int, err error) {
112 defer func() {
113 if recover() != nil {
114 err = ErrInvalidSourceShortBuffer
115 }
116 }()
117
118 sn, dn := len(src)-mfLimit, len(dst)
119 if sn <= 0 || dn == 0 {
120 return 0, nil
121 }
122 var si int
123
124 // Fast scan strategy: the hash table only stores the last 4 bytes sequences.
125 // const accInit = 1 << skipStrength
126
127 anchor := si // Position of the current literals.
128 // acc := accInit // Variable step: improves performance on non-compressible data.
129
130 for si < sn {
131 // Hash the next 4 bytes (sequence)...
132 match := binary.LittleEndian.Uint32(src[si:])
133 h := blockHash(match)
134
135 ref := hashTable[h]
136 hashTable[h] = si
137 if ref >= sn { // Invalid reference (dirty hashtable).
138 si++
139 continue
140 }
141 offset := si - ref
142 if offset <= 0 || offset >= winSize || // Out of window.
143 match != binary.LittleEndian.Uint32(src[ref:]) { // Hash collision on different matches.
144 // si += acc >> skipStrength
145 // acc++
146 si++
147 continue
148 }
149
150 // Match found.
151 // acc = accInit
152 lLen := si - anchor // Literal length.
153
154 // Encode match length part 1.
155 si += minMatch
156 mLen := si // Match length has minMatch already.
157 // Find the longest match, first looking by batches of 8 bytes.
158 for si < sn && binary.LittleEndian.Uint64(src[si:]) == binary.LittleEndian.Uint64(src[si-offset:]) {
159 si += 8
160 }
161 // Then byte by byte.
162 for si < sn && src[si] == src[si-offset] {
163 si++
164 }
165
166 mLen = si - mLen
167 if mLen < 0xF {
168 dst[di] = byte(mLen)
169 } else {
170 dst[di] = 0xF
171 }
172
173 // Encode literals length.
174 if lLen < 0xF {
175 dst[di] |= byte(lLen << 4)
176 } else {
177 dst[di] |= 0xF0
178 di++
179 l := lLen - 0xF
180 for ; l >= 0xFF; l -= 0xFF {
181 dst[di] = 0xFF
182 di++
183 }
184 dst[di] = byte(l)
185 }
186 di++
187
188 // Literals.
Scott Baker70c16092019-09-30 17:08:54 -0700189 copy(dst[di:], src[anchor:anchor+lLen])
Scott Bakereee8dd82019-09-24 12:52:34 -0700190 di += lLen + 2
191 anchor = si
192
193 // Encode offset.
194 _ = dst[di] // Bound check elimination.
195 dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
196
197 // Encode match length part 2.
198 if mLen >= 0xF {
199 for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
200 dst[di] = 0xFF
201 di++
202 }
203 dst[di] = byte(mLen)
204 di++
205 }
206 }
207
208 if anchor == 0 {
209 // Incompressible.
210 return 0, nil
211 }
212
213 // Last literals.
214 lLen := len(src) - anchor
215 if lLen < 0xF {
216 dst[di] = byte(lLen << 4)
217 } else {
218 dst[di] = 0xF0
219 di++
220 for lLen -= 0xF; lLen >= 0xFF; lLen -= 0xFF {
221 dst[di] = 0xFF
222 di++
223 }
224 dst[di] = byte(lLen)
225 }
226 di++
227
228 // Write the last literals.
229 if di >= anchor {
230 // Incompressible.
231 return 0, nil
232 }
Scott Baker70c16092019-09-30 17:08:54 -0700233 di += copy(dst[di:], src[anchor:])
Scott Bakereee8dd82019-09-24 12:52:34 -0700234 return di, nil
235}
236
237// CompressBlockHC compresses the source buffer src into the destination dst
238// with max search depth (use 0 or negative value for no max).
239//
240// CompressBlockHC compression ratio is better than CompressBlock but it is also slower.
241//
242// The size of the compressed data is returned. If it is 0 and no error, then the data is not compressible.
243//
244// An error is returned if the destination buffer is too small.
245func CompressBlockHC(src, dst []byte, depth int) (di int, err error) {
246 defer func() {
247 if recover() != nil {
248 err = ErrInvalidSourceShortBuffer
249 }
250 }()
251
252 sn, dn := len(src)-mfLimit, len(dst)
253 if sn <= 0 || dn == 0 {
254 return 0, nil
255 }
256 var si int
257
258 // hashTable: stores the last position found for a given hash
259 // chaingTable: stores previous positions for a given hash
260 var hashTable, chainTable [winSize]int
261
262 if depth <= 0 {
263 depth = winSize
264 }
265
266 anchor := si
267 for si < sn {
268 // Hash the next 4 bytes (sequence).
269 match := binary.LittleEndian.Uint32(src[si:])
270 h := blockHash(match)
271
272 // Follow the chain until out of window and give the longest match.
273 mLen := 0
274 offset := 0
275 for next, try := hashTable[h], depth; try > 0 && next > 0 && si-next < winSize; next = chainTable[next&winMask] {
276 // The first (mLen==0) or next byte (mLen>=minMatch) at current match length
277 // must match to improve on the match length.
278 if src[next+mLen] != src[si+mLen] {
279 continue
280 }
281 ml := 0
282 // Compare the current position with a previous with the same hash.
283 for ml < sn-si && binary.LittleEndian.Uint64(src[next+ml:]) == binary.LittleEndian.Uint64(src[si+ml:]) {
284 ml += 8
285 }
286 for ml < sn-si && src[next+ml] == src[si+ml] {
287 ml++
288 }
Scott Baker70c16092019-09-30 17:08:54 -0700289 if ml+1 < minMatch || ml <= mLen {
Scott Bakereee8dd82019-09-24 12:52:34 -0700290 // Match too small (<minMath) or smaller than the current match.
291 continue
292 }
293 // Found a longer match, keep its position and length.
294 mLen = ml
295 offset = si - next
296 // Try another previous position with the same hash.
297 try--
298 }
299 chainTable[si&winMask] = hashTable[h]
300 hashTable[h] = si
301
302 // No match found.
303 if mLen == 0 {
304 si++
305 continue
306 }
307
308 // Match found.
309 // Update hash/chain tables with overlapping bytes:
310 // si already hashed, add everything from si+1 up to the match length.
311 winStart := si + 1
312 if ws := si + mLen - winSize; ws > winStart {
313 winStart = ws
314 }
315 for si, ml := winStart, si+mLen; si < ml; {
316 match >>= 8
317 match |= uint32(src[si+3]) << 24
318 h := blockHash(match)
319 chainTable[si&winMask] = hashTable[h]
320 hashTable[h] = si
321 si++
322 }
323
324 lLen := si - anchor
325 si += mLen
326 mLen -= minMatch // Match length does not include minMatch.
327
328 if mLen < 0xF {
329 dst[di] = byte(mLen)
330 } else {
331 dst[di] = 0xF
332 }
333
334 // Encode literals length.
335 if lLen < 0xF {
336 dst[di] |= byte(lLen << 4)
337 } else {
338 dst[di] |= 0xF0
339 di++
340 l := lLen - 0xF
341 for ; l >= 0xFF; l -= 0xFF {
342 dst[di] = 0xFF
343 di++
344 }
345 dst[di] = byte(l)
346 }
347 di++
348
349 // Literals.
Scott Baker70c16092019-09-30 17:08:54 -0700350 copy(dst[di:], src[anchor:anchor+lLen])
Scott Bakereee8dd82019-09-24 12:52:34 -0700351 di += lLen
352 anchor = si
353
354 // Encode offset.
355 di += 2
356 dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
357
358 // Encode match length part 2.
359 if mLen >= 0xF {
360 for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
361 dst[di] = 0xFF
362 di++
363 }
364 dst[di] = byte(mLen)
365 di++
366 }
367 }
368
369 if anchor == 0 {
370 // Incompressible.
371 return 0, nil
372 }
373
374 // Last literals.
375 lLen := len(src) - anchor
376 if lLen < 0xF {
377 dst[di] = byte(lLen << 4)
378 } else {
379 dst[di] = 0xF0
380 di++
381 lLen -= 0xF
382 for ; lLen >= 0xFF; lLen -= 0xFF {
383 dst[di] = 0xFF
384 di++
385 }
386 dst[di] = byte(lLen)
387 }
388 di++
389
390 // Write the last literals.
391 if di >= anchor {
392 // Incompressible.
393 return 0, nil
394 }
Scott Baker70c16092019-09-30 17:08:54 -0700395 di += copy(dst[di:], src[anchor:])
Scott Bakereee8dd82019-09-24 12:52:34 -0700396 return di, nil
397}