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