Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 38 additions & 52 deletions internal/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,58 @@ import (
"sync"
)

const hash32ByteLength = 32

// bufPool provides temporary buffers to reduce allocations.
var bufPool = &sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

// varintPool provides temporary slices for varint encoding.
var varintPool = &sync.Pool{
New: func() interface{} {
return &[binary.MaxVarintLen64]byte{}
},
}

// uvarintPool provides temporary slices for uvarint encoding.
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 writeBytes(w io.Writer, b []byte) error {
_, err := w.Write(b)
return err
}

// 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)
// Check for overflow or negative sizes.
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
// Check for overflow of end index.
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.
// DecodeUvarint decodes a varint-encoded unsigned integer from a byte slice.
func DecodeUvarint(bz []byte) (uint64, int, error) {
u, n := binary.Uvarint(bz)
if n == 0 {
Expand All @@ -71,15 +76,12 @@ func DecodeUvarint(bz []byte) (uint64, int, error) {
return u, n, nil
}

// decodeVarint decodes a varint-encoded integer from a byte slice, returning it and the number of
// bytes decoded.
// DecodeVarint decodes a varint-encoded integer from a byte slice.
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")
}
Expand All @@ -88,29 +90,32 @@ func DecodeVarint(bz []byte) (int64, int, error) {

// 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 {
if err := EncodeUvarint(w, uint64(len(bz))); err != nil {
return err
}
_, err = w.Write(bz)
return err
}

var hashLenBz []byte

func init() {
hashLenBz = make([]byte, 1)
binary.PutUvarint(hashLenBz, 32)
return writeBytes(w, bz)
}

// Encode 32 byte long hash
// Encode32BytesHash writes a hardcoded 1-byte length prefix (32) and then a 32-byte hash.
func Encode32BytesHash(w io.Writer, bz []byte) error {
_, err := w.Write(hashLenBz)
if err != nil {
if len(bz) != hash32ByteLength {
return fmt.Errorf("expected %d-byte hash, got %d bytes", hash32ByteLength, len(bz))
}
if err := writeBytes(w, []byte{hash32ByteLength}); err != nil {
return err
}
_, err = w.Write(bz)
return err
return writeBytes(w, bz)
}

// Encode32BytesHashSlice returns a length-prefixed 32-byte hash as a slice.
func Encode32BytesHashSlice(bz []byte) ([]byte, error) {
if len(bz) != hash32ByteLength {
return nil, fmt.Errorf("expected %d-byte hash, got %d bytes", hash32ByteLength, len(bz))
}
out := make([]byte, 1+hash32ByteLength)
out[0] = hash32ByteLength
copy(out[1:], bz)
return out, nil
}

// EncodeBytesSlice length-prefixes the byte slice and returns it.
Expand All @@ -120,10 +125,8 @@ func EncodeBytesSlice(bz []byte) ([]byte, error) {
defer bufPool.Put(buf)

err := EncodeBytes(buf, bz)

bytesCopy := make([]byte, buf.Len())
copy(bytesCopy, buf.Bytes())

return bytesCopy, err
}

Expand All @@ -134,14 +137,10 @@ func EncodeBytesSize(bz []byte) int {

// 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])

_, err := w.Write(buf[:n])
uvarintPool.Put(buf)

return err
}

Expand All @@ -158,22 +157,10 @@ func EncodeVarint(w io.Writer, i int64) error {
if bw, ok := w.(io.ByteWriter); ok {
return fVarintEncode(bw, i)
}

// 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])

_, err := w.Write(buf[:n])
varintPool.Put(buf)

return err
}

Expand All @@ -183,7 +170,7 @@ func fVarintEncode(bw io.ByteWriter, x int64) error {
if x < 0 {
ux = ^ux
}
for ux >= 0x80 { // While there are 7 or more bits in the value, keep going
for ux >= 0x80 {
// Convert it into a byte then toggle the
// 7th bit to indicate that more bytes coming.
// byte(x & 0x7f) is redundant but useful for illustrative
Expand All @@ -193,7 +180,6 @@ func fVarintEncode(bw io.ByteWriter, x int64) error {
}
ux >>= 7
}

return bw.WriteByte(byte(ux & 0x7f))
}

Expand Down
Loading