blob: ea8f7afeb335c2f889407fe3a9a6c915dabfb89f [file] [log] [blame]
Scott Bakereee8dd82019-09-24 12:52:34 -07001package snappy
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7
8 master "github.com/golang/snappy"
9)
10
11const (
12 sizeOffset = 16
13 sizeBytes = 4
14)
15
16var (
17 xerialHeader = []byte{130, 83, 78, 65, 80, 80, 89, 0}
18
19 // This is xerial version 1 and minimally compatible with version 1
20 xerialVersionInfo = []byte{0, 0, 0, 1, 0, 0, 0, 1}
21
22 // ErrMalformed is returned by the decoder when the xerial framing
23 // is malformed
24 ErrMalformed = errors.New("malformed xerial framing")
25)
26
27func min(x, y int) int {
28 if x < y {
29 return x
30 }
31 return y
32}
33
34// Encode encodes data as snappy with no framing header.
35func Encode(src []byte) []byte {
36 return master.Encode(nil, src)
37}
38
39// EncodeStream *appends* to the specified 'dst' the compressed
40// 'src' in xerial framing format. If 'dst' does not have enough
41// capacity, then a new slice will be allocated. If 'dst' has
42// non-zero length, then if *must* have been built using this function.
43func EncodeStream(dst, src []byte) []byte {
44 if len(dst) == 0 {
45 dst = append(dst, xerialHeader...)
46 dst = append(dst, xerialVersionInfo...)
47 }
48
49 // Snappy encode in blocks of maximum 32KB
50 var (
51 max = len(src)
52 blockSize = 32 * 1024
53 pos = 0
54 chunk []byte
55 )
56
57 for pos < max {
58 newPos := min(pos + blockSize, max)
59 chunk = master.Encode(chunk[:cap(chunk)], src[pos:newPos])
60
61 // First encode the compressed size (big-endian)
62 // Put* panics if the buffer is too small, so pad 4 bytes first
63 origLen := len(dst)
64 dst = append(dst, dst[0:4]...)
65 binary.BigEndian.PutUint32(dst[origLen:], uint32(len(chunk)))
66
67 // And now the compressed data
68 dst = append(dst, chunk...)
69 pos = newPos
70 }
71 return dst
72}
73
74// Decode decodes snappy data whether it is traditional unframed
75// or includes the xerial framing format.
76func Decode(src []byte) ([]byte, error) {
77 return DecodeInto(nil, src)
78}
79
80// DecodeInto decodes snappy data whether it is traditional unframed
81// or includes the xerial framing format into the specified `dst`.
82// It is assumed that the entirety of `dst` including all capacity is available
83// for use by this function. If `dst` is nil *or* insufficiently large to hold
84// the decoded `src`, new space will be allocated.
85func DecodeInto(dst, src []byte) ([]byte, error) {
86 var max = len(src)
87 if max < len(xerialHeader) {
88 return nil, ErrMalformed
89 }
90
91 if !bytes.Equal(src[:8], xerialHeader) {
92 return master.Decode(dst[:cap(dst)], src)
93 }
94
95 if max < sizeOffset+sizeBytes {
96 return nil, ErrMalformed
97 }
98
99 if dst == nil {
100 dst = make([]byte, 0, len(src))
101 }
102
103 dst = dst[:0]
104 var (
105 pos = sizeOffset
106 chunk []byte
107 err error
108 )
109
110 for pos+sizeBytes <= max {
111 size := int(binary.BigEndian.Uint32(src[pos : pos+sizeBytes]))
112 pos += sizeBytes
113
114 nextPos := pos + size
115 // On architectures where int is 32-bytes wide size + pos could
116 // overflow so we need to check the low bound as well as the
117 // high
118 if nextPos < pos || nextPos > max {
119 return nil, ErrMalformed
120 }
121
122 chunk, err = master.Decode(chunk[:cap(chunk)], src[pos:nextPos])
123
124 if err != nil {
125 return nil, err
126 }
127 pos = nextPos
128 dst = append(dst, chunk...)
129 }
130 return dst, nil
131}