mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
Fixes #169 Fixes https://github.com/tendermint/tendermint/issues/1322 The previous code was very trusting assuming that rational actors will use this code. However, Byzantine actors don't care and in the case of the linked issue negative lengths can be sent to this code unfettered having been received from a peer. This code is essentially just a sign change from `==` to `<=` and we've gutted out that attack by being more defensive.
324 lines
6.2 KiB
Go
324 lines
6.2 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type BitArray struct {
|
|
mtx sync.Mutex
|
|
Bits int `json:"bits"` // NOTE: persisted via reflect, must be exported
|
|
Elems []uint64 `json:"elems"` // NOTE: persisted via reflect, must be exported
|
|
}
|
|
|
|
// There is no BitArray whose Size is 0. Use nil instead.
|
|
func NewBitArray(bits int) *BitArray {
|
|
if bits <= 0 {
|
|
return nil
|
|
}
|
|
return &BitArray{
|
|
Bits: bits,
|
|
Elems: make([]uint64, (bits+63)/64),
|
|
}
|
|
}
|
|
|
|
func (bA *BitArray) Size() int {
|
|
if bA == nil {
|
|
return 0
|
|
}
|
|
return bA.Bits
|
|
}
|
|
|
|
// NOTE: behavior is undefined if i >= bA.Bits
|
|
func (bA *BitArray) GetIndex(i int) bool {
|
|
if bA == nil {
|
|
return false
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
return bA.getIndex(i)
|
|
}
|
|
|
|
func (bA *BitArray) getIndex(i int) bool {
|
|
if i >= bA.Bits {
|
|
return false
|
|
}
|
|
return bA.Elems[i/64]&(uint64(1)<<uint(i%64)) > 0
|
|
}
|
|
|
|
// NOTE: behavior is undefined if i >= bA.Bits
|
|
func (bA *BitArray) SetIndex(i int, v bool) bool {
|
|
if bA == nil {
|
|
return false
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
return bA.setIndex(i, v)
|
|
}
|
|
|
|
func (bA *BitArray) setIndex(i int, v bool) bool {
|
|
if i >= bA.Bits {
|
|
return false
|
|
}
|
|
if v {
|
|
bA.Elems[i/64] |= (uint64(1) << uint(i%64))
|
|
} else {
|
|
bA.Elems[i/64] &= ^(uint64(1) << uint(i%64))
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (bA *BitArray) Copy() *BitArray {
|
|
if bA == nil {
|
|
return nil
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
return bA.copy()
|
|
}
|
|
|
|
func (bA *BitArray) copy() *BitArray {
|
|
c := make([]uint64, len(bA.Elems))
|
|
copy(c, bA.Elems)
|
|
return &BitArray{
|
|
Bits: bA.Bits,
|
|
Elems: c,
|
|
}
|
|
}
|
|
|
|
func (bA *BitArray) copyBits(bits int) *BitArray {
|
|
c := make([]uint64, (bits+63)/64)
|
|
copy(c, bA.Elems)
|
|
return &BitArray{
|
|
Bits: bits,
|
|
Elems: c,
|
|
}
|
|
}
|
|
|
|
// Returns a BitArray of larger bits size.
|
|
func (bA *BitArray) Or(o *BitArray) *BitArray {
|
|
if bA == nil && o == nil {
|
|
return nil
|
|
}
|
|
if bA == nil && o != nil {
|
|
return o.Copy()
|
|
}
|
|
if o == nil {
|
|
return bA.Copy()
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
c := bA.copyBits(MaxInt(bA.Bits, o.Bits))
|
|
for i := 0; i < len(c.Elems); i++ {
|
|
c.Elems[i] |= o.Elems[i]
|
|
}
|
|
return c
|
|
}
|
|
|
|
// Returns a BitArray of smaller bit size.
|
|
func (bA *BitArray) And(o *BitArray) *BitArray {
|
|
if bA == nil || o == nil {
|
|
return nil
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
return bA.and(o)
|
|
}
|
|
|
|
func (bA *BitArray) and(o *BitArray) *BitArray {
|
|
c := bA.copyBits(MinInt(bA.Bits, o.Bits))
|
|
for i := 0; i < len(c.Elems); i++ {
|
|
c.Elems[i] &= o.Elems[i]
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (bA *BitArray) Not() *BitArray {
|
|
if bA == nil {
|
|
return nil // Degenerate
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
c := bA.copy()
|
|
for i := 0; i < len(c.Elems); i++ {
|
|
c.Elems[i] = ^c.Elems[i]
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (bA *BitArray) Sub(o *BitArray) *BitArray {
|
|
if bA == nil || o == nil {
|
|
// TODO: Decide if we should do 1's complement here?
|
|
return nil
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
if bA.Bits > o.Bits {
|
|
c := bA.copy()
|
|
for i := 0; i < len(o.Elems)-1; i++ {
|
|
c.Elems[i] &= ^c.Elems[i]
|
|
}
|
|
i := len(o.Elems) - 1
|
|
if i >= 0 {
|
|
for idx := i * 64; idx < o.Bits; idx++ {
|
|
// NOTE: each individual GetIndex() call to o is safe.
|
|
c.setIndex(idx, c.getIndex(idx) && !o.GetIndex(idx))
|
|
}
|
|
}
|
|
return c
|
|
} else {
|
|
return bA.and(o.Not()) // Note degenerate case where o == nil
|
|
}
|
|
}
|
|
|
|
func (bA *BitArray) IsEmpty() bool {
|
|
if bA == nil {
|
|
return true // should this be opposite?
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
for _, e := range bA.Elems {
|
|
if e > 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (bA *BitArray) IsFull() bool {
|
|
if bA == nil {
|
|
return true
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
|
|
// Check all elements except the last
|
|
for _, elem := range bA.Elems[:len(bA.Elems)-1] {
|
|
if (^elem) != 0 {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Check that the last element has (lastElemBits) 1's
|
|
lastElemBits := (bA.Bits+63)%64 + 1
|
|
lastElem := bA.Elems[len(bA.Elems)-1]
|
|
return (lastElem+1)&((uint64(1)<<uint(lastElemBits))-1) == 0
|
|
}
|
|
|
|
func (bA *BitArray) PickRandom() (int, bool) {
|
|
if bA == nil {
|
|
return 0, false
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
|
|
length := len(bA.Elems)
|
|
if length == 0 {
|
|
return 0, false
|
|
}
|
|
randElemStart := RandIntn(length)
|
|
for i := 0; i < length; i++ {
|
|
elemIdx := ((i + randElemStart) % length)
|
|
if elemIdx < length-1 {
|
|
if bA.Elems[elemIdx] > 0 {
|
|
randBitStart := RandIntn(64)
|
|
for j := 0; j < 64; j++ {
|
|
bitIdx := ((j + randBitStart) % 64)
|
|
if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 {
|
|
return 64*elemIdx + bitIdx, true
|
|
}
|
|
}
|
|
PanicSanity("should not happen")
|
|
}
|
|
} else {
|
|
// Special case for last elem, to ignore straggler bits
|
|
elemBits := bA.Bits % 64
|
|
if elemBits == 0 {
|
|
elemBits = 64
|
|
}
|
|
randBitStart := RandIntn(elemBits)
|
|
for j := 0; j < elemBits; j++ {
|
|
bitIdx := ((j + randBitStart) % elemBits)
|
|
if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 {
|
|
return 64*elemIdx + bitIdx, true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (bA *BitArray) String() string {
|
|
if bA == nil {
|
|
return "nil-BitArray"
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
return bA.stringIndented("")
|
|
}
|
|
|
|
func (bA *BitArray) StringIndented(indent string) string {
|
|
if bA == nil {
|
|
return "nil-BitArray"
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
return bA.stringIndented(indent)
|
|
}
|
|
|
|
func (bA *BitArray) stringIndented(indent string) string {
|
|
|
|
lines := []string{}
|
|
bits := ""
|
|
for i := 0; i < bA.Bits; i++ {
|
|
if bA.getIndex(i) {
|
|
bits += "X"
|
|
} else {
|
|
bits += "_"
|
|
}
|
|
if i%100 == 99 {
|
|
lines = append(lines, bits)
|
|
bits = ""
|
|
}
|
|
if i%10 == 9 {
|
|
bits += " "
|
|
}
|
|
if i%50 == 49 {
|
|
bits += " "
|
|
}
|
|
}
|
|
if len(bits) > 0 {
|
|
lines = append(lines, bits)
|
|
}
|
|
return fmt.Sprintf("BA{%v:%v}", bA.Bits, strings.Join(lines, indent))
|
|
}
|
|
|
|
func (bA *BitArray) Bytes() []byte {
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
|
|
numBytes := (bA.Bits + 7) / 8
|
|
bytes := make([]byte, numBytes)
|
|
for i := 0; i < len(bA.Elems); i++ {
|
|
elemBytes := [8]byte{}
|
|
binary.LittleEndian.PutUint64(elemBytes[:], bA.Elems[i])
|
|
copy(bytes[i*8:], elemBytes[:])
|
|
}
|
|
return bytes
|
|
}
|
|
|
|
// NOTE: other bitarray o is not locked when reading,
|
|
// so if necessary, caller must copy or lock o prior to calling Update.
|
|
// If bA is nil, does nothing.
|
|
func (bA *BitArray) Update(o *BitArray) {
|
|
if bA == nil || o == nil {
|
|
return
|
|
}
|
|
bA.mtx.Lock()
|
|
defer bA.mtx.Unlock()
|
|
|
|
copy(bA.Elems, o.Elems)
|
|
}
|