2017-01-12 21:25:31 +01:00
|
|
|
// Copyright (c) 2016-2017 The btcsuite developers
|
|
|
|
// Copyright (c) 2016-2017 The Lightning Network Developers
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package gcs_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"math/rand"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcutil/gcs"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// No need to allocate an err variable in every test
|
|
|
|
err error
|
|
|
|
|
|
|
|
// Collision probability for the tests (1/2**20)
|
|
|
|
P = uint8(20)
|
|
|
|
|
|
|
|
// Filters are conserved between tests but we must define with an
|
|
|
|
// interface which functions we're testing because the gcsFilter
|
|
|
|
// type isn't exported
|
2017-01-18 00:49:31 +01:00
|
|
|
filter, filter2 *gcs.Filter
|
2017-01-12 21:25:31 +01:00
|
|
|
|
|
|
|
// We need to use the same key for building and querying the filters
|
|
|
|
key [gcs.KeySize]byte
|
|
|
|
|
|
|
|
// List of values for building a filter
|
|
|
|
contents = [][]byte{
|
|
|
|
[]byte("Alex"),
|
|
|
|
[]byte("Bob"),
|
|
|
|
[]byte("Charlie"),
|
|
|
|
[]byte("Dick"),
|
|
|
|
[]byte("Ed"),
|
|
|
|
[]byte("Frank"),
|
|
|
|
[]byte("George"),
|
|
|
|
[]byte("Harry"),
|
|
|
|
[]byte("Ilya"),
|
|
|
|
[]byte("John"),
|
|
|
|
[]byte("Kevin"),
|
|
|
|
[]byte("Larry"),
|
|
|
|
[]byte("Michael"),
|
|
|
|
[]byte("Nate"),
|
|
|
|
[]byte("Owen"),
|
|
|
|
[]byte("Paul"),
|
|
|
|
[]byte("Quentin"),
|
|
|
|
}
|
|
|
|
|
|
|
|
// List of values for querying a filter using MatchAny()
|
|
|
|
contents2 = [][]byte{
|
|
|
|
[]byte("Alice"),
|
|
|
|
[]byte("Betty"),
|
|
|
|
[]byte("Charmaine"),
|
|
|
|
[]byte("Donna"),
|
|
|
|
[]byte("Edith"),
|
|
|
|
[]byte("Faina"),
|
|
|
|
[]byte("Georgia"),
|
|
|
|
[]byte("Hannah"),
|
|
|
|
[]byte("Ilsbeth"),
|
|
|
|
[]byte("Jennifer"),
|
|
|
|
[]byte("Kayla"),
|
|
|
|
[]byte("Lena"),
|
|
|
|
[]byte("Michelle"),
|
|
|
|
[]byte("Natalie"),
|
|
|
|
[]byte("Ophelia"),
|
|
|
|
[]byte("Peggy"),
|
|
|
|
[]byte("Queenie"),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestGCSFilterBuild builds a test filter with a randomized key. For Bitcoin
|
|
|
|
// use, deterministic filter generation is desired. Therefore, a
|
|
|
|
// key that's derived deterministically would be required.
|
|
|
|
func TestGCSFilterBuild(t *testing.T) {
|
|
|
|
for i := 0; i < gcs.KeySize; i += 4 {
|
|
|
|
binary.BigEndian.PutUint32(key[i:], rand.Uint32())
|
|
|
|
}
|
|
|
|
filter, err = gcs.BuildGCSFilter(P, key, contents)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter build failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestGCSFilterCopy deserializes and serializes a filter to create a copy.
|
|
|
|
func TestGCSFilterCopy(t *testing.T) {
|
|
|
|
filter2, err = gcs.FromBytes(filter.N(), P, filter.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter copy failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestGCSFilterMetadata checks that the filter metadata is built and
|
|
|
|
// copied correctly.
|
|
|
|
func TestGCSFilterMetadata(t *testing.T) {
|
|
|
|
if filter.P() != P {
|
|
|
|
t.Fatal("P not correctly stored in filter metadata")
|
|
|
|
}
|
|
|
|
if filter.N() != uint32(len(contents)) {
|
|
|
|
t.Fatal("N not correctly stored in filter metadata")
|
|
|
|
}
|
|
|
|
if filter.P() != filter2.P() {
|
|
|
|
t.Fatal("P doesn't match between copied filters")
|
|
|
|
}
|
|
|
|
if filter.N() != filter2.N() {
|
|
|
|
t.Fatal("N doesn't match between copied filters")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestGCSFilterMatch checks that both the built and copied filters match
|
|
|
|
// correctly, logging any false positives without failing on them.
|
|
|
|
func TestGCSFilterMatch(t *testing.T) {
|
|
|
|
match, err := filter.Match(key, []byte("Nate"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
t.Fatal("Filter didn't match when it should have!")
|
|
|
|
}
|
|
|
|
match, err = filter2.Match(key, []byte("Nate"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
t.Fatal("Filter didn't match when it should have!")
|
|
|
|
}
|
|
|
|
match, err = filter.Match(key, []byte("Quentin"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
t.Fatal("Filter didn't match when it should have!")
|
|
|
|
}
|
|
|
|
match, err = filter2.Match(key, []byte("Quentin"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
t.Fatal("Filter didn't match when it should have!")
|
|
|
|
}
|
|
|
|
match, err = filter.Match(key, []byte("Nates"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
t.Logf("False positive match, should be 1 in 2**%d!", P)
|
|
|
|
}
|
|
|
|
match, err = filter2.Match(key, []byte("Nates"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
t.Logf("False positive match, should be 1 in 2**%d!", P)
|
|
|
|
}
|
|
|
|
match, err = filter.Match(key, []byte("Quentins"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
t.Logf("False positive match, should be 1 in 2**%d!", P)
|
|
|
|
}
|
|
|
|
match, err = filter2.Match(key, []byte("Quentins"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
t.Logf("False positive match, should be 1 in 2**%d!", P)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestGCSFilterMatchAny checks that both the built and copied filters match
|
|
|
|
// a list correctly, logging any false positives without failing on them.
|
|
|
|
func TestGCSFilterMatchAny(t *testing.T) {
|
|
|
|
match, err := filter.MatchAny(key, contents2)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match any failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
t.Logf("False positive match, should be 1 in 2**%d!", P)
|
|
|
|
}
|
|
|
|
match, err = filter2.MatchAny(key, contents2)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match any failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
t.Logf("False positive match, should be 1 in 2**%d!", P)
|
|
|
|
}
|
|
|
|
contents2 = append(contents2, []byte("Nate"))
|
|
|
|
match, err = filter.MatchAny(key, contents2)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match any failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
t.Fatal("Filter didn't match any when it should have!")
|
|
|
|
}
|
|
|
|
match, err = filter2.MatchAny(key, contents2)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Filter match any failed: %s", err.Error())
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
t.Fatal("Filter didn't match any when it should have!")
|
|
|
|
}
|
|
|
|
}
|