dcrd/gcs/bits.go
Dave Collins 2a8856d026
gcs: Overhaul tests and benchmarks.
This rewrites the tests to make them more consistent with the rest of
the code base and significantly increases their coverage of the code.

It also reworks the benchmarks to actually benchmark what their names
claim, renames them for consistency, and make them more stable by
ensuring the same prng seed is used each run to eliminate variance
introduced by different values.

Finally, it removes an impossible to hit condition from the bit reader
and adds a couple of additional checks to harden the filters against
potential misuse.

This is part of the ongoing process to cleanup and improve the gcs
module to the quality level required by consensus code for ultimate
inclusion in header commitments.
2019-08-20 09:36:10 -05:00

190 lines
3.1 KiB
Go

// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package gcs
import (
"io"
)
type bitWriter struct {
bytes []byte
p *byte // Pointer to last byte
next byte // Next bit to write or skip
}
// writeOne writes a one bit to the bit stream.
func (b *bitWriter) writeOne() {
if b.next == 0 {
b.bytes = append(b.bytes, 1<<7)
b.p = &b.bytes[len(b.bytes)-1]
b.next = 1 << 6
return
}
*b.p |= b.next
b.next >>= 1
}
// writeZero writes a zero bit to the bit stream.
func (b *bitWriter) writeZero() {
if b.next == 0 {
b.bytes = append(b.bytes, 0)
b.p = &b.bytes[len(b.bytes)-1]
b.next = 1 << 6
return
}
b.next >>= 1
}
// writeNBits writes n number of LSB bits of data to the bit stream in big
// endian format. Panics if n > 64.
func (b *bitWriter) writeNBits(data uint64, n uint) {
if n > 64 {
panic("gcs: cannot write more than 64 bits of a uint64")
}
data <<= 64 - n
// If byte is partially written, fill the rest
for n > 0 {
if b.next == 0 {
break
}
if data&(1<<63) != 0 {
b.writeOne()
} else {
b.writeZero()
}
n--
data <<= 1
}
if n == 0 {
return
}
// Write 8 bits at a time.
for n >= 8 {
b.bytes = append(b.bytes, byte(data>>56))
n -= 8
data <<= 8
}
// Write the remaining bits.
for n > 0 {
if data&(1<<63) != 0 {
b.writeOne()
} else {
b.writeZero()
}
n--
data <<= 1
}
}
type bitReader struct {
bytes []byte
next byte // next bit to read in bytes[0]
}
func newBitReader(bitstream []byte) bitReader {
return bitReader{
bytes: bitstream,
next: 1 << 7,
}
}
// readUnary returns the number of unread sequential one bits before the next
// zero bit. Errors with io.EOF if no zero bits are encountered.
func (b *bitReader) readUnary() (uint64, error) {
var value uint64
for {
if len(b.bytes) == 0 {
return value, io.EOF
}
for b.next != 0 {
bit := b.bytes[0] & b.next
b.next >>= 1
if bit == 0 {
return value, nil
}
value++
}
b.bytes = b.bytes[1:]
b.next = 1 << 7
}
}
// readNBits reads n number of LSB bits of data from the bit stream in big
// endian format. Panics if n > 64.
func (b *bitReader) readNBits(n uint) (uint64, error) {
if n > 64 {
panic("gcs: cannot read more than 64 bits as a uint64")
}
if n == 0 {
return 0, nil
}
if len(b.bytes) == 0 {
return 0, io.EOF
}
var value uint64
// If byte is partially read, read the rest
if b.next != 1<<7 {
for n > 0 {
if b.next == 0 {
b.next = 1 << 7
b.bytes = b.bytes[1:]
break
}
n--
if b.bytes[0]&b.next != 0 {
value |= 1 << n
}
b.next >>= 1
}
}
if n == 0 {
return value, nil
}
// Read 8 bits at a time.
for n >= 8 {
if len(b.bytes) == 0 {
return 0, io.EOF
}
n -= 8
value |= uint64(b.bytes[0]) << n
b.bytes = b.bytes[1:]
}
if len(b.bytes) == 0 {
if n != 0 {
return 0, io.EOF
}
return value, nil
}
// Read the remaining bits.
for n > 0 {
n--
if b.bytes[0]&b.next != 0 {
value |= 1 << n
}
b.next >>= 1
}
return value, nil
}