- package bits
-
- import (
- "encoding/binary"
- "errors"
- "fmt"
- "math"
- "math/rand"
- "regexp"
- "strings"
- "sync"
-
- tmmath "github.com/tendermint/tendermint/libs/math"
- tmprotobits "github.com/tendermint/tendermint/proto/tendermint/libs/bits"
- )
-
- // BitArray is a thread-safe implementation of a bit array.
- 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
- }
-
- // NewBitArray returns a new bit array.
- // It returns nil if the number of bits is zero.
- func NewBitArray(bits int) *BitArray {
- if bits <= 0 {
- return nil
- }
- bA := &BitArray{}
- bA.reset(bits)
- return bA
- }
-
- // reset changes size of BitArray to `bits` and re-allocates (zeroed) data buffer
- func (bA *BitArray) reset(bits int) {
- bA.mtx.Lock()
- defer bA.mtx.Unlock()
-
- bA.Bits = bits
- if bits == 0 {
- bA.Elems = nil
- } else {
- bA.Elems = make([]uint64, numElems(bits))
- }
- }
-
- // Size returns the number of bits in the bitarray
- func (bA *BitArray) Size() int {
- if bA == nil {
- return 0
- }
- return bA.Bits
- }
-
- // GetIndex returns the bit at index i within the bit array.
- // The 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
- }
-
- // SetIndex sets the bit at index i within the bit array.
- // This method returns false if i is out of range of the BitArray.
- 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 < 0 || 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
- }
-
- // Copy returns a copy of the provided bit array.
- 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, numElems(bits))
- copy(c, bA.Elems)
- return &BitArray{
- Bits: bits,
- Elems: c,
- }
- }
-
- // Or returns a bit array resulting from a bitwise OR of the two bit arrays.
- // If the two bit-arrys have different lengths, Or right-pads the smaller of the two bit-arrays with zeroes.
- // Thus the size of the return value is the maximum of the two provided bit arrays.
- 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()
- o.mtx.Lock()
- c := bA.copyBits(tmmath.MaxInt(bA.Bits, o.Bits))
- smaller := tmmath.MinInt(len(bA.Elems), len(o.Elems))
- for i := 0; i < smaller; i++ {
- c.Elems[i] |= o.Elems[i]
- }
- bA.mtx.Unlock()
- o.mtx.Unlock()
- return c
- }
-
- // And returns a bit array resulting from a bitwise AND of the two bit arrays.
- // If the two bit-arrys have different lengths, this truncates the larger of the two bit-arrays from the right.
- // Thus the size of the return value is the minimum of the two provided bit arrays.
- func (bA *BitArray) And(o *BitArray) *BitArray {
- if bA == nil || o == nil {
- return nil
- }
- bA.mtx.Lock()
- o.mtx.Lock()
- defer func() {
- bA.mtx.Unlock()
- o.mtx.Unlock()
- }()
- return bA.and(o)
- }
-
- func (bA *BitArray) and(o *BitArray) *BitArray {
- c := bA.copyBits(tmmath.MinInt(bA.Bits, o.Bits))
- for i := 0; i < len(c.Elems); i++ {
- c.Elems[i] &= o.Elems[i]
- }
- return c
- }
-
- // Not returns a bit array resulting from a bitwise Not of the provided bit array.
- func (bA *BitArray) Not() *BitArray {
- if bA == nil {
- return nil // Degenerate
- }
- bA.mtx.Lock()
- defer bA.mtx.Unlock()
- return bA.not()
- }
-
- func (bA *BitArray) not() *BitArray {
- c := bA.copy()
- for i := 0; i < len(c.Elems); i++ {
- c.Elems[i] = ^c.Elems[i]
- }
- return c
- }
-
- // Sub subtracts the two bit-arrays bitwise, without carrying the bits.
- // Note that carryless subtraction of a - b is (a and not b).
- // The output is the same as bA, regardless of o's size.
- // If bA is longer than o, o is right padded with zeroes
- 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()
- o.mtx.Lock()
- // output is the same size as bA
- c := bA.copyBits(bA.Bits)
- // Only iterate to the minimum size between the two.
- // If o is longer, those bits are ignored.
- // If bA is longer, then skipping those iterations is equivalent
- // to right padding with 0's
- smaller := tmmath.MinInt(len(bA.Elems), len(o.Elems))
- for i := 0; i < smaller; i++ {
- // &^ is and not in golang
- c.Elems[i] &^= o.Elems[i]
- }
- bA.mtx.Unlock()
- o.mtx.Unlock()
- return c
- }
-
- // IsEmpty returns true iff all bits in the bit array are 0
- 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
- }
-
- // IsFull returns true iff all bits in the bit array are 1.
- 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
- }
-
- // PickRandom returns a random index for a set bit in the bit array.
- // If there is no such value, it returns 0, false.
- // It uses math/rand's global randomness Source to get this index.
- func (bA *BitArray) PickRandom() (int, bool) {
- if bA == nil {
- return 0, false
- }
-
- bA.mtx.Lock()
- trueIndices := bA.getTrueIndices()
- bA.mtx.Unlock()
-
- if len(trueIndices) == 0 { // no bits set to true
- return 0, false
- }
-
- // NOTE: using the default math/rand might result in somewhat
- // amount of determinism here. It would be possible to use
- // rand.New(rand.NewSeed(time.Now().Unix())).Intn() to
- // counteract this possibility if it proved to be material.
- //
- // nolint:gosec // G404: Use of weak random number generator
- return trueIndices[rand.Intn(len(trueIndices))], true
- }
-
- func (bA *BitArray) getTrueIndices() []int {
- trueIndices := make([]int, 0, bA.Bits)
- curBit := 0
- numElems := len(bA.Elems)
- // set all true indices
- for i := 0; i < numElems-1; i++ {
- elem := bA.Elems[i]
- if elem == 0 {
- curBit += 64
- continue
- }
- for j := 0; j < 64; j++ {
- if (elem & (uint64(1) << uint64(j))) > 0 {
- trueIndices = append(trueIndices, curBit)
- }
- curBit++
- }
- }
- // handle last element
- lastElem := bA.Elems[numElems-1]
- numFinalBits := bA.Bits - curBit
- for i := 0; i < numFinalBits; i++ {
- if (lastElem & (uint64(1) << uint64(i))) > 0 {
- trueIndices = append(trueIndices, curBit)
- }
- curBit++
- }
- return trueIndices
- }
-
- // String returns a string representation of BitArray: BA{<bit-string>},
- // where <bit-string> is a sequence of 'x' (1) and '_' (0).
- // The <bit-string> includes spaces and newlines to help people.
- // For a simple sequence of 'x' and '_' characters with no spaces or newlines,
- // see the MarshalJSON() method.
- // Example: "BA{_x_}" or "nil-BitArray" for nil.
- func (bA *BitArray) String() string {
- return bA.StringIndented("")
- }
-
- // StringIndented returns the same thing as String(), but applies the indent
- // at every 10th bit, and twice at every 50th bit.
- 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 += indent
- }
- if i%50 == 49 {
- bits += indent
- }
- }
- if len(bits) > 0 {
- lines = append(lines, bits)
- }
- return fmt.Sprintf("BA{%v:%v}", bA.Bits, strings.Join(lines, indent))
- }
-
- // Bytes returns the byte representation of the bits within the bitarray.
- 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
- }
-
- // Update sets the bA's bits to be that of the other bit array.
- // The copying begins from the begin of both bit arrays.
- func (bA *BitArray) Update(o *BitArray) {
- if bA == nil || o == nil {
- return
- }
-
- bA.mtx.Lock()
- o.mtx.Lock()
- copy(bA.Elems, o.Elems)
- o.mtx.Unlock()
- bA.mtx.Unlock()
- }
-
- // MarshalJSON implements json.Marshaler interface by marshaling bit array
- // using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit.
- func (bA *BitArray) MarshalJSON() ([]byte, error) {
- if bA == nil {
- return []byte("null"), nil
- }
-
- bA.mtx.Lock()
- defer bA.mtx.Unlock()
-
- bits := `"`
- for i := 0; i < bA.Bits; i++ {
- if bA.getIndex(i) {
- bits += `x`
- } else {
- bits += `_`
- }
- }
- bits += `"`
- return []byte(bits), nil
- }
-
- var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`)
-
- // UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom
- // JSON description.
- func (bA *BitArray) UnmarshalJSON(bz []byte) error {
- b := string(bz)
- if b == "null" {
- // This is required e.g. for encoding/json when decoding
- // into a pointer with pre-allocated BitArray.
- bA.reset(0)
- return nil
- }
-
- // Validate 'b'.
- match := bitArrayJSONRegexp.FindStringSubmatch(b)
- if match == nil {
- return fmt.Errorf("bitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b)
- }
- bits := match[1]
- numBits := len(bits)
-
- bA.reset(numBits)
- for i := 0; i < numBits; i++ {
- if bits[i] == 'x' {
- bA.SetIndex(i, true)
- }
- }
-
- return nil
- }
-
- // ToProto converts BitArray to protobuf. It returns nil if BitArray is
- // nil/empty.
- func (bA *BitArray) ToProto() *tmprotobits.BitArray {
- if bA == nil ||
- (len(bA.Elems) == 0 && bA.Bits == 0) { // empty
- return nil
- }
-
- bA.mtx.Lock()
- defer bA.mtx.Unlock()
-
- bc := bA.copy()
- return &tmprotobits.BitArray{Bits: int64(bc.Bits), Elems: bc.Elems}
- }
-
- // FromProto sets BitArray to the given protoBitArray. It returns an error if
- // protoBitArray is invalid.
- func (bA *BitArray) FromProto(protoBitArray *tmprotobits.BitArray) error {
- if protoBitArray == nil {
- return nil
- }
-
- // Validate protoBitArray.
- if protoBitArray.Bits < 0 {
- return errors.New("negative Bits")
- }
- // #[32bit]
- if protoBitArray.Bits > math.MaxInt32 { // prevent overflow on 32bit systems
- return errors.New("too many Bits")
- }
- if got, exp := len(protoBitArray.Elems), numElems(int(protoBitArray.Bits)); got != exp {
- return fmt.Errorf("invalid number of Elems: got %d, but exp %d", got, exp)
- }
-
- bA.mtx.Lock()
- defer bA.mtx.Unlock()
-
- ec := make([]uint64, len(protoBitArray.Elems))
- copy(ec, protoBitArray.Elems)
-
- bA.Bits = int(protoBitArray.Bits)
- bA.Elems = ec
- return nil
- }
-
- func numElems(bits int) int {
- return (bits + 63) / 64
- }
|