1 package lz4ref
2
3 import (
4 "encoding/binary"
5 "fmt"
6 "math/bits"
7 "sync"
8 )
9
10 const (
11
12 minMatch = 4
13 winSizeLog = 16
14 winSize = 1 << winSizeLog
15 winMask = winSize - 1
16
17
18
19
20
21 hashLog = 16
22 htSize = 1 << hashLog
23
24 mfLimit = 10 + minMatch
25 )
26
27
28 func blockHash(x uint64) uint32 {
29 const prime6bytes = 227718039650203
30 x &= 1<<40 - 1
31 return uint32((x * prime6bytes) >> (64 - hashLog))
32 }
33
34 func CompressBlockBound(n int) int {
35 return n + n/255 + 16
36 }
37
38 type Compressor struct {
39
40
41
42
43
44
45
46
47 table [htSize]uint16
48
49
50
51
52 inUse [htSize / 32]uint32
53 }
54
55
56
57
58 func (c *Compressor) get(h uint32, si int) int {
59 h &= htSize - 1
60 i := 0
61 if c.inUse[h/32]&(1<<(h%32)) != 0 {
62 i = int(c.table[h])
63 }
64 i += si &^ winMask
65 if i >= si {
66
67 i -= winSize
68 }
69 return i
70 }
71
72 func (c *Compressor) put(h uint32, si int) {
73 h &= htSize - 1
74 c.table[h] = uint16(si)
75 c.inUse[h/32] |= 1 << (h % 32)
76 }
77
78 func (c *Compressor) reset() { c.inUse = [htSize / 32]uint32{} }
79
80 var compressorPool = sync.Pool{New: func() interface{} { return new(Compressor) }}
81
82 func CompressBlock(src, dst []byte) (int, error) {
83 c := compressorPool.Get().(*Compressor)
84 n, err := c.CompressBlock(src, dst)
85 compressorPool.Put(c)
86 return n, err
87 }
88
89 func CompressBlockLZ4s(src, dst []byte) (int, error) {
90 c := compressorPool.Get().(*Compressor)
91 n, err := c.CompressBlockLZ4s(src, dst)
92 compressorPool.Put(c)
93 return n, err
94 }
95
96 func (c *Compressor) CompressBlock(src, dst []byte) (int, error) {
97
98 c.reset()
99
100 const debug = false
101
102 if debug {
103 fmt.Printf("lz4 block start: len(src): %d, len(dst):%d \n", len(src), len(dst))
104 }
105
106
107 isNotCompressible := len(dst) < CompressBlockBound(len(src))
108
109
110
111
112 const adaptSkipLog = 7
113
114
115
116 var si, di, anchor int
117 sn := len(src) - mfLimit
118 if sn <= 0 {
119 goto lastLiterals
120 }
121
122
123 for si < sn {
124
125 match := binary.LittleEndian.Uint64(src[si:])
126 h := blockHash(match)
127 h2 := blockHash(match >> 8)
128
129
130
131 ref := c.get(h, si)
132 ref2 := c.get(h2, si+1)
133 c.put(h, si)
134 c.put(h2, si+1)
135
136 offset := si - ref
137
138 if offset <= 0 || offset >= winSize || uint32(match) != binary.LittleEndian.Uint32(src[ref:]) {
139
140
141 h = blockHash(match >> 16)
142 ref3 := c.get(h, si+2)
143
144
145 si += 1
146 offset = si - ref2
147
148 if offset <= 0 || offset >= winSize || uint32(match>>8) != binary.LittleEndian.Uint32(src[ref2:]) {
149
150 si += 1
151 offset = si - ref3
152 c.put(h, si)
153
154 if offset <= 0 || offset >= winSize || uint32(match>>16) != binary.LittleEndian.Uint32(src[ref3:]) {
155
156 si += 2 + (si-anchor)>>adaptSkipLog
157 continue
158 }
159 }
160 }
161
162
163 lLen := si - anchor
164
165 mLen := 4
166
167
168 tOff := si - offset - 1
169 for lLen > 0 && tOff >= 0 && src[si-1] == src[tOff] {
170 si--
171 tOff--
172 lLen--
173 mLen++
174 }
175
176
177
178 si, mLen = si+mLen, si+minMatch
179
180
181 for si+8 <= sn {
182 x := binary.LittleEndian.Uint64(src[si:]) ^ binary.LittleEndian.Uint64(src[si-offset:])
183 if x == 0 {
184 si += 8
185 } else {
186
187 si += bits.TrailingZeros64(x) >> 3
188 break
189 }
190 }
191
192 mLen = si - mLen
193 if di >= len(dst) {
194 return 0, ErrInvalidSourceShortBuffer
195 }
196 if mLen < 0xF {
197 dst[di] = byte(mLen)
198 } else {
199 dst[di] = 0xF
200 }
201
202
203 if debug {
204 fmt.Printf("emit %d literals\n", lLen)
205 }
206 if lLen < 0xF {
207 dst[di] |= byte(lLen << 4)
208 } else {
209 dst[di] |= 0xF0
210 di++
211 l := lLen - 0xF
212 for ; l >= 0xFF && di < len(dst); l -= 0xFF {
213 dst[di] = 0xFF
214 di++
215 }
216 if di >= len(dst) {
217 return 0, ErrInvalidSourceShortBuffer
218 }
219 dst[di] = byte(l)
220 }
221 di++
222
223
224 if di+lLen > len(dst) {
225 return 0, ErrInvalidSourceShortBuffer
226 }
227 copy(dst[di:di+lLen], src[anchor:anchor+lLen])
228 di += lLen + 2
229 anchor = si
230
231
232 if debug {
233 fmt.Printf("emit copy, length: %d, offset: %d\n", mLen+minMatch, offset)
234 }
235 if di > len(dst) {
236 return 0, ErrInvalidSourceShortBuffer
237 }
238 dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
239
240
241 if mLen >= 0xF {
242 for mLen -= 0xF; mLen >= 0xFF && di < len(dst); mLen -= 0xFF {
243 dst[di] = 0xFF
244 di++
245 }
246 if di >= len(dst) {
247 return 0, ErrInvalidSourceShortBuffer
248 }
249 dst[di] = byte(mLen)
250 di++
251 }
252
253 if si >= sn {
254 break
255 }
256
257 h = blockHash(binary.LittleEndian.Uint64(src[si-2:]))
258 c.put(h, si-2)
259 }
260
261 lastLiterals:
262 if isNotCompressible && anchor == 0 {
263
264 return 0, nil
265 }
266
267
268 if di >= len(dst) {
269 return 0, ErrInvalidSourceShortBuffer
270 }
271 lLen := len(src) - anchor
272 if lLen < 0xF {
273 dst[di] = byte(lLen << 4)
274 } else {
275 dst[di] = 0xF0
276 di++
277 for lLen -= 0xF; lLen >= 0xFF && di < len(dst); lLen -= 0xFF {
278 dst[di] = 0xFF
279 di++
280 }
281 if di >= len(dst) {
282 return 0, ErrInvalidSourceShortBuffer
283 }
284 dst[di] = byte(lLen)
285 }
286 di++
287
288
289 if isNotCompressible && di >= anchor {
290
291 return 0, nil
292 }
293 if di+len(src)-anchor > len(dst) {
294 return 0, ErrInvalidSourceShortBuffer
295 }
296 di += copy(dst[di:di+len(src)-anchor], src[anchor:])
297 return di, nil
298 }
299
300 func (c *Compressor) CompressBlockLZ4s(src, dst []byte) (int, error) {
301
302 c.reset()
303
304 const debug = false
305 const minMatch = 3
306 const addExtraLits = 32
307
308 if debug {
309 fmt.Printf("lz4 block start: len(src): %d, len(dst):%d \n", len(src), len(dst))
310 }
311
312
313 isNotCompressible := len(dst) < CompressBlockBound(len(src))
314
315
316
317
318 const adaptSkipLog = 7
319
320
321
322 var si, di, anchor int
323 sn := len(src) - mfLimit
324 if sn <= 0 {
325 goto lastLiterals
326 }
327
328
329 for si < sn {
330
331 match := binary.LittleEndian.Uint64(src[si:])
332 h := blockHash(match)
333 h2 := blockHash(match >> 8)
334
335
336
337 ref := c.get(h, si)
338 ref2 := c.get(h2, si+1)
339 c.put(h, si)
340 c.put(h2, si+1)
341
342 offset := si - ref
343
344 if offset <= 0 || offset >= winSize || uint32(match) != binary.LittleEndian.Uint32(src[ref:]) {
345
346
347 h = blockHash(match >> 16)
348 ref3 := c.get(h, si+2)
349
350
351 si += 1
352 offset = si - ref2
353
354 if offset <= 0 || offset >= winSize || uint32(match>>8) != binary.LittleEndian.Uint32(src[ref2:]) {
355
356 si += 1
357 offset = si - ref3
358 c.put(h, si)
359
360 if offset <= 0 || offset >= winSize || uint32(match>>16) != binary.LittleEndian.Uint32(src[ref3:]) {
361
362 si += 2 + (si-anchor)>>adaptSkipLog
363 continue
364 }
365 }
366 }
367
368
369 lLen := si - anchor
370
371 mLen := 4
372
373
374 tOff := si - offset - 1
375 for lLen > 0 && tOff >= 0 && src[si-1] == src[tOff] {
376 si--
377 tOff--
378 lLen--
379 mLen++
380 }
381
382
383
384 si, mLen = si+mLen, si+minMatch
385
386
387 for si+8 <= sn {
388 x := binary.LittleEndian.Uint64(src[si:]) ^ binary.LittleEndian.Uint64(src[si-offset:])
389 if x == 0 {
390 si += 8
391 } else {
392
393 si += bits.TrailingZeros64(x) >> 3
394 break
395 }
396 }
397 if addExtraLits > 15 {
398
399 if lLen > addExtraLits {
400 dst[di] = 0xf0
401 dst[di+1] = byte(int(addExtraLits-15) & 0xff)
402 di += 2
403 copy(dst[di:di+addExtraLits], src[anchor:anchor+lLen])
404 di += addExtraLits
405 lLen -= addExtraLits
406 anchor += addExtraLits
407 }
408 }
409 mLen = si - mLen
410 if di >= len(dst) {
411 return 0, ErrInvalidSourceShortBuffer
412 }
413 if mLen < 0xF {
414 dst[di] = byte(mLen)
415 } else {
416 dst[di] = 0xF
417 }
418
419
420 if debug {
421 fmt.Printf("emit %d literals\n", lLen)
422 }
423 if lLen < 0xF {
424 dst[di] |= byte(lLen << 4)
425 } else {
426 dst[di] |= 0xF0
427 di++
428 l := lLen - 0xF
429 for ; l >= 0xFF && di < len(dst); l -= 0xFF {
430 dst[di] = 0xFF
431 di++
432 }
433 if di >= len(dst) {
434 return 0, ErrInvalidSourceShortBuffer
435 }
436 dst[di] = byte(l)
437 }
438 di++
439
440
441 if di+lLen > len(dst) {
442 return 0, ErrInvalidSourceShortBuffer
443 }
444 copy(dst[di:di+lLen], src[anchor:anchor+lLen])
445 di += lLen + 2
446 anchor = si
447
448
449 if debug {
450 fmt.Printf("emit copy, length: %d, offset: %d\n", mLen+minMatch, offset)
451 }
452 if di > len(dst) {
453 return 0, ErrInvalidSourceShortBuffer
454 }
455 dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
456
457
458 if mLen >= 0xF {
459 for mLen -= 0xF; mLen >= 0xFF && di < len(dst); mLen -= 0xFF {
460 dst[di] = 0xFF
461 di++
462 }
463 if di >= len(dst) {
464 return 0, ErrInvalidSourceShortBuffer
465 }
466 dst[di] = byte(mLen)
467 di++
468 }
469
470 if si >= sn {
471 break
472 }
473
474 h = blockHash(binary.LittleEndian.Uint64(src[si-2:]))
475 c.put(h, si-2)
476 }
477
478 lastLiterals:
479 if isNotCompressible && anchor == 0 {
480
481 return 0, nil
482 }
483
484
485 if di >= len(dst) {
486 return 0, ErrInvalidSourceShortBuffer
487 }
488 lLen := len(src) - anchor
489 if lLen < 0xF {
490 dst[di] = byte(lLen << 4)
491 } else {
492 dst[di] = 0xF0
493 di++
494 for lLen -= 0xF; lLen >= 0xFF && di < len(dst); lLen -= 0xFF {
495 dst[di] = 0xFF
496 di++
497 }
498 if di >= len(dst) {
499 return 0, ErrInvalidSourceShortBuffer
500 }
501 dst[di] = byte(lLen)
502 }
503 di++
504
505
506 if isNotCompressible && di >= anchor {
507
508 return 0, nil
509 }
510 if di+len(src)-anchor > len(dst) {
511 return 0, ErrInvalidSourceShortBuffer
512 }
513 di += copy(dst[di:di+len(src)-anchor], src[anchor:])
514 return di, nil
515 }
516
517 func UncompressBlock(dst, src []byte) (ret int) {
518
519 dst = dst[:len(dst):len(dst)]
520 src = src[:len(src):len(src)]
521
522 const debug = false
523
524 const hasError = -2
525
526 if len(src) == 0 {
527 return hasError
528 }
529
530 defer func() {
531 if r := recover(); r != nil {
532 if debug {
533 fmt.Println("recover:", r)
534 }
535 ret = hasError
536 }
537 }()
538
539 var si, di uint
540 for {
541 if si >= uint(len(src)) {
542 return hasError
543 }
544
545 b := uint(src[si])
546 si++
547
548
549 if lLen := b >> 4; lLen > 0 {
550 switch {
551 case lLen < 0xF && si+16 < uint(len(src)):
552
553
554
555
556 copy(dst[di:], src[si:si+16])
557 si += lLen
558 di += lLen
559 if debug {
560 fmt.Println("ll:", lLen)
561 }
562 if mLen := b & 0xF; mLen < 0xF {
563
564
565
566 mLen += 4
567 if offset := u16(src[si:]); mLen <= offset && offset < di {
568 i := di - offset
569
570
571 if end := i + 18; end <= uint(len(dst)) {
572 copy(dst[di:], dst[i:end])
573 si += 2
574 di += mLen
575 continue
576 }
577 }
578 }
579 case lLen == 0xF:
580 for {
581 x := uint(src[si])
582 if lLen += x; int(lLen) < 0 {
583 if debug {
584 fmt.Println("int(lLen) < 0")
585 }
586 return hasError
587 }
588 si++
589 if x != 0xFF {
590 break
591 }
592 }
593 fallthrough
594 default:
595 copy(dst[di:di+lLen], src[si:si+lLen])
596 si += lLen
597 di += lLen
598 if debug {
599 fmt.Println("ll:", lLen)
600 }
601
602 }
603 }
604
605 mLen := b & 0xF
606 if si == uint(len(src)) && mLen == 0 {
607 break
608 } else if si >= uint(len(src))-2 {
609 return hasError
610 }
611
612 offset := u16(src[si:])
613 if offset == 0 {
614 return hasError
615 }
616 si += 2
617
618
619 mLen += minMatch
620 if mLen == minMatch+0xF {
621 for {
622 x := uint(src[si])
623 if mLen += x; int(mLen) < 0 {
624 return hasError
625 }
626 si++
627 if x != 0xFF {
628 break
629 }
630 }
631 }
632 if debug {
633 fmt.Println("ml:", mLen, "offset:", offset)
634 }
635
636
637 if di < offset {
638 return hasError
639 }
640
641 expanded := dst[di-offset:]
642 if mLen > offset {
643
644 bytesToCopy := offset * (mLen / offset)
645 for n := offset; n <= bytesToCopy+offset; n *= 2 {
646 copy(expanded[n:], expanded[:n])
647 }
648 di += bytesToCopy
649 mLen -= bytesToCopy
650 }
651 di += uint(copy(dst[di:di+mLen], expanded[:mLen]))
652 }
653
654 return int(di)
655 }
656
657 func u16(p []byte) uint { return uint(binary.LittleEndian.Uint16(p)) }
658
View as plain text