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  }