Make Digest implement the encoding.Binary(Un)Marshaler interfaces
diff --git a/xxhash.go b/xxhash.go index b0e5440..db0b35f 100644 --- a/xxhash.go +++ b/xxhash.go
@@ -4,6 +4,7 @@ import ( "encoding/binary" + "errors" "math/bits" ) @@ -35,7 +36,7 @@ v2 uint64 v3 uint64 v4 uint64 - total int + total uint64 mem [32]byte n int // how much of mem is used } @@ -49,12 +50,12 @@ // Reset clears the Digest's state so that it can be reused. func (d *Digest) Reset() { - d.n = 0 - d.total = 0 d.v1 = prime1v + prime2 d.v2 = prime2 d.v3 = 0 d.v4 = -prime1v + d.total = 0 + d.n = 0 } // Size always returns 8 bytes. @@ -66,12 +67,12 @@ // Write adds more data to d. It always returns len(b), nil. func (d *Digest) Write(b []byte) (n int, err error) { n = len(b) - d.total += len(b) + d.total += uint64(n) - if d.n+len(b) < 32 { + if d.n+n < 32 { // This new data doesn't even fill the current block. copy(d.mem[d.n:], b) - d.n += len(b) + d.n += n return } @@ -130,7 +131,7 @@ h = d.v3 + prime5 } - h += uint64(d.total) + h += d.total i, end := 0, d.n for ; i+8 <= end; i += 8 { @@ -158,6 +159,56 @@ return h } +const ( + magic = "xxh\x06" + marshaledSize = len(magic) + 8*5 + 32 +) + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (d *Digest) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + b = appendUint64(b, d.v1) + b = appendUint64(b, d.v2) + b = appendUint64(b, d.v3) + b = appendUint64(b, d.v4) + b = appendUint64(b, d.total) + b = append(b, d.mem[:d.n]...) + b = b[:len(b)+len(d.mem)-d.n] + return b, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("xxhash: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("xxhash: invalid hash state size") + } + b = b[len(magic):] + b, d.v1 = consumeUint64(b) + b, d.v2 = consumeUint64(b) + b, d.v3 = consumeUint64(b) + b, d.v4 = consumeUint64(b) + b, d.total = consumeUint64(b) + copy(d.mem[:], b) + b = b[len(d.mem):] + d.n = int(d.total % uint64(len(d.mem))) + return nil +} + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.LittleEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := u64(b) + return b[8:], x +} + func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) } func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
diff --git a/xxhash_test.go b/xxhash_test.go index 767c4a0..58f3584 100644 --- a/xxhash_test.go +++ b/xxhash_test.go
@@ -97,6 +97,43 @@ } } +func TestBinaryMarshaling(t *testing.T) { + d := New() + d.WriteString("abc") + b, err := d.MarshalBinary() + if err != nil { + t.Fatal(err) + } + d = New() + d.WriteString("junk") + if err := d.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + d.WriteString("def") + if got, want := d.Sum64(), Sum64String("abcdef"); got != want { + t.Fatalf("after MarshalBinary+UnmarshalBinary, got 0x%x; want 0x%x", got, want) + } + + d0 := New() + d1 := New() + for i := 0; i < 64; i++ { + b, err := d0.MarshalBinary() + if err != nil { + t.Fatal(err) + } + d0 = new(Digest) + if err := d0.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + if got, want := d0.Sum64(), d1.Sum64(); got != want { + t.Fatalf("after %d Writes, unmarshaled Digest gave sum 0x%x; want 0x%x", i, got, want) + } + + d0.Write([]byte{'a'}) + d1.Write([]byte{'a'}) + } +} + func TestAllocs(t *testing.T) { const shortStr = "abcdefghijklmnop" // Sum64([]byte(shortString)) shouldn't allocate because the