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  }