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 }