blob: 7e8360d47dcf461cc110ed8960c87243a071d4ae [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpbin
import (
"errors"
"io"
"math"
)
// MaxIntLenN is the maximum length of a cmpbin-encoded N-bit integer
// (signed or unsigned).
const (
MaxIntLen16 = 3
MaxIntLen32 = 5
MaxIntLen64 = 9
)
// ErrOverflow is returned when reading an number which is too large for the
// destination type (either uint64 or int64)
var ErrOverflow = errors.New("cmpbin: varint overflows")
// ErrUnderflow is returned when reading an number which is too small for the
// destination type (either uint64 or int64)
var ErrUnderflow = errors.New("cmpbin: uvarint underflows")
var paddingMasks = [...]uint64{
0xFFFFFFFF00000000,
0xFFFF0000,
0xFF00,
0xF0,
0xC,
0x2,
0x1,
}
// Calculate the log2 of the unsigned value v.
//
// This is used to find the position of the highest-set bit in v.
//
// from https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
// 32 bit implementation extended to 64 bits
func uint64Log2(v uint64) uint {
log := uint(0)
for i, m := range paddingMasks {
if v&m != 0 {
shift := uint(1<<uint(len(paddingMasks)-2)) >> uint(i)
v >>= shift
log |= shift
}
}
return log + 1
}
// WriteInt val as a cmpbin Int to the ByteWriter. Returns the number of bytes
// written. Only returns an error if the underlying ByteWriter returns an error.
func WriteInt(w io.ByteWriter, val int64) (int, error) {
var inv byte
if val < 0 {
inv = 0xff
}
mag := uint64(val)
if inv != 0 {
mag = -mag
}
return writeSignMag(w, mag, inv)
}
// WriteUint writes mag to the ByteWriter. Returns the number of bytes written.
// Only returns an error if the underlying ByteWriter returns an error.
func WriteUint(w io.ByteWriter, mag uint64) (int, error) {
return writeSignMag(w, mag, 0)
}
// ReadInt decodes a cmpbin-encoded number from a ByteReader. It returns the
// decoded value and the number of bytes read. The error may be
// Err{Over,Under}flow if the number is out of bounds. It may also return an
// error if the ByteReader returns an error.
func ReadInt(r io.ByteReader) (ret int64, n int, err error) {
pos, sigs, mag, n, err := readSignMag(r)
if err != nil {
return
}
if pos {
if sigs > 63 {
err = ErrOverflow
} else {
ret = int64(mag)
}
} else {
if mag > uint64(-math.MinInt64) {
err = ErrUnderflow
} else {
ret = int64(-mag)
}
}
return
}
// ReadUint decodes a cmpbin-encoded positive number from a ByteReader. It
// returns the decoded value and the number of bytes read. The error may be
// Err{Over,Under}flow if the number is out of bounds. It may also return an
// error if the ByteReader returns an error.
func ReadUint(r io.ByteReader) (mag uint64, n int, err error) {
pos, _, mag, n, err := readSignMag(r)
if err != nil {
return
}
if !pos {
err = ErrUnderflow
}
return
}
func writeSignMag(w io.ByteWriter, mag uint64, inv byte) (n int, err error) {
sigs := uint64Log2(mag)
wb := func(b byte) error {
n++
return w.WriteByte(b)
}
if err = wb(byte(0x80|(sigs-1)) ^ inv); err != nil {
return
}
for sigs > 8 {
sigs -= 8
if err = wb(byte(mag>>sigs) ^ inv); err != nil {
return
}
}
if sigs != 0 {
if err = wb(byte(mag<<(8-sigs)) ^ inv); err != nil {
return
}
}
return
}
func readSignMag(r io.ByteReader) (positive bool, sigs uint, mag uint64, n int, err error) {
var inv byte
rb := func() (byte, error) {
n++
return r.ReadByte()
}
b0, err := rb()
if err != nil {
return
}
positive = true
if b0&0x80 == 0 {
positive = false
inv = 0xff
}
sigs = uint((b0^inv)&0x7f) + 1
if sigs > 64 {
err = ErrOverflow
return
}
numBytes := int((sigs+7)>>3) + 1
var b byte
shift := uint(64 - 8)
for i := 1; i < numBytes; i++ {
b, err = rb()
if err != nil {
return
}
mag |= uint64(b^inv) << shift
shift -= 8
}
mag >>= 64 - sigs
return
}