Skip to content

Commit 32d26d1

Browse files
authored
BACKWARDS INCOMPATIBLE: version update + json marshalling (#5)
* go modules support * minor refactorings * update binary format, fix text marshalling * implement json marshalling/unmarshalling * remove text marshaller interface * remove debug, remove gob file * Add .circleci/config.yml * circle-ci config * lint nitpicks * readme: circleci badge
1 parent 242f565 commit 32d26d1

18 files changed

+196
-436
lines changed

.circleci/config.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Golang CircleCI 2.0 configuration file
2+
#
3+
# Check https://circleci.com/docs/2.0/language-go/ for more details
4+
version: 2
5+
jobs:
6+
build:
7+
docker:
8+
# specify the version
9+
- image: circleci/golang:1.14
10+
11+
working_directory: /go/src/github.com/holiman/bloomfilter
12+
steps:
13+
- checkout
14+
15+
# specify any bash command here prefixed with `run: `
16+
- run: go get -v -t -d ./...
17+
- run: go test -v ./...
18+
- run:
19+
name: "Install tools"
20+
command: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.8
21+
- run:
22+
name: "Lint"
23+
command: golangci-lint run
24+

.travis.yml

Lines changed: 0 additions & 14 deletions
This file was deleted.

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11

2-
[![GoDoc](https://godoc.org/github.com/steakknife/bloomfilter?status.png)](https://godoc.org/github.com/steakknife/bloomfilter) [![travis](https://img.shields.io/travis/steakknife/bloomfilter.svg)](https://travis-ci.org/steakknife/bloomfilter)
2+
[![GoDoc](https://godoc.org/github.com/holiman/bloomfilter?status.png)](https://godoc.org/github.com/holiman/bloomfilter)
3+
[![CircleCI](https://circleci.com/gh/holiman/bloomfilter.svg?style=svg)](https://app.circleci.com/pipelines/github/holiman/bloomfilter)
34

45
# History
56

67
This bloom filter implementation is a fork from [steakknife/bloomfilter](https://github.com/steakknife/bloomfilter) by Barry Allard.
78
The upstream project is now archived, so this fork exists to fix some bugs and also
89
make a few improvements. Below is the original description.
910

10-
All recent changes are copyright © 2019 Martin Holst Swende.
11+
The original implemenation is Copyright © 2014-2016,2018 Barry Allard
12+
[MIT license](MIT-LICENSE.txt)
13+
14+
All recent changes are copyright © 2019-2020 Martin Holst Swende.
1115

1216
## Installation
1317

1418
```
1519
$ go get github.com/holiman/bloomfilter
1620
```
1721

18-
# Face-meltingly fast, thread-safe, marshalable, unionable, probability- and optimal-size-calculating Bloom filter in go
22+
## Face-meltingly fast, thread-safe, marshalable, unionable, probability- and optimal-size-calculating Bloom filter in go
1923

20-
Copyright © 2014-2016,2018 Barry Allard
21-
[MIT license](MIT-LICENSE.txt)
24+
### WTF is a bloom filter
2225

23-
## WTF is a bloom filter
24-
25-
**TL;DR: **Probabilistic, extra lookup table to track a set of elements kept elsewhere to reduce expensive, unnecessary set element retrieval and/or iterator operations **when an element is not present in the set.** It's a classic time-storage tradeoff algoritm.
26+
**TL;DR:** Probabilistic, extra lookup table to track a set of elements kept elsewhere to reduce expensive, unnecessary set element retrieval and/or iterator operations **when an element is not present in the set.** It's a classic time-storage tradeoff algoritm.
2627

2728
### Properties
2829

@@ -70,7 +71,7 @@ All values in Little-endian format
7071

7172
```go
7273

73-
import "github.com/steakknife/bloomfilter"
74+
import "github.com/holiman/bloomfilter"
7475

7576
const (
7677
maxElements = 100000
@@ -120,3 +121,5 @@ Where possible, branch-free operations are used to avoid deep pipeline / executi
120121
[MIT license](MIT-LICENSE.txt)
121122

122123
Copyright © 2014-2016 Barry Allard
124+
Copyright © 2019-2020 Martin Holst Swende
125+

binarymarshaler.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import (
1717
"io"
1818
)
1919

20+
// headerMagic is used to disambiguate between this package and the original
21+
// steakknife implementation.
22+
// Since the key hashing algorithm has changed, the format is no longer
23+
// binary compatible
24+
var version = []byte("v02\n")
25+
var headerMagic = append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, version...)
26+
2027
// counter is a utility to count bytes written
2128
type counter struct {
2229
bytes int
@@ -51,8 +58,10 @@ func (f *Filter) MarshallToWriter(out io.Writer) (int, [sha512.Size384]byte, err
5158
)
5259
f.lock.RLock()
5360
defer f.lock.RUnlock()
54-
debug("write bf k=%d n=%d m=%d\n", f.K(), f.n, f.m)
5561

62+
if _, err := mw.Write(headerMagic); err != nil {
63+
return c.bytes, hash, err
64+
}
5665
if err := binary.Write(mw, binary.LittleEndian, f.K()); err != nil {
5766
return c.bytes, hash, err
5867
}
@@ -90,10 +99,6 @@ func (f *Filter) MarshallToWriter(out io.Writer) (int, [sha512.Size384]byte, err
9099
hashbytes := hasher.Sum(nil)
91100
copy(hash[:], hashbytes[:sha512.Size384])
92101
err := binary.Write(out, binary.LittleEndian, hashbytes)
93-
if err != nil {
94-
debug("bloomfilter.MarshalBinary: Successfully wrote %d byte(s), sha384 %v",
95-
c.bytes, hash)
96-
}
97102
return c.bytes + len(hashbytes), hash, err
98103
}
99104

binaryunmarshaler.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,41 @@ import (
1414
"bytes"
1515
"crypto/sha512"
1616
"encoding/binary"
17+
"fmt"
1718
"hash"
1819
"io"
1920
)
2021

2122
func unmarshalBinaryHeader(r io.Reader) (k, n, m uint64, err error) {
23+
magic := make([]byte, len(headerMagic))
24+
if _, err := r.Read(magic); err != nil {
25+
return 0, 0, 0, err
26+
}
27+
if !bytes.Equal(magic, headerMagic) {
28+
return 0, 0, 0, fmt.Errorf("incompatible version (wrong magic), got %x", magic)
29+
}
2230
err = binary.Read(r, binary.LittleEndian, &k)
2331
if err != nil {
24-
return k, n, m, err
32+
return 0, 0, 0, err
2533
}
26-
2734
if k < KMin {
28-
return k, n, m, errK()
35+
return 0, 0, 0, fmt.Errorf("keys must have length %d or greater (was %d)", KMin, k)
2936
}
3037

3138
err = binary.Read(r, binary.LittleEndian, &n)
3239
if err != nil {
33-
return k, n, m, err
40+
return 0, 0, 0, err
3441
}
3542

3643
err = binary.Read(r, binary.LittleEndian, &m)
3744
if err != nil {
38-
return k, n, m, err
45+
return 0, 0, 0, err
3946
}
4047

4148
if m < MMin {
42-
return k, n, m, errM()
49+
return 0, 0, 0, fmt.Errorf("number of bits in the filter must be >= %d (was %d)", MMin, m)
4350
}
4451

45-
debug("read bf k=%d n=%d m=%d\n", k, n, m)
46-
4752
return k, n, m, err
4853
}
4954

@@ -82,7 +87,7 @@ func (h *hashingReader) Read(p []byte) (n int, err error) {
8287
if err != nil {
8388
return n, err
8489
}
85-
h.hasher.Write(p)
90+
_, _ = h.hasher.Write(p)
8691
return n, err
8792
}
8893

@@ -126,9 +131,7 @@ func (f *Filter) UnmarshalFromReader(input io.Reader) (n int64, err error) {
126131
return buf.tot, err
127132
}
128133
if !bytes.Equal(gotHash, expHash) {
129-
debug("bloomfilter.UnmarshalBinary() sha384 hash failed:"+
130-
" actual %v expected %v", gotHash, expHash)
131-
return buf.tot, errHash()
134+
return buf.tot, errHashMismatch
132135
}
133136
return buf.tot, nil
134137
}

bloomfilter.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313
package bloomfilter
1414

1515
import (
16+
"errors"
1617
"hash"
1718
"sync"
1819
)
1920

21+
var (
22+
errHashMismatch = errors.New("hash mismatch, bloom filter corruption or wrong version")
23+
)
24+
2025
// Filter is an opaque Bloom filter type
2126
type Filter struct {
2227
lock sync.RWMutex
@@ -104,7 +109,7 @@ func (f *Filter) Copy() (*Filter, error) {
104109
// UnionInPlace merges Bloom filter f2 into f
105110
func (f *Filter) UnionInPlace(f2 *Filter) error {
106111
if !f.IsCompatible(f2) {
107-
return errIncompatibleBloomFilters()
112+
return errors.New("incompatible bloom filters")
108113
}
109114

110115
f.lock.Lock()
@@ -121,7 +126,7 @@ func (f *Filter) UnionInPlace(f2 *Filter) error {
121126
// Union merges f2 and f2 into a new Filter out
122127
func (f *Filter) Union(f2 *Filter) (out *Filter, err error) {
123128
if !f.IsCompatible(f2) {
124-
return nil, errIncompatibleBloomFilters()
129+
return nil, errors.New("incompatible bloom filters")
125130
}
126131

127132
f.lock.RLock()

bloomfilter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func BenchmarkUnionInPlace(b *testing.B) {
195195
b.Run("union-8", func(b *testing.B) {
196196
for i := 0; i < b.N; i++ {
197197
for _, bx := range filters {
198-
b1.UnionInPlace(bx)
198+
_ = b1.UnionInPlace(bx)
199199
}
200200
}
201201
})

conformance.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@ package bloomfilter
1313
import (
1414
"encoding"
1515
"encoding/gob"
16+
"encoding/json"
1617
"io"
1718
)
1819

1920
// compile-time conformance tests
2021
var (
2122
_ encoding.BinaryMarshaler = (*Filter)(nil)
2223
_ encoding.BinaryUnmarshaler = (*Filter)(nil)
23-
_ encoding.TextMarshaler = (*Filter)(nil)
24-
_ encoding.TextUnmarshaler = (*Filter)(nil)
2524
_ io.ReaderFrom = (*Filter)(nil)
2625
_ io.WriterTo = (*Filter)(nil)
2726
_ gob.GobDecoder = (*Filter)(nil)
2827
_ gob.GobEncoder = (*Filter)(nil)
28+
_ json.Marshaler = (*Filter)(nil)
29+
_ json.Unmarshaler = (*Filter)(nil)
2930
)

debug.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

errors.go

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)