go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/cmpbin/number.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmpbin
    16  
    17  import (
    18  	"errors"
    19  	"io"
    20  	"math"
    21  )
    22  
    23  // MaxIntLenN is the maximum length of a cmpbin-encoded N-bit integer
    24  // (signed or unsigned).
    25  const (
    26  	MaxIntLen16 = 3
    27  	MaxIntLen32 = 5
    28  	MaxIntLen64 = 9
    29  )
    30  
    31  // ErrOverflow is returned when reading an number which is too large for the
    32  // destination type (either uint64 or int64)
    33  var ErrOverflow = errors.New("cmpbin: varint overflows")
    34  
    35  // ErrUnderflow is returned when reading an number which is too small for the
    36  // destination type (either uint64 or int64)
    37  var ErrUnderflow = errors.New("cmpbin: uvarint underflows")
    38  
    39  var paddingMasks = [...]uint64{
    40  	0xFFFFFFFF00000000,
    41  	0xFFFF0000,
    42  	0xFF00,
    43  	0xF0,
    44  	0xC,
    45  	0x2,
    46  	0x1,
    47  }
    48  
    49  // Calculate the log2 of the unsigned value v.
    50  //
    51  // This is used to find the position of the highest-set bit in v.
    52  //
    53  // from https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
    54  // 32 bit implementation extended to 64 bits
    55  func uint64Log2(v uint64) uint {
    56  	log := uint(0)
    57  	for i, m := range paddingMasks {
    58  		if v&m != 0 {
    59  			shift := uint(1<<uint(len(paddingMasks)-2)) >> uint(i)
    60  			v >>= shift
    61  			log |= shift
    62  		}
    63  	}
    64  	return log + 1
    65  }
    66  
    67  // WriteInt val as a cmpbin Int to the ByteWriter. Returns the number of bytes
    68  // written. Only returns an error if the underlying ByteWriter returns an error.
    69  func WriteInt(w io.ByteWriter, val int64) (int, error) {
    70  	var inv byte
    71  	if val < 0 {
    72  		inv = 0xff
    73  	}
    74  	mag := uint64(val)
    75  	if inv != 0 {
    76  		mag = -mag
    77  	}
    78  	return writeSignMag(w, mag, inv)
    79  }
    80  
    81  // WriteUint writes mag to the ByteWriter. Returns the number of bytes written.
    82  // Only returns an error if the underlying ByteWriter returns an error.
    83  func WriteUint(w io.ByteWriter, mag uint64) (int, error) {
    84  	return writeSignMag(w, mag, 0)
    85  }
    86  
    87  // ReadInt decodes a cmpbin-encoded number from a ByteReader. It returns the
    88  // decoded value and the number of bytes read. The error may be
    89  // Err{Over,Under}flow if the number is out of bounds. It may also return an
    90  // error if the ByteReader returns an error.
    91  func ReadInt(r io.ByteReader) (ret int64, n int, err error) {
    92  	pos, sigs, mag, n, err := readSignMag(r)
    93  	if err != nil {
    94  		return
    95  	}
    96  	if pos {
    97  		if sigs > 63 {
    98  			err = ErrOverflow
    99  		} else {
   100  			ret = int64(mag)
   101  		}
   102  	} else {
   103  		if mag > uint64(-math.MinInt64) {
   104  			err = ErrUnderflow
   105  		} else {
   106  			ret = int64(-mag)
   107  		}
   108  	}
   109  	return
   110  }
   111  
   112  // ReadUint decodes a cmpbin-encoded positive number from a ByteReader.  It
   113  // returns the decoded value and the number of bytes read. The error may be
   114  // Err{Over,Under}flow if the number is out of bounds. It may also return an
   115  // error if the ByteReader returns an error.
   116  func ReadUint(r io.ByteReader) (mag uint64, n int, err error) {
   117  	pos, _, mag, n, err := readSignMag(r)
   118  	if err != nil {
   119  		return
   120  	}
   121  	if !pos {
   122  		err = ErrUnderflow
   123  	}
   124  	return
   125  }
   126  
   127  func writeSignMag(w io.ByteWriter, mag uint64, inv byte) (n int, err error) {
   128  	sigs := uint64Log2(mag)
   129  
   130  	wb := func(b byte) error {
   131  		n++
   132  		return w.WriteByte(b)
   133  	}
   134  
   135  	if err = wb(byte(0x80|(sigs-1)) ^ inv); err != nil {
   136  		return
   137  	}
   138  
   139  	for sigs > 8 {
   140  		sigs -= 8
   141  
   142  		if err = wb(byte(mag>>sigs) ^ inv); err != nil {
   143  			return
   144  		}
   145  	}
   146  	if sigs != 0 {
   147  		if err = wb(byte(mag<<(8-sigs)) ^ inv); err != nil {
   148  			return
   149  		}
   150  	}
   151  
   152  	return
   153  }
   154  
   155  func readSignMag(r io.ByteReader) (positive bool, sigs uint, mag uint64, n int, err error) {
   156  	var inv byte
   157  
   158  	rb := func() (byte, error) {
   159  		n++
   160  		return r.ReadByte()
   161  	}
   162  
   163  	b0, err := rb()
   164  	if err != nil {
   165  		return
   166  	}
   167  	positive = true
   168  	if b0&0x80 == 0 {
   169  		positive = false
   170  		inv = 0xff
   171  	}
   172  
   173  	sigs = uint((b0^inv)&0x7f) + 1
   174  	if sigs > 64 {
   175  		err = ErrOverflow
   176  		return
   177  	}
   178  
   179  	numBytes := int((sigs+7)>>3) + 1
   180  
   181  	var b byte
   182  	shift := uint(64 - 8)
   183  	for i := 1; i < numBytes; i++ {
   184  		b, err = rb()
   185  		if err != nil {
   186  			return
   187  		}
   188  		mag |= uint64(b^inv) << shift
   189  		shift -= 8
   190  	}
   191  	mag >>= 64 - sigs
   192  
   193  	return
   194  }