Skip to content

Commit

Permalink
FilterBitArray for ledger transaction filters.
Browse files Browse the repository at this point in the history
I decided to implement our own FilterBitArray rather than using the
Golang one because it is bloated and lacking of function to restore
the Bit Array from a byte array; ie ToBytes and FromBytes functions.

Change-Id: I5235928db9ed65ddab90c507ba3beea46f414d8b
Signed-off-by: Binh Q. Nguyen <binhn@us.ibm.com>
  • Loading branch information
binhn committed Nov 28, 2016
1 parent 69a3aa6 commit 3e88fd3
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
82 changes: 82 additions & 0 deletions core/ledger/util/filterbitarray.go
Original file line number Diff line number Diff line change
@@ -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
}
102 changes: 102 additions & 0 deletions core/ledger/util/filterbitarray_test.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}

0 comments on commit 3e88fd3

Please sign in to comment.