github.com/Finschia/finschia-sdk@v0.48.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 := 0; ; elem++ { 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 104 // Copy returns a copy of the provided bit array. 105 func (bA *CompactBitArray) Copy() *CompactBitArray { 106 if bA == nil { 107 return nil 108 } 109 110 c := make([]byte, len(bA.Elems)) 111 copy(c, bA.Elems) 112 113 return &CompactBitArray{ 114 ExtraBitsStored: bA.ExtraBitsStored, 115 Elems: c, 116 } 117 } 118 119 // Equal checks if both bit arrays are equal. If both arrays are nil then it returns true. 120 func (bA *CompactBitArray) Equal(other *CompactBitArray) bool { 121 if bA == other { 122 return true 123 } 124 if bA == nil || other == nil { 125 return false 126 } 127 return bA.ExtraBitsStored == other.ExtraBitsStored && 128 bytes.Equal(bA.Elems, other.Elems) 129 } 130 131 // String returns a string representation of CompactBitArray: BA{<bit-string>}, 132 // where <bit-string> is a sequence of 'x' (1) and '_' (0). 133 // The <bit-string> includes spaces and newlines to help people. 134 // For a simple sequence of 'x' and '_' characters with no spaces or newlines, 135 // see the MarshalJSON() method. 136 // Example: "BA{_x_}" or "nil-BitArray" for nil. 137 func (bA *CompactBitArray) String() string { return bA.StringIndented("") } 138 139 // StringIndented returns the same thing as String(), but applies the indent 140 // at every 10th bit, and twice at every 50th bit. 141 func (bA *CompactBitArray) StringIndented(indent string) string { 142 if bA == nil { 143 return "nil-BitArray" 144 } 145 lines := []string{} 146 bits := "" 147 size := bA.Count() 148 for i := 0; i < size; i++ { 149 if bA.GetIndex(i) { 150 bits += "x" 151 } else { 152 bits += "_" 153 } 154 155 if i%100 == 99 { 156 lines = append(lines, bits) 157 bits = "" 158 } 159 160 if i%10 == 9 { 161 bits += indent 162 } 163 164 if i%50 == 49 { 165 bits += indent 166 } 167 } 168 169 if len(bits) > 0 { 170 lines = append(lines, bits) 171 } 172 173 return fmt.Sprintf("BA{%v:%v}", size, strings.Join(lines, indent)) 174 } 175 176 // MarshalJSON implements json.Marshaler interface by marshaling bit array 177 // using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit. 178 func (bA *CompactBitArray) MarshalJSON() ([]byte, error) { 179 if bA == nil { 180 return []byte("null"), nil 181 } 182 183 bits := `"` 184 size := bA.Count() 185 for i := 0; i < size; i++ { 186 if bA.GetIndex(i) { 187 bits += `x` 188 } else { 189 bits += `_` 190 } 191 } 192 193 bits += `"` 194 195 return []byte(bits), nil 196 } 197 198 var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`) 199 200 // UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom 201 // JSON description. 202 func (bA *CompactBitArray) UnmarshalJSON(bz []byte) error { 203 b := string(bz) 204 if b == "null" { 205 // This is required e.g. for encoding/json when decoding 206 // into a pointer with pre-allocated BitArray. 207 bA.ExtraBitsStored = 0 208 bA.Elems = nil 209 210 return nil 211 } 212 213 match := bitArrayJSONRegexp.FindStringSubmatch(b) 214 if match == nil { 215 return fmt.Errorf("bitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b) 216 } 217 218 bits := match[1] 219 220 // Construct new CompactBitArray and copy over. 221 numBits := len(bits) 222 bA2 := NewCompactBitArray(numBits) 223 for i := 0; i < numBits; i++ { 224 if bits[i] == 'x' { 225 bA2.SetIndex(i, true) 226 } 227 } 228 *bA = *bA2 229 230 return nil 231 } 232 233 // CompactMarshal is a space efficient encoding for CompactBitArray. 234 // It is not amino compatible. 235 func (bA *CompactBitArray) CompactMarshal() []byte { 236 size := bA.Count() 237 if size <= 0 { 238 return []byte("null") 239 } 240 241 bz := make([]byte, 0, size/8) 242 // length prefix number of bits, not number of bytes. This difference 243 // takes 3-4 bits in encoding, as opposed to instead encoding the number of 244 // bytes (saving 3-4 bits) and including the offset as a full byte. 245 bz = appendUvarint(bz, uint64(size)) 246 bz = append(bz, bA.Elems...) 247 248 return bz 249 } 250 251 // CompactUnmarshal is a space efficient decoding for CompactBitArray. 252 // It is not amino compatible. 253 func CompactUnmarshal(bz []byte) (*CompactBitArray, error) { 254 if len(bz) < 2 { 255 return nil, errors.New("compact bit array: invalid compact unmarshal size") 256 } else if bytes.Equal(bz, []byte("null")) { 257 return NewCompactBitArray(0), nil 258 } 259 260 size, n := binary.Uvarint(bz) 261 if n < 0 || n >= len(bz) { 262 return nil, fmt.Errorf("compact bit array: n=%d is out of range of len(bz)=%d", n, len(bz)) 263 } 264 bz = bz[n:] 265 266 if len(bz) != int(size+7)/8 { 267 return nil, errors.New("compact bit array: invalid compact unmarshal size") 268 } 269 270 bA := &CompactBitArray{uint32(size % 8), bz} 271 272 return bA, nil 273 } 274 275 func appendUvarint(b []byte, x uint64) []byte { 276 var a [binary.MaxVarintLen64]byte 277 n := binary.PutUvarint(a[:], x) 278 279 return append(b, a[:n]...) 280 }