| package zstd |
| |
| import ( |
| "fmt" |
| "runtime" |
| "strings" |
| ) |
| |
| // EOption is an option for creating a encoder. |
| type EOption func(*encoderOptions) error |
| |
| // options retains accumulated state of multiple options. |
| type encoderOptions struct { |
| concurrent int |
| crc bool |
| single *bool |
| pad int |
| blockSize int |
| windowSize int |
| level EncoderLevel |
| fullZero bool |
| } |
| |
| func (o *encoderOptions) setDefault() { |
| *o = encoderOptions{ |
| // use less ram: true for now, but may change. |
| concurrent: runtime.GOMAXPROCS(0), |
| crc: true, |
| single: nil, |
| blockSize: 1 << 16, |
| windowSize: 1 << 22, |
| level: SpeedDefault, |
| } |
| } |
| |
| // encoder returns an encoder with the selected options. |
| func (o encoderOptions) encoder() encoder { |
| switch o.level { |
| case SpeedDefault: |
| return &doubleFastEncoder{fastEncoder: fastEncoder{maxMatchOff: int32(o.windowSize)}} |
| case SpeedFastest: |
| return &fastEncoder{maxMatchOff: int32(o.windowSize)} |
| } |
| panic("unknown compression level") |
| } |
| |
| // WithEncoderCRC will add CRC value to output. |
| // Output will be 4 bytes larger. |
| func WithEncoderCRC(b bool) EOption { |
| return func(o *encoderOptions) error { o.crc = b; return nil } |
| } |
| |
| // WithEncoderConcurrency will set the concurrency, |
| // meaning the maximum number of decoders to run concurrently. |
| // The value supplied must be at least 1. |
| // By default this will be set to GOMAXPROCS. |
| func WithEncoderConcurrency(n int) EOption { |
| return func(o *encoderOptions) error { |
| if n <= 0 { |
| return fmt.Errorf("concurrency must be at least 1") |
| } |
| o.concurrent = n |
| return nil |
| } |
| } |
| |
| // WithEncoderPadding will add padding to all output so the size will be a multiple of n. |
| // This can be used to obfuscate the exact output size or make blocks of a certain size. |
| // The contents will be a skippable frame, so it will be invisible by the decoder. |
| // n must be > 0 and <= 1GB, 1<<30 bytes. |
| // The padded area will be filled with data from crypto/rand.Reader. |
| // If `EncodeAll` is used with data already in the destination, the total size will be multiple of this. |
| func WithEncoderPadding(n int) EOption { |
| return func(o *encoderOptions) error { |
| if n <= 0 { |
| return fmt.Errorf("padding must be at least 1") |
| } |
| // No need to waste our time. |
| if n == 1 { |
| o.pad = 0 |
| } |
| if n > 1<<30 { |
| return fmt.Errorf("padding must less than 1GB (1<<30 bytes) ") |
| } |
| o.pad = n |
| return nil |
| } |
| } |
| |
| // EncoderLevel predefines encoder compression levels. |
| // Only use the constants made available, since the actual mapping |
| // of these values are very likely to change and your compression could change |
| // unpredictably when upgrading the library. |
| type EncoderLevel int |
| |
| const ( |
| speedNotSet EncoderLevel = iota |
| |
| // SpeedFastest will choose the fastest reasonable compression. |
| // This is roughly equivalent to the fastest Zstandard mode. |
| SpeedFastest |
| |
| // SpeedDefault is the default "pretty fast" compression option. |
| // This is roughly equivalent to the default Zstandard mode (level 3). |
| SpeedDefault |
| |
| // speedLast should be kept as the last actual compression option. |
| // The is not for external usage, but is used to keep track of the valid options. |
| speedLast |
| |
| // SpeedBetterCompression will (in the future) yield better compression than the default, |
| // but at approximately 4x the CPU usage of the default. |
| // For now this is not implemented. |
| SpeedBetterCompression = SpeedDefault |
| |
| // SpeedBestCompression will choose the best available compression option. |
| // For now this is not implemented. |
| SpeedBestCompression = SpeedDefault |
| ) |
| |
| // EncoderLevelFromString will convert a string representation of an encoding level back |
| // to a compression level. The compare is not case sensitive. |
| // If the string wasn't recognized, (false, SpeedDefault) will be returned. |
| func EncoderLevelFromString(s string) (bool, EncoderLevel) { |
| for l := EncoderLevel(speedNotSet + 1); l < speedLast; l++ { |
| if strings.EqualFold(s, l.String()) { |
| return true, l |
| } |
| } |
| return false, SpeedDefault |
| } |
| |
| // EncoderLevelFromZstd will return an encoder level that closest matches the compression |
| // ratio of a specific zstd compression level. |
| // Many input values will provide the same compression level. |
| func EncoderLevelFromZstd(level int) EncoderLevel { |
| switch { |
| case level < 3: |
| return SpeedFastest |
| case level >= 3: |
| return SpeedDefault |
| } |
| return SpeedDefault |
| } |
| |
| // String provides a string representation of the compression level. |
| func (e EncoderLevel) String() string { |
| switch e { |
| case SpeedFastest: |
| return "fastest" |
| case SpeedDefault: |
| return "default" |
| default: |
| return "invalid" |
| } |
| } |
| |
| // WithEncoderLevel specifies a predefined compression level. |
| func WithEncoderLevel(l EncoderLevel) EOption { |
| return func(o *encoderOptions) error { |
| switch { |
| case l <= speedNotSet || l >= speedLast: |
| return fmt.Errorf("unknown encoder level") |
| } |
| o.level = l |
| return nil |
| } |
| } |
| |
| // WithZeroFrames will encode 0 length input as full frames. |
| // This can be needed for compatibility with zstandard usage, |
| // but is not needed for this package. |
| func WithZeroFrames(b bool) EOption { |
| return func(o *encoderOptions) error { |
| o.fullZero = b |
| return nil |
| } |
| } |
| |
| // WithSingleSegment will set the "single segment" flag when EncodeAll is used. |
| // If this flag is set, data must be regenerated within a single continuous memory segment. |
| // In this case, Window_Descriptor byte is skipped, but Frame_Content_Size is necessarily present. |
| // As a consequence, the decoder must allocate a memory segment of size equal or larger than size of your content. |
| // In order to preserve the decoder from unreasonable memory requirements, |
| // a decoder is allowed to reject a compressed frame which requests a memory size beyond decoder's authorized range. |
| // For broader compatibility, decoders are recommended to support memory sizes of at least 8 MB. |
| // This is only a recommendation, each decoder is free to support higher or lower limits, depending on local limitations. |
| // If this is not specified, block encodes will automatically choose this based on the input size. |
| // This setting has no effect on streamed encodes. |
| func WithSingleSegment(b bool) EOption { |
| return func(o *encoderOptions) error { |
| o.single = &b |
| return nil |
| } |
| } |