blob: c2eb20507024611a0aff2a4cdeb3f720a029e2e1 [file] [log] [blame]
khenaidooac637102019-01-14 15:44:34 -05001package 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 {
Abhay Kumara2ae5992025-11-10 14:02:24 +000028 if x < y {
29 return x
30 }
31 return y
khenaidooac637102019-01-14 15:44:34 -050032}
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 (
Abhay Kumara2ae5992025-11-10 14:02:24 +000051 max = len(src)
khenaidooac637102019-01-14 15:44:34 -050052 blockSize = 32 * 1024
Abhay Kumara2ae5992025-11-10 14:02:24 +000053 pos = 0
54 chunk []byte
khenaidooac637102019-01-14 15:44:34 -050055 )
56
57 for pos < max {
Abhay Kumara2ae5992025-11-10 14:02:24 +000058 newPos := min(pos+blockSize, max)
khenaidooac637102019-01-14 15:44:34 -050059 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) {
Abhay Kumara2ae5992025-11-10 14:02:24 +000086 if len(src) < 8 || !bytes.Equal(src[:8], xerialHeader) {
87 dst, err := master.Decode(dst[:cap(dst)], src)
88 if err != nil && len(src) < len(xerialHeader) {
89 // Keep compatibility and return ErrMalformed when there is a
90 // short or truncated header.
91 return nil, ErrMalformed
92 }
93 return dst, err
94 }
95
khenaidooac637102019-01-14 15:44:34 -050096 var max = len(src)
97 if max < len(xerialHeader) {
98 return nil, ErrMalformed
99 }
100
Abhay Kumara2ae5992025-11-10 14:02:24 +0000101 if max == sizeOffset {
102 return []byte{}, nil
khenaidooac637102019-01-14 15:44:34 -0500103 }
104
105 if max < sizeOffset+sizeBytes {
106 return nil, ErrMalformed
107 }
108
109 if dst == nil {
110 dst = make([]byte, 0, len(src))
111 }
112
113 dst = dst[:0]
114 var (
115 pos = sizeOffset
116 chunk []byte
Abhay Kumara2ae5992025-11-10 14:02:24 +0000117 err error
khenaidooac637102019-01-14 15:44:34 -0500118 )
119
120 for pos+sizeBytes <= max {
121 size := int(binary.BigEndian.Uint32(src[pos : pos+sizeBytes]))
122 pos += sizeBytes
123
124 nextPos := pos + size
125 // On architectures where int is 32-bytes wide size + pos could
126 // overflow so we need to check the low bound as well as the
127 // high
128 if nextPos < pos || nextPos > max {
129 return nil, ErrMalformed
130 }
131
132 chunk, err = master.Decode(chunk[:cap(chunk)], src[pos:nextPos])
133
134 if err != nil {
135 return nil, err
136 }
137 pos = nextPos
138 dst = append(dst, chunk...)
139 }
140 return dst, nil
141}