From 6f51807c27a6ec09ea82a22d02897ec07e60c5d6 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 24 Apr 2019 17:04:27 -0700 Subject: [PATCH] gcs/gcs_test: match zero element --- gcs/gcs_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/gcs/gcs_test.go b/gcs/gcs_test.go index d51ba83..369b773 100644 --- a/gcs/gcs_test.go +++ b/gcs/gcs_test.go @@ -88,6 +88,85 @@ func TestGCSFilterBuild(t *testing.T) { } } +// TestGCSMatchZeroHash ensures that Match and MatchAny properly match an item +// if it's hash after the reduction is zero. This is accomplished by brute +// forcing a specific target whose hash is zero given a certain (P, M, key, +// len(elements)) combination. In this case, P and M are the default, key was +// chosen randomly, and len(elements) is 13. The target 4-byte value of 16060032 +// is the first such 32-bit value, thus we use the number 0-11 as the other +// elements in the filter since we know they won't collide. We test both the +// positive and negative cases, when the zero hash item is in the filter and +// when it is excluded. In the negative case, the 32-bit value of 12 is added to +// the filter instead of the target. +func TestGCSMatchZeroHash(t *testing.T) { + t.Run("include zero", func(t *testing.T) { + testGCSMatchZeroHash(t, true) + }) + t.Run("exclude zero", func(t *testing.T) { + testGCSMatchZeroHash(t, false) + }) +} + +func testGCSMatchZeroHash(t *testing.T, includeZeroHash bool) { + key := [gcs.KeySize]byte{ + 0x25, 0x28, 0x0d, 0x25, 0x26, 0xe1, 0xd3, 0xc7, + 0xa5, 0x71, 0x85, 0x34, 0x92, 0xa5, 0x7e, 0x68, + } + + // Construct the target data to match, whose hash is zero after applying + // the reduction with the parameters in the test. + target := make([]byte, 4) + binary.BigEndian.PutUint32(target, 16060032) + + // Construct the set of 13 items including the target, using the 32-bit + // values of 0 through 11 as the first 12 items. We known none of these + // hash to zero since the brute force ended well beyond them. + elements := make([][]byte, 0, 13) + for i := 0; i < 12; i++ { + data := make([]byte, 4) + binary.BigEndian.PutUint32(data, uint32(i)) + elements = append(elements, data) + } + + // If the filter should include the zero hash element, add the target + // which we know hashes to zero. Otherwise add 32-bit value of 12 which + // we know does not hash to zero. + if includeZeroHash { + elements = append(elements, target) + } else { + data := make([]byte, 4) + binary.BigEndian.PutUint32(data, 12) + elements = append(elements, data) + } + + filter, err := gcs.BuildGCSFilter(P, M, key, elements) + if err != nil { + t.Fatalf("unable to build filter: %v", err) + } + + match, err := filter.Match(key, target) + if err != nil { + t.Fatalf("unable to match: %v", err) + } + + // We should only get a match iff the target was included. + if match != includeZeroHash { + t.Fatalf("expected match from Match: %t, got %t", + includeZeroHash, match) + } + + match, err = filter.MatchAny(key, [][]byte{target}) + if err != nil { + t.Fatalf("unable to match any: %v", err) + } + + // We should only get a match iff the target was included. + if match != includeZeroHash { + t.Fatalf("expected match from MatchAny: %t, got %t", + includeZeroHash, match) + } +} + // TestGCSFilterCopy deserializes and serializes a filter to create a copy. func TestGCSFilterCopy(t *testing.T) { serialized2, err := filter.Bytes()