gcs: allow optional de/serialization of N and P parameters

This commit is contained in:
Alex 2017-02-21 19:08:08 -07:00 committed by Olaoluwa Osuntokun
parent ca65f28ca1
commit f74edf2c4d
2 changed files with 91 additions and 1 deletions

View file

@ -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 {

View file

@ -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