cosmos-sdk/store/internal/encoding/encoding.go

167 lines
4.4 KiB
Go

package encoding
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math/bits"
"sync"
)
var bufPool = &sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
var varintPool = &sync.Pool{
New: func() interface{} {
return &[binary.MaxVarintLen64]byte{}
},
}
var uvarintPool = &sync.Pool{
New: func() interface{} {
return &[binary.MaxVarintLen64]byte{}
},
}
// decodeBytes decodes a varint length-prefixed byte slice, returning it along with the number
// of input bytes read.
// Assumes bz will not be mutated.
func DecodeBytes(bz []byte) ([]byte, int, error) {
s, n, err := DecodeUvarint(bz)
if err != nil {
return nil, n, err
}
// Make sure size doesn't overflow. ^uint(0) >> 1 will help determine the
// max int value variably on 32-bit and 64-bit machines. We also doublecheck
// that size is positive.
size := int(s)
if s >= uint64(^uint(0)>>1) || size < 0 {
return nil, n, fmt.Errorf("invalid out of range length %v decoding []byte", s)
}
// Make sure end index doesn't overflow. We know n>0 from decodeUvarint().
end := n + size
if end < n {
return nil, n, fmt.Errorf("invalid out of range length %v decoding []byte", size)
}
// Make sure the end index is within bounds.
if len(bz) < end {
return nil, n, fmt.Errorf("insufficient bytes decoding []byte of length %v", size)
}
return bz[n:end], end, nil
}
// decodeUvarint decodes a varint-encoded unsigned integer from a byte slice, returning it and the
// number of bytes decoded.
func DecodeUvarint(bz []byte) (uint64, int, error) {
u, n := binary.Uvarint(bz)
if n == 0 {
// buf too small
return u, n, errors.New("buffer too small")
} else if n < 0 {
// value larger than 64 bits (overflow)
// and -n is the number of bytes read
n = -n
return u, n, errors.New("EOF decoding uvarint")
}
return u, n, nil
}
// decodeVarint decodes a varint-encoded integer from a byte slice, returning it and the number of
// bytes decoded.
func DecodeVarint(bz []byte) (int64, int, error) {
i, n := binary.Varint(bz)
if n == 0 {
return i, n, errors.New("buffer too small")
} else if n < 0 {
// value larger than 64 bits (overflow)
// and -n is the number of bytes read
n = -n
return i, n, errors.New("EOF decoding varint")
}
return i, n, nil
}
// EncodeBytes writes a varint length-prefixed byte slice to the writer.
func EncodeBytes(w io.Writer, bz []byte) error {
err := EncodeUvarint(w, uint64(len(bz)))
if err != nil {
return err
}
_, err = w.Write(bz)
return err
}
// encodeBytesSlice length-prefixes the byte slice and returns it.
func EncodeBytesSlice(bz []byte) ([]byte, error) {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
err := EncodeBytes(buf, bz)
bytesCopy := make([]byte, buf.Len())
copy(bytesCopy, buf.Bytes())
return bytesCopy, err
}
// encodeBytesSize returns the byte size of the given slice including length-prefixing.
func EncodeBytesSize(bz []byte) int {
return EncodeUvarintSize(uint64(len(bz))) + len(bz)
}
// EncodeUvarint writes a varint-encoded unsigned integer to an io.Writer.
func EncodeUvarint(w io.Writer, u uint64) error {
// See comment in encodeVarint
buf := uvarintPool.Get().(*[binary.MaxVarintLen64]byte)
n := binary.PutUvarint(buf[:], u)
_, err := w.Write(buf[0:n])
uvarintPool.Put(buf)
return err
}
// EncodeUvarintSize returns the byte size of the given integer as a varint.
func EncodeUvarintSize(u uint64) int {
if u == 0 {
return 1
}
return (bits.Len64(u) + 6) / 7
}
// EncodeVarint writes a varint-encoded integer to an io.Writer.
func EncodeVarint(w io.Writer, i int64) error {
// Use a pool here to reduce allocations.
//
// Though this allocates just 10 bytes on the stack, doing allocation for every calls
// cost us a huge memory. The profiling show that using pool save us ~30% memory.
//
// Since when we don't have concurrent access to the pool, the speed will nearly identical.
// If we need to support concurrent access, we can accept a *[binary.MaxVarintLen64]byte as
// input, so the caller can allocate just one and pass the same array pointer to each call.
buf := varintPool.Get().(*[binary.MaxVarintLen64]byte)
n := binary.PutVarint(buf[:], i)
_, err := w.Write(buf[0:n])
varintPool.Put(buf)
return err
}
// EncodeVarintSize returns the byte size of the given integer as a varint.
func EncodeVarintSize(i int64) int {
ux := uint64(i) << 1
if i < 0 {
ux = ^ux
}
return EncodeUvarintSize(ux)
}