gcs: allow optional de/serialization of N and P parameters
This commit is contained in:
parent
ca65f28ca1
commit
f74edf2c4d
2 changed files with 91 additions and 1 deletions
47
gcs/gcs.go
47
gcs/gcs.go
|
@ -6,6 +6,7 @@
|
|||
package gcs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
@ -140,6 +141,24 @@ func FromBytes(N uint32, P uint8, d []byte) (*Filter, error) {
|
|||
return f, nil
|
||||
}
|
||||
|
||||
// FromNBytes deserializes a GCS filter from a known P, and serialized N and
|
||||
// filter as returned by NBytes().
|
||||
func FromNBytes(P uint8, d []byte) (*Filter, error) {
|
||||
return FromBytes(binary.BigEndian.Uint32(d[:4]), P, d[4:])
|
||||
}
|
||||
|
||||
// FromPBytes deserializes a GCS filter from a known N, and serialized P and
|
||||
// filter as returned by NBytes().
|
||||
func FromPBytes(N uint32, d []byte) (*Filter, error) {
|
||||
return FromBytes(N, d[0], d[1:])
|
||||
}
|
||||
|
||||
// FromNPBytes deserializes a GCS filter from a serialized N, P, and filter as
|
||||
// returned by NPBytes().
|
||||
func FromNPBytes(d []byte) (*Filter, error) {
|
||||
return FromBytes(binary.BigEndian.Uint32(d[:4]), d[4], d[5:])
|
||||
}
|
||||
|
||||
// Bytes returns the serialized format of the GCS filter, which does not
|
||||
// include N or P (returned by separate methods) or the key used by SipHash.
|
||||
func (f *Filter) Bytes() []byte {
|
||||
|
@ -148,6 +167,34 @@ func (f *Filter) Bytes() []byte {
|
|||
return filterData
|
||||
}
|
||||
|
||||
// NBytes returns the serialized format of the GCS filter with N, which does
|
||||
// not include P (returned by a separate method) or the key used by SipHash.
|
||||
func (f *Filter) NBytes() []byte {
|
||||
filterData := make([]byte, len(f.filterData)+4)
|
||||
binary.BigEndian.PutUint32(filterData[:4], f.n)
|
||||
copy(filterData[4:], f.filterData)
|
||||
return filterData
|
||||
}
|
||||
|
||||
// PBytes returns the serialized format of the GCS filter with P, which does
|
||||
// not include N (returned by a separate method) or the key used by SipHash.
|
||||
func (f *Filter) PBytes() []byte {
|
||||
filterData := make([]byte, len(f.filterData)+1)
|
||||
filterData[0] = f.p
|
||||
copy(filterData[1:], f.filterData)
|
||||
return filterData
|
||||
}
|
||||
|
||||
// NPBytes returns the serialized format of the GCS filter with N and P, which
|
||||
// does not include the key used by SipHash.
|
||||
func (f *Filter) NPBytes() []byte {
|
||||
filterData := make([]byte, len(f.filterData)+5)
|
||||
binary.BigEndian.PutUint32(filterData[:4], f.n)
|
||||
filterData[4] = f.p
|
||||
copy(filterData[5:], f.filterData)
|
||||
return filterData
|
||||
}
|
||||
|
||||
// P returns the filter's collision probability as a negative power of 2 (that
|
||||
// is, a collision probability of `1/2**20` is represented as 20).
|
||||
func (f *Filter) P() uint8 {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package gcs_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
@ -23,7 +24,7 @@ var (
|
|||
// Filters are conserved between tests but we must define with an
|
||||
// interface which functions we're testing because the gcsFilter
|
||||
// type isn't exported
|
||||
filter, filter2 *gcs.Filter
|
||||
filter, filter2, filter3, filter4, filter5 *gcs.Filter
|
||||
|
||||
// We need to use the same key for building and querying the filters
|
||||
key [gcs.KeySize]byte
|
||||
|
@ -90,6 +91,18 @@ func TestGCSFilterCopy(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Filter copy failed: %s", err.Error())
|
||||
}
|
||||
filter3, err = gcs.FromNBytes(filter.P(), filter.NBytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Filter copy failed: %s", err.Error())
|
||||
}
|
||||
filter4, err = gcs.FromPBytes(filter.N(), filter.PBytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Filter copy failed: %s", err.Error())
|
||||
}
|
||||
filter5, err = gcs.FromNPBytes(filter.NPBytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Filter copy failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TestGCSFilterMetadata checks that the filter metadata is built and
|
||||
|
@ -104,9 +117,39 @@ func TestGCSFilterMetadata(t *testing.T) {
|
|||
if filter.P() != filter2.P() {
|
||||
t.Fatal("P doesn't match between copied filters")
|
||||
}
|
||||
if filter.P() != filter3.P() {
|
||||
t.Fatal("P doesn't match between copied filters")
|
||||
}
|
||||
if filter.P() != filter4.P() {
|
||||
t.Fatal("P doesn't match between copied filters")
|
||||
}
|
||||
if filter.P() != filter5.P() {
|
||||
t.Fatal("P doesn't match between copied filters")
|
||||
}
|
||||
if filter.N() != filter2.N() {
|
||||
t.Fatal("N doesn't match between copied filters")
|
||||
}
|
||||
if filter.N() != filter3.N() {
|
||||
t.Fatal("N doesn't match between copied filters")
|
||||
}
|
||||
if filter.N() != filter4.N() {
|
||||
t.Fatal("N doesn't match between copied filters")
|
||||
}
|
||||
if filter.N() != filter5.N() {
|
||||
t.Fatal("N doesn't match between copied filters")
|
||||
}
|
||||
if !bytes.Equal(filter.Bytes(), filter2.Bytes()) {
|
||||
t.Fatal("Bytes don't match between copied filters")
|
||||
}
|
||||
if !bytes.Equal(filter.Bytes(), filter3.Bytes()) {
|
||||
t.Fatal("Bytes don't match between copied filters")
|
||||
}
|
||||
if !bytes.Equal(filter.Bytes(), filter4.Bytes()) {
|
||||
t.Fatal("Bytes don't match between copied filters")
|
||||
}
|
||||
if !bytes.Equal(filter.Bytes(), filter5.Bytes()) {
|
||||
t.Fatal("Bytes don't match between copied filters")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGCSFilterMatch checks that both the built and copied filters match
|
||||
|
|
Loading…
Reference in a new issue