123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- // Copyright 2019+ Klaus Post. All rights reserved.
- // License information can be found in the LICENSE file.
- // Based on work by Yann Collet, released under BSD License.
- package zstd
- import (
- "encoding/binary"
- "errors"
- "hash/crc32"
- "io"
- "github.com/klauspost/compress/huff0"
- "github.com/klauspost/compress/snappy"
- )
- const (
- snappyTagLiteral = 0x00
- snappyTagCopy1 = 0x01
- snappyTagCopy2 = 0x02
- snappyTagCopy4 = 0x03
- )
- const (
- snappyChecksumSize = 4
- snappyMagicBody = "sNaPpY"
- // snappyMaxBlockSize is the maximum size of the input to encodeBlock. It is not
- // part of the wire format per se, but some parts of the encoder assume
- // that an offset fits into a uint16.
- //
- // Also, for the framing format (Writer type instead of Encode function),
- // https://github.com/google/snappy/blob/master/framing_format.txt says
- // that "the uncompressed data in a chunk must be no longer than 65536
- // bytes".
- snappyMaxBlockSize = 65536
- // snappyMaxEncodedLenOfMaxBlockSize equals MaxEncodedLen(snappyMaxBlockSize), but is
- // hard coded to be a const instead of a variable, so that obufLen can also
- // be a const. Their equivalence is confirmed by
- // TestMaxEncodedLenOfMaxBlockSize.
- snappyMaxEncodedLenOfMaxBlockSize = 76490
- )
- const (
- chunkTypeCompressedData = 0x00
- chunkTypeUncompressedData = 0x01
- chunkTypePadding = 0xfe
- chunkTypeStreamIdentifier = 0xff
- )
- var (
- // ErrSnappyCorrupt reports that the input is invalid.
- ErrSnappyCorrupt = errors.New("snappy: corrupt input")
- // ErrSnappyTooLarge reports that the uncompressed length is too large.
- ErrSnappyTooLarge = errors.New("snappy: decoded block is too large")
- // ErrSnappyUnsupported reports that the input isn't supported.
- ErrSnappyUnsupported = errors.New("snappy: unsupported input")
- errUnsupportedLiteralLength = errors.New("snappy: unsupported literal length")
- )
- // SnappyConverter can read SnappyConverter-compressed streams and convert them to zstd.
- // Conversion is done by converting the stream directly from Snappy without intermediate
- // full decoding.
- // Therefore the compression ratio is much less than what can be done by a full decompression
- // and compression, and a faulty Snappy stream may lead to a faulty Zstandard stream without
- // any errors being generated.
- // No CRC value is being generated and not all CRC values of the Snappy stream are checked.
- // However, it provides really fast recompression of Snappy streams.
- // The converter can be reused to avoid allocations, even after errors.
- type SnappyConverter struct {
- r io.Reader
- err error
- buf []byte
- block *blockEnc
- }
- // Convert the Snappy stream supplied in 'in' and write the zStandard stream to 'w'.
- // If any error is detected on the Snappy stream it is returned.
- // The number of bytes written is returned.
- func (r *SnappyConverter) Convert(in io.Reader, w io.Writer) (int64, error) {
- initPredefined()
- r.err = nil
- r.r = in
- if r.block == nil {
- r.block = &blockEnc{}
- r.block.init()
- }
- r.block.initNewEncode()
- if len(r.buf) != snappyMaxEncodedLenOfMaxBlockSize+snappyChecksumSize {
- r.buf = make([]byte, snappyMaxEncodedLenOfMaxBlockSize+snappyChecksumSize)
- }
- r.block.litEnc.Reuse = huff0.ReusePolicyNone
- var written int64
- var readHeader bool
- {
- var header []byte
- var n int
- header, r.err = frameHeader{WindowSize: snappyMaxBlockSize}.appendTo(r.buf[:0])
- n, r.err = w.Write(header)
- if r.err != nil {
- return written, r.err
- }
- written += int64(n)
- }
- for {
- if !r.readFull(r.buf[:4], true) {
- // Add empty last block
- r.block.reset(nil)
- r.block.last = true
- err := r.block.encodeLits(false)
- if err != nil {
- return written, err
- }
- n, err := w.Write(r.block.output)
- if err != nil {
- return written, err
- }
- written += int64(n)
- return written, r.err
- }
- chunkType := r.buf[0]
- if !readHeader {
- if chunkType != chunkTypeStreamIdentifier {
- println("chunkType != chunkTypeStreamIdentifier", chunkType)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- readHeader = true
- }
- chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
- if chunkLen > len(r.buf) {
- println("chunkLen > len(r.buf)", chunkType)
- r.err = ErrSnappyUnsupported
- return written, r.err
- }
- // The chunk types are specified at
- // https://github.com/google/snappy/blob/master/framing_format.txt
- switch chunkType {
- case chunkTypeCompressedData:
- // Section 4.2. Compressed data (chunk type 0x00).
- if chunkLen < snappyChecksumSize {
- println("chunkLen < snappyChecksumSize", chunkLen, snappyChecksumSize)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- buf := r.buf[:chunkLen]
- if !r.readFull(buf, false) {
- return written, r.err
- }
- //checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
- buf = buf[snappyChecksumSize:]
- n, hdr, err := snappyDecodedLen(buf)
- if err != nil {
- r.err = err
- return written, r.err
- }
- buf = buf[hdr:]
- if n > snappyMaxBlockSize {
- println("n > snappyMaxBlockSize", n, snappyMaxBlockSize)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- r.block.reset(nil)
- r.block.pushOffsets()
- if err := decodeSnappy(r.block, buf); err != nil {
- r.err = err
- return written, r.err
- }
- if r.block.size+r.block.extraLits != n {
- printf("invalid size, want %d, got %d\n", n, r.block.size+r.block.extraLits)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- err = r.block.encode(false)
- switch err {
- case errIncompressible:
- r.block.popOffsets()
- r.block.reset(nil)
- r.block.literals, err = snappy.Decode(r.block.literals[:n], r.buf[snappyChecksumSize:chunkLen])
- if err != nil {
- println("snappy.Decode:", err)
- return written, err
- }
- err = r.block.encodeLits(false)
- if err != nil {
- return written, err
- }
- case nil:
- default:
- return written, err
- }
- n, r.err = w.Write(r.block.output)
- if r.err != nil {
- return written, err
- }
- written += int64(n)
- continue
- case chunkTypeUncompressedData:
- if debug {
- println("Uncompressed, chunklen", chunkLen)
- }
- // Section 4.3. Uncompressed data (chunk type 0x01).
- if chunkLen < snappyChecksumSize {
- println("chunkLen < snappyChecksumSize", chunkLen, snappyChecksumSize)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- r.block.reset(nil)
- buf := r.buf[:snappyChecksumSize]
- if !r.readFull(buf, false) {
- return written, r.err
- }
- checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
- // Read directly into r.decoded instead of via r.buf.
- n := chunkLen - snappyChecksumSize
- if n > snappyMaxBlockSize {
- println("n > snappyMaxBlockSize", n, snappyMaxBlockSize)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- r.block.literals = r.block.literals[:n]
- if !r.readFull(r.block.literals, false) {
- return written, r.err
- }
- if snappyCRC(r.block.literals) != checksum {
- println("literals crc mismatch")
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- err := r.block.encodeLits(false)
- if err != nil {
- return written, err
- }
- n, r.err = w.Write(r.block.output)
- if r.err != nil {
- return written, err
- }
- written += int64(n)
- continue
- case chunkTypeStreamIdentifier:
- if debug {
- println("stream id", chunkLen, len(snappyMagicBody))
- }
- // Section 4.1. Stream identifier (chunk type 0xff).
- if chunkLen != len(snappyMagicBody) {
- println("chunkLen != len(snappyMagicBody)", chunkLen, len(snappyMagicBody))
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- if !r.readFull(r.buf[:len(snappyMagicBody)], false) {
- return written, r.err
- }
- for i := 0; i < len(snappyMagicBody); i++ {
- if r.buf[i] != snappyMagicBody[i] {
- println("r.buf[i] != snappyMagicBody[i]", r.buf[i], snappyMagicBody[i], i)
- r.err = ErrSnappyCorrupt
- return written, r.err
- }
- }
- continue
- }
- if chunkType <= 0x7f {
- // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
- println("chunkType <= 0x7f")
- r.err = ErrSnappyUnsupported
- return written, r.err
- }
- // Section 4.4 Padding (chunk type 0xfe).
- // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
- if !r.readFull(r.buf[:chunkLen], false) {
- return written, r.err
- }
- }
- }
- // decodeSnappy writes the decoding of src to dst. It assumes that the varint-encoded
- // length of the decompressed bytes has already been read.
- func decodeSnappy(blk *blockEnc, src []byte) error {
- //decodeRef(make([]byte, snappyMaxBlockSize), src)
- var s, length int
- lits := blk.extraLits
- var offset uint32
- for s < len(src) {
- switch src[s] & 0x03 {
- case snappyTagLiteral:
- x := uint32(src[s] >> 2)
- switch {
- case x < 60:
- s++
- case x == 60:
- s += 2
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, src)
- return ErrSnappyCorrupt
- }
- x = uint32(src[s-1])
- case x == 61:
- s += 3
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, src)
- return ErrSnappyCorrupt
- }
- x = uint32(src[s-2]) | uint32(src[s-1])<<8
- case x == 62:
- s += 4
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, src)
- return ErrSnappyCorrupt
- }
- x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
- case x == 63:
- s += 5
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, src)
- return ErrSnappyCorrupt
- }
- x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
- }
- if x > snappyMaxBlockSize {
- println("x > snappyMaxBlockSize", x, snappyMaxBlockSize)
- return ErrSnappyCorrupt
- }
- length = int(x) + 1
- if length <= 0 {
- println("length <= 0 ", length)
- return errUnsupportedLiteralLength
- }
- //if length > snappyMaxBlockSize-d || uint32(length) > len(src)-s {
- // return ErrSnappyCorrupt
- //}
- blk.literals = append(blk.literals, src[s:s+length]...)
- //println(length, "litLen")
- lits += length
- s += length
- continue
- case snappyTagCopy1:
- s += 2
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, len(src))
- return ErrSnappyCorrupt
- }
- length = 4 + int(src[s-2])>>2&0x7
- offset = uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])
- case snappyTagCopy2:
- s += 3
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, len(src))
- return ErrSnappyCorrupt
- }
- length = 1 + int(src[s-3])>>2
- offset = uint32(src[s-2]) | uint32(src[s-1])<<8
- case snappyTagCopy4:
- s += 5
- if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
- println("uint(s) > uint(len(src)", s, len(src))
- return ErrSnappyCorrupt
- }
- length = 1 + int(src[s-5])>>2
- offset = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
- }
- if offset <= 0 || blk.size+lits < int(offset) /*|| length > len(blk)-d */ {
- println("offset <= 0 || blk.size+lits < int(offset)", offset, blk.size+lits, int(offset), blk.size, lits)
- return ErrSnappyCorrupt
- }
- // Check if offset is one of the recent offsets.
- // Adjusts the output offset accordingly.
- // Gives a tiny bit of compression, typically around 1%.
- if false {
- offset = blk.matchOffset(offset, uint32(lits))
- } else {
- offset += 3
- }
- blk.sequences = append(blk.sequences, seq{
- litLen: uint32(lits),
- offset: offset,
- matchLen: uint32(length) - zstdMinMatch,
- })
- blk.size += length + lits
- lits = 0
- }
- blk.extraLits = lits
- return nil
- }
- func (r *SnappyConverter) readFull(p []byte, allowEOF bool) (ok bool) {
- if _, r.err = io.ReadFull(r.r, p); r.err != nil {
- if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) {
- r.err = ErrSnappyCorrupt
- }
- return false
- }
- return true
- }
- var crcTable = crc32.MakeTable(crc32.Castagnoli)
- // crc implements the checksum specified in section 3 of
- // https://github.com/google/snappy/blob/master/framing_format.txt
- func snappyCRC(b []byte) uint32 {
- c := crc32.Update(0, crcTable, b)
- return uint32(c>>15|c<<17) + 0xa282ead8
- }
- // snappyDecodedLen returns the length of the decoded block and the number of bytes
- // that the length header occupied.
- func snappyDecodedLen(src []byte) (blockLen, headerLen int, err error) {
- v, n := binary.Uvarint(src)
- if n <= 0 || v > 0xffffffff {
- return 0, 0, ErrSnappyCorrupt
- }
- const wordSize = 32 << (^uint(0) >> 32 & 1)
- if wordSize == 32 && v > 0x7fffffff {
- return 0, 0, ErrSnappyTooLarge
- }
- return int(v), n, nil
- }
|