github.com/segmentio/kafka-go@v0.4.48-0.20240318174348-3f6244eb34fd/compress/snappy/go-xerial-snappy/snappy.go (about) 1 package snappy 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 8 master "github.com/klauspost/compress/snappy" 9 ) 10 11 const ( 12 sizeOffset = 16 13 sizeBytes = 4 14 ) 15 16 var ( 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 27 func 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. 35 func 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. 43 func 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. 76 func 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. 85 func 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 }