package lz4 import ( "fmt" "github.com/pierrec/lz4/v4/internal/lz4block" "github.com/pierrec/lz4/v4/internal/lz4errors" "reflect" "runtime" ) //go:generate go run golang.org/x/tools/cmd/stringer -type=BlockSize,CompressionLevel -output options_gen.go type ( applier interface { Apply(...Option) error private() } // Option defines the parameters to setup an LZ4 Writer or Reader. Option func(applier) error ) // String returns a string representation of the option with its parameter(s). func (o Option) String() string { return o(nil).Error() } // Default options. var ( DefaultBlockSizeOption = BlockSizeOption(Block4Mb) DefaultChecksumOption = ChecksumOption(true) DefaultConcurrency = ConcurrencyOption(1) defaultOnBlockDone = OnBlockDoneOption(nil) ) const ( Block64Kb BlockSize = 1 << (16 + iota*2) Block256Kb Block1Mb Block4Mb ) // BlockSizeIndex defines the size of the blocks to be compressed. type BlockSize uint32 // BlockSizeOption defines the maximum size of compressed blocks (default=Block4Mb). func BlockSizeOption(size BlockSize) Option { return func(a applier) error { switch w := a.(type) { case nil: s := fmt.Sprintf("BlockSizeOption(%s)", size) return lz4errors.Error(s) case *Writer: size := uint32(size) if !lz4block.IsValid(size) { return fmt.Errorf("%w: %d", lz4errors.ErrOptionInvalidBlockSize, size) } w.frame.Descriptor.Flags.BlockSizeIndexSet(lz4block.Index(size)) return nil } return lz4errors.ErrOptionNotApplicable } } // BlockChecksumOption enables or disables block checksum (default=false). func BlockChecksumOption(flag bool) Option { return func(a applier) error { switch w := a.(type) { case nil: s := fmt.Sprintf("BlockChecksumOption(%v)", flag) return lz4errors.Error(s) case *Writer: w.frame.Descriptor.Flags.BlockChecksumSet(flag) return nil } return lz4errors.ErrOptionNotApplicable } } // ChecksumOption enables/disables all blocks or content checksum (default=true). func ChecksumOption(flag bool) Option { return func(a applier) error { switch w := a.(type) { case nil: s := fmt.Sprintf("ChecksumOption(%v)", flag) return lz4errors.Error(s) case *Writer: w.frame.Descriptor.Flags.ContentChecksumSet(flag) return nil } return lz4errors.ErrOptionNotApplicable } } // SizeOption sets the size of the original uncompressed data (default=0). It is useful to know the size of the // whole uncompressed data stream. func SizeOption(size uint64) Option { return func(a applier) error { switch w := a.(type) { case nil: s := fmt.Sprintf("SizeOption(%d)", size) return lz4errors.Error(s) case *Writer: w.frame.Descriptor.Flags.SizeSet(size > 0) w.frame.Descriptor.ContentSize = size return nil } return lz4errors.ErrOptionNotApplicable } } // ConcurrencyOption sets the number of go routines used for compression. // If n <= 0, then the output of runtime.GOMAXPROCS(0) is used. func ConcurrencyOption(n int) Option { if n <= 0 { n = runtime.GOMAXPROCS(0) } return func(a applier) error { switch rw := a.(type) { case nil: s := fmt.Sprintf("ConcurrencyOption(%d)", n) return lz4errors.Error(s) case *Writer: rw.num = n return nil case *Reader: rw.num = n return nil } return lz4errors.ErrOptionNotApplicable } } // CompressionLevel defines the level of compression to use. The higher the better, but slower, compression. type CompressionLevel uint32 const ( Fast CompressionLevel = 0 Level1 CompressionLevel = 1 << (8 + iota) Level2 Level3 Level4 Level5 Level6 Level7 Level8 Level9 ) // CompressionLevelOption defines the compression level (default=Fast). func CompressionLevelOption(level CompressionLevel) Option { return func(a applier) error { switch w := a.(type) { case nil: s := fmt.Sprintf("CompressionLevelOption(%s)", level) return lz4errors.Error(s) case *Writer: switch level { case Fast, Level1, Level2, Level3, Level4, Level5, Level6, Level7, Level8, Level9: default: return fmt.Errorf("%w: %d", lz4errors.ErrOptionInvalidCompressionLevel, level) } w.level = lz4block.CompressionLevel(level) return nil } return lz4errors.ErrOptionNotApplicable } } func onBlockDone(int) {} // OnBlockDoneOption is triggered when a block has been processed. For a Writer, it is when is has been compressed, // for a Reader, it is when it has been uncompressed. func OnBlockDoneOption(handler func(size int)) Option { if handler == nil { handler = onBlockDone } return func(a applier) error { switch rw := a.(type) { case nil: s := fmt.Sprintf("OnBlockDoneOption(%s)", reflect.TypeOf(handler).String()) return lz4errors.Error(s) case *Writer: rw.handler = handler return nil case *Reader: rw.handler = handler return nil } return lz4errors.ErrOptionNotApplicable } } // LegacyOption provides support for writing LZ4 frames in the legacy format. // // See https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md#legacy-frame. // // NB. compressed Linux kernel images use a tweaked LZ4 legacy format where // the compressed stream is followed by the original (uncompressed) size of // the kernel (https://events.static.linuxfound.org/sites/events/files/lcjpcojp13_klee.pdf). // This is also supported as a special case. func LegacyOption(legacy bool) Option { return func(a applier) error { switch rw := a.(type) { case nil: s := fmt.Sprintf("LegacyOption(%v)", legacy) return lz4errors.Error(s) case *Writer: rw.legacy = legacy return nil } return lz4errors.ErrOptionNotApplicable } }