package proto

import (
	
	
	
	
	

	
	
	

	
)

type gzipPool struct {
	writers sync.Pool
	readers sync.Pool
}

func () *gzipPool {
	return &gzipPool{
		writers: sync.Pool{
			New: func() interface{} {
				return gzip.NewWriter(nil)
			},
		},
		readers: sync.Pool{},
	}
}

func ( *gzipPool) ( io.Writer) *gzip.Writer {
	 := .writers.Get().(*gzip.Writer)
	.Reset()
	return 
}

func ( *gzipPool) ( *gzip.Writer) {
	.writers.Put()
}

func ( *gzipPool) ( io.Reader) (*gzip.Reader, error) {
	,  := .readers.Get().(*gzip.Reader)
	if ! {
		,  := gzip.NewReader()
		if  != nil {
			return nil, 
		}
		return , nil
	}

	if  := .Reset();  != nil {
		.readers.Put()
		return nil, 
	}
	return , nil
}

func ( *gzipPool) ( *gzip.Reader) {
	.readers.Put()
}

// GZIP represents a Packed Object.
//
// Used to replace any other object (or rather, a serialization thereof)
// with its archived (gzipped) representation.
type GZIP struct {
	Data []byte
}

// GZIPTypeID is TL type id of GZIP.
const GZIPTypeID = 0x3072cfa1

var (
	gzipRWPool  = newGzipPool()
	gzipBufPool = sync.Pool{New: func() interface{} {
		return bytes.NewBuffer(nil)
	}}
)

// Encode implements bin.Encoder.
func ( GZIP) ( *bin.Buffer) ( error) {
	.PutID(GZIPTypeID)

	// Writing compressed data to buf.
	 := gzipBufPool.Get().(*bytes.Buffer)
	.Reset()
	defer gzipBufPool.Put()

	 := gzipRWPool.GetWriter()
	defer func() {
		if  := .Close();  != nil {
			 = errors.Wrap(, "close")
			multierr.AppendInto(&, )
		}
		gzipRWPool.PutWriter()
	}()
	if ,  := .Write(.Data);  != nil {
		return errors.Wrap(, "compress")
	}
	if  := .Close();  != nil {
		return errors.Wrap(, "close")
	}

	// Writing compressed data as bytes.
	.PutBytes(.Bytes())

	return nil
}

type countReader struct {
	reader io.Reader
	read   int64
}

func ( *countReader) () int64 {
	return atomic.LoadInt64(&.read)
}

func ( *countReader) ( []byte) ( int,  error) {
	,  = .reader.Read()
	atomic.AddInt64(&.read, int64())
	return , 
}

// DecompressionBombErr means that GZIP decode detected decompression bomb
// which decompressed payload is significantly higher than initial compressed
// size and stopped decompression to prevent OOM.
type DecompressionBombErr struct {
	Compressed   int
	Decompressed int
}

func ( *DecompressionBombErr) () string {
	return fmt.Sprintf("payload too big (expanded %d bytes to greater than %d)",
		.Compressed, .Decompressed,
	)
}

// Decode implements bin.Decoder.
func ( *GZIP) ( *bin.Buffer) ( error) {
	if  := .ConsumeID(GZIPTypeID);  != nil {
		return 
	}
	,  := .Bytes()
	if  != nil {
		return 
	}

	,  := gzipRWPool.GetReader(bytes.NewReader())
	if  != nil {
		return errors.Wrap(, "gzip error")
	}
	defer func() {
		if  := .Close();  != nil {
			 = errors.Wrap(, "close")
			multierr.AppendInto(&, )
		}
		gzipRWPool.PutReader()
	}()

	// Apply mitigation for reading too much data which can result in OOM.
	const  = 1024 * 1024 * 10 // 10 mb
	 := &countReader{
		reader: io.LimitReader(, ),
	}
	if .Data,  = io.ReadAll();  != nil {
		return errors.Wrap(, "decompress")
	}
	if .Total() >=  {
		// Read limit reached, possible decompression bomb detected.
		return errors.Wrap(&DecompressionBombErr{
			Compressed:   ,
			Decompressed: int(.Total()),
		}, "decompress")
	}

	if  := .Close();  != nil {
		return errors.Wrap(, "checksum")
	}

	return nil
}