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
|
package gcs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -140,6 +141,24 @@ func FromBytes(N uint32, P uint8, d []byte) (*Filter, error) {
|
||||||
return f, nil
|
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
|
// 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.
|
// include N or P (returned by separate methods) or the key used by SipHash.
|
||||||
func (f *Filter) Bytes() []byte {
|
func (f *Filter) Bytes() []byte {
|
||||||
|
@ -148,6 +167,34 @@ func (f *Filter) Bytes() []byte {
|
||||||
return filterData
|
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
|
// 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).
|
// is, a collision probability of `1/2**20` is represented as 20).
|
||||||
func (f *Filter) P() uint8 {
|
func (f *Filter) P() uint8 {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package gcs_test
|
package gcs_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -23,7 +24,7 @@ var (
|
||||||
// Filters are conserved between tests but we must define with an
|
// Filters are conserved between tests but we must define with an
|
||||||
// interface which functions we're testing because the gcsFilter
|
// interface which functions we're testing because the gcsFilter
|
||||||
// type isn't exported
|
// 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
|
// We need to use the same key for building and querying the filters
|
||||||
key [gcs.KeySize]byte
|
key [gcs.KeySize]byte
|
||||||
|
@ -90,6 +91,18 @@ func TestGCSFilterCopy(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Filter copy failed: %s", err.Error())
|
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
|
// TestGCSFilterMetadata checks that the filter metadata is built and
|
||||||
|
@ -104,9 +117,39 @@ func TestGCSFilterMetadata(t *testing.T) {
|
||||||
if filter.P() != filter2.P() {
|
if filter.P() != filter2.P() {
|
||||||
t.Fatal("P doesn't match between copied filters")
|
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() {
|
if filter.N() != filter2.N() {
|
||||||
t.Fatal("N doesn't match between copied filters")
|
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
|
// TestGCSFilterMatch checks that both the built and copied filters match
|
||||||
|
|
Loading…
Reference in a new issue