diff --git a/core/ledger/util/filterbitarray.go b/core/ledger/util/filterbitarray.go new file mode 100644 index 00000000000..ed3cdb8dfde --- /dev/null +++ b/core/ledger/util/filterbitarray.go @@ -0,0 +1,82 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +// FilterBitArray is an array of bits based on byte unit, so 8 bits at each +// index. The array automatically increases if the set index is larger than the +// current capacity. The bit index starts at 0. +type FilterBitArray []byte + +// NewFilterBitArray creates an array with the specified bit-size. This is an +// optimization to make array once for the expected capacity rather than +// using Set function to auto-increase the array. +func NewFilterBitArray(size uint) FilterBitArray { + ba := make(FilterBitArray, (size-1)/8+1) + return ba +} + +// Capacity returns the number of bits in the FilterBitArray. +func (ba *FilterBitArray) Capacity() uint { + return uint(len(*ba) * 8) +} + +// Set assigns 1 to the specified bit-index, which is starting from 0. +// Set automatically increases the array to accommodate the bit-index. +func (ba *FilterBitArray) Set(i uint) { + // Location of i in the array index is floor(i/8) + 1. If it exceeds the + // current byte array, we'll make a new one large enough to include the + // specified bit-index + if i >= ba.Capacity() { + array := make([]byte, i/8+1) + copy(array, *ba) + *ba = array + } + (*ba)[i/8] |= 1 << (i % 8) +} + +// Unset assigns 0 the specified bit-index. If bit-index is larger than capacity, +// do nothing. +func (ba *FilterBitArray) Unset(i uint) { + if i < ba.Capacity() { + (*ba)[i/8] &^= 1 << (i % 8) + } +} + +// ValueAt returns the value at the specified bit-index. If bit-index is out +// of range, return 0. Note that the returned value is in byte, so it may be +// a power of 2 if not 0. +func (ba *FilterBitArray) ValueAt(i uint) byte { + if i < ba.Capacity() { + return (*ba)[i/8] & (1 << (i % 8)) + } + return 0 +} + +// IsSet returns true if the specified bit-index is 1; false otherwise. +func (ba *FilterBitArray) IsSet(i uint) bool { + return (ba.ValueAt(i) != 0) +} + +// ToBytes returns the byte array for storage. +func (ba *FilterBitArray) ToBytes() []byte { + return *ba +} + +// FromBytes accepts a byte array. +func (ba *FilterBitArray) FromBytes(bytes []byte) { + *ba = bytes +} diff --git a/core/ledger/util/filterbitarray_test.go b/core/ledger/util/filterbitarray_test.go new file mode 100644 index 00000000000..d1c19cc8aff --- /dev/null +++ b/core/ledger/util/filterbitarray_test.go @@ -0,0 +1,102 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// To run this test by itself, enter `go test -run FilterBitArray` from this folder +package util + +import ( + "bytes" + "encoding/binary" + "testing" +) + +func TestFilterBitArrayFixed(t *testing.T) { + ba := NewFilterBitArray(25) + // Note that capacity may be greater than 25 + t.Logf("FilterBitArray capacity: %d\n", ba.Capacity()) + var i uint + for i = 0; i < ba.Capacity(); i++ { + ba.Set(i) + } + // All bits must be set + for i = 0; i < ba.Capacity(); i++ { + if !ba.IsSet(i) { + t.FailNow() + } + } + for i = 0; i < ba.Capacity(); i++ { + ba.Unset(i) + } + // All bits must be unset + for i = 0; i < ba.Capacity(); i++ { + if ba.IsSet(i) { + t.FailNow() + } + } +} + +func TestFilterBitArraySparse(t *testing.T) { + ba := new(FilterBitArray) + // test byte boundary + ba.Unset(0) + ba.Set(8) + ba.Unset(9) + ba.Set(116) + if ba.IsSet(0) { + t.FailNow() + } + if !ba.IsSet(8) { + t.FailNow() + } + if ba.IsSet(9) { + t.FailNow() + } + if !ba.IsSet(116) { + t.FailNow() + } +} + +func TestFilterBitArrayIO(t *testing.T) { + ba := NewFilterBitArray(20) + var i uint + for i = 0; i < 20; i++ { + if i%2 == 0 { + ba.Set(i) + } else { + ba.Unset(i) + } + } + b := ba.ToBytes() + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.LittleEndian, b); err != nil { + t.Fatalf("binary.Write failed: %s", err) + } + if err := binary.Read(buf, binary.LittleEndian, b); err != nil { + t.Fatalf("binary.Read failed: %s", err) + } + ba.FromBytes(b) + for i = 0; i < 20; i++ { + if i%2 == 0 { + if !ba.IsSet(i) { + t.FailNow() + } + } else { + if ba.IsSet(i) { + t.FailNow() + } + } + } +}