github.com/okex/exchain@v1.8.0/libs/tendermint/crypto/multisig/bitarray/compact_bit_array.go (about)

     1  package bitarray
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"regexp"
    10  	"strings"
    11  )
    12  
    13  // CompactBitArray is an implementation of a space efficient bit array.
    14  // This is used to ensure that the encoded data takes up a minimal amount of
    15  // space after amino encoding.
    16  // This is not thread safe, and is not intended for concurrent usage.
    17  type CompactBitArray struct {
    18  	ExtraBitsStored byte   `json:"extra_bits"` // The number of extra bits in elems.
    19  	Elems           []byte `json:"bits"`
    20  }
    21  
    22  // NewCompactBitArray returns a new compact bit array.
    23  // It returns nil if the number of bits is zero, or if there is any overflow
    24  // in the arithmetic to encounter for the number of its elements: (bits+7)/8,
    25  // or if the number of elements will be an unreasonably large number like
    26  // > maxint32 aka >2**31.
    27  func NewCompactBitArray(bits int) *CompactBitArray {
    28  	if bits <= 0 {
    29  		return nil
    30  	}
    31  	nElems := (bits + 7) / 8
    32  	if nElems <= 0 || nElems > math.MaxInt32 {
    33  		// We encountered an overflow here, and shouldn't pass negatives
    34  		// to make, nor should we allow unreasonable limits > maxint32.
    35  		// See https://github.com/cosmos/cosmos-sdk/issues/9162
    36  		return nil
    37  	}
    38  	return &CompactBitArray{
    39  		ExtraBitsStored: byte(bits % 8),
    40  		Elems:           make([]byte, nElems),
    41  	}
    42  }
    43  
    44  // Size returns the number of bits in the bitarray
    45  func (bA *CompactBitArray) Size() int {
    46  	if bA == nil {
    47  		return 0
    48  	} else if bA.ExtraBitsStored == byte(0) {
    49  		return len(bA.Elems) * 8
    50  	}
    51  	// num_bits = 8*num_full_bytes + overflow_in_last_byte
    52  	// num_full_bytes = (len(bA.Elems)-1)
    53  	return (len(bA.Elems)-1)*8 + int(bA.ExtraBitsStored)
    54  }
    55  
    56  // GetIndex returns the bit at index i within the bit array.
    57  // The behavior is undefined if i >= bA.Size()
    58  func (bA *CompactBitArray) GetIndex(i int) bool {
    59  	if bA == nil {
    60  		return false
    61  	}
    62  	if i >= bA.Size() {
    63  		return false
    64  	}
    65  	return bA.Elems[i>>3]&(uint8(1)<<uint8(7-(i%8))) > 0
    66  }
    67  
    68  // SetIndex sets the bit at index i within the bit array.
    69  // The behavior is undefined if i >= bA.Size()
    70  func (bA *CompactBitArray) SetIndex(i int, v bool) bool {
    71  	if bA == nil {
    72  		return false
    73  	}
    74  	if i >= bA.Size() {
    75  		return false
    76  	}
    77  	if v {
    78  		bA.Elems[i>>3] |= (uint8(1) << uint8(7-(i%8)))
    79  	} else {
    80  		bA.Elems[i>>3] &= ^(uint8(1) << uint8(7-(i%8)))
    81  	}
    82  	return true
    83  }
    84  
    85  // NumTrueBitsBefore returns the number of bits set to true before the
    86  // given index. e.g. if bA = _XX__XX, NumOfTrueBitsBefore(4) = 2, since
    87  // there are two bits set to true before index 4.
    88  func (bA *CompactBitArray) NumTrueBitsBefore(index int) int {
    89  	numTrueValues := 0
    90  	for i := 0; i < index; i++ {
    91  		if bA.GetIndex(i) {
    92  			numTrueValues++
    93  		}
    94  	}
    95  	return numTrueValues
    96  }
    97  
    98  // Copy returns a copy of the provided bit array.
    99  func (bA *CompactBitArray) Copy() *CompactBitArray {
   100  	if bA == nil {
   101  		return nil
   102  	}
   103  	c := make([]byte, len(bA.Elems))
   104  	copy(c, bA.Elems)
   105  	return &CompactBitArray{
   106  		ExtraBitsStored: bA.ExtraBitsStored,
   107  		Elems:           c,
   108  	}
   109  }
   110  
   111  // String returns a string representation of CompactBitArray: BA{<bit-string>},
   112  // where <bit-string> is a sequence of 'x' (1) and '_' (0).
   113  // The <bit-string> includes spaces and newlines to help people.
   114  // For a simple sequence of 'x' and '_' characters with no spaces or newlines,
   115  // see the MarshalJSON() method.
   116  // Example: "BA{_x_}" or "nil-BitArray" for nil.
   117  func (bA *CompactBitArray) String() string {
   118  	return bA.StringIndented("")
   119  }
   120  
   121  // StringIndented returns the same thing as String(), but applies the indent
   122  // at every 10th bit, and twice at every 50th bit.
   123  func (bA *CompactBitArray) StringIndented(indent string) string {
   124  	if bA == nil {
   125  		return "nil-BitArray"
   126  	}
   127  	lines := []string{}
   128  	bits := ""
   129  	size := bA.Size()
   130  	for i := 0; i < size; i++ {
   131  		if bA.GetIndex(i) {
   132  			bits += "x"
   133  		} else {
   134  			bits += "_"
   135  		}
   136  		if i%100 == 99 {
   137  			lines = append(lines, bits)
   138  			bits = ""
   139  		}
   140  		if i%10 == 9 {
   141  			bits += indent
   142  		}
   143  		if i%50 == 49 {
   144  			bits += indent
   145  		}
   146  	}
   147  	if len(bits) > 0 {
   148  		lines = append(lines, bits)
   149  	}
   150  	return fmt.Sprintf("BA{%v:%v}", size, strings.Join(lines, indent))
   151  }
   152  
   153  // MarshalJSON implements json.Marshaler interface by marshaling bit array
   154  // using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit.
   155  func (bA *CompactBitArray) MarshalJSON() ([]byte, error) {
   156  	if bA == nil {
   157  		return []byte("null"), nil
   158  	}
   159  
   160  	bits := `"`
   161  	size := bA.Size()
   162  	for i := 0; i < size; i++ {
   163  		if bA.GetIndex(i) {
   164  			bits += `x`
   165  		} else {
   166  			bits += `_`
   167  		}
   168  	}
   169  	bits += `"`
   170  	return []byte(bits), nil
   171  }
   172  
   173  var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`)
   174  
   175  // UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom
   176  // JSON description.
   177  func (bA *CompactBitArray) UnmarshalJSON(bz []byte) error {
   178  	b := string(bz)
   179  	if b == "null" {
   180  		// This is required e.g. for encoding/json when decoding
   181  		// into a pointer with pre-allocated BitArray.
   182  		bA.ExtraBitsStored = 0
   183  		bA.Elems = nil
   184  		return nil
   185  	}
   186  
   187  	// Validate 'b'.
   188  	match := bitArrayJSONRegexp.FindStringSubmatch(b)
   189  	if match == nil {
   190  		return fmt.Errorf("bitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b)
   191  	}
   192  	bits := match[1]
   193  
   194  	// Construct new CompactBitArray and copy over.
   195  	numBits := len(bits)
   196  	bA2 := NewCompactBitArray(numBits)
   197  	for i := 0; i < numBits; i++ {
   198  		if bits[i] == 'x' {
   199  			bA2.SetIndex(i, true)
   200  		}
   201  	}
   202  	*bA = *bA2
   203  	return nil
   204  }
   205  
   206  // CompactMarshal is a space efficient encoding for CompactBitArray.
   207  // It is not amino compatible.
   208  func (bA *CompactBitArray) CompactMarshal() []byte {
   209  	size := bA.Size()
   210  	if size <= 0 {
   211  		return []byte("null")
   212  	}
   213  	bz := make([]byte, 0, size/8)
   214  	// length prefix number of bits, not number of bytes. This difference
   215  	// takes 3-4 bits in encoding, as opposed to instead encoding the number of
   216  	// bytes (saving 3-4 bits) and including the offset as a full byte.
   217  	bz = appendUvarint(bz, uint64(size))
   218  	bz = append(bz, bA.Elems...)
   219  	return bz
   220  }
   221  
   222  // CompactUnmarshal is a space efficient decoding for CompactBitArray.
   223  // It is not amino compatible.
   224  func CompactUnmarshal(bz []byte) (*CompactBitArray, error) {
   225  	if len(bz) < 2 {
   226  		return nil, errors.New("compact bit array: invalid compact unmarshal size")
   227  	} else if bytes.Equal(bz, []byte("null")) {
   228  		return NewCompactBitArray(0), nil
   229  	}
   230  	size, n := binary.Uvarint(bz)
   231  	bz = bz[n:]
   232  	if len(bz) != int(size+7)/8 {
   233  		return nil, errors.New("compact bit array: invalid compact unmarshal size")
   234  	}
   235  
   236  	bA := &CompactBitArray{byte(int(size % 8)), bz}
   237  	return bA, nil
   238  }
   239  
   240  func appendUvarint(b []byte, x uint64) []byte {
   241  	var a [binary.MaxVarintLen64]byte
   242  	n := binary.PutUvarint(a[:], x)
   243  	return append(b, a[:n]...)
   244  }