Skip to content

Commit

Permalink
Merge pull request #20 from stackb/cleanup
Browse files Browse the repository at this point in the history
Tidy up options
  • Loading branch information
pcj committed Jun 22, 2023
2 parents c7036ab + 2b301a5 commit 246a60f
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 218 deletions.
2 changes: 0 additions & 2 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ go_library(
srcs = [
"hasher.go",
"hashing.go",
"protoreflecthash.go",
],
importpath = "github.com/stackb/protoreflecthash",
visibility = ["//visibility:public"],
Expand All @@ -26,7 +25,6 @@ go_test(
name = "protoreflecthash_test",
srcs = [
"hasher_test.go",
"protoreflecthash_test.go",
],
data = glob(["testdata/**"]),
embed = [":protoreflecthash"],
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ protoreflecthash is a re-implementation of
<https://github.com/deepmind/objecthash-proto>. That repo is now archived and
was never updated for [protobuf-apiv2](https://go.dev/blog/protobuf-apiv2).

This package is currently experimental; the hash values for messages will likely
change without warning until v1.

# Usage

```
Expand All @@ -36,11 +33,33 @@ import (
func main() {
msg := mustGetProtoMessageSomewhere()

hex, err := protoreflecthash.String(msg)
options := []protoreflect.Option{
// protoreflect.MessageFullnameIdentifier(),
// protoreflect.FieldNamesAsKeys(),
}
hasher := protoreflect.NewHasher(options...)

hash, err := hasher.HashProto(msg.ProtoReflect())
if err != nil {
panic(err.Error())
}
println(hex)

fmt.Printf("%x\n", hash)
}
```

# Background

`protoreflecthash` computes the hash value for a protobuf message by taking a
sha256 of the sum the individual component hashes of the message. Special care
is taken to account for various semantics of the protobuf format.

This implementation passes all functional unit tests from the original library
[deepmind/objecthash-proto](https://github.com/deepmind/objecthash-proto)
(excluding badness detection).

Open questions remain about the handling of protobufs with extension fields and
the google.protobuf.Any type.

This package is currently experimental; hash values for messages may change
without warning until v1.
40 changes: 32 additions & 8 deletions hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,37 @@ import (

const valueName = protoreflect.Name("value")

type ProtoHasherOption func(*hasher)
// Option modifies how hashes for protobufs is calculated.
type Option func(*hasher)

// ProtoHasher is an interface for hashers that are capable of returning an
// ObjectHash for protobufs.
type ProtoHasher interface {
// HashProto returns the object hash of a given protocol buffer message.
HashProto(msg protoreflect.Message) ([]byte, error)
}

func NewHasher(options ...ProtoHasherOption) ProtoHasher {
return &hasher{}
// NewHasher creates a new ProtoHasher with the options specified in the
// argument.
func NewHasher(options ...Option) ProtoHasher {
h := &hasher{}
for _, opt := range options {
opt(h)
}
return h
}

func FieldNamesAsKeys() ProtoHasherOption {
// MessageFullnameIdentifier is an option that uses the message descriptor
// fullname rather than a generic map identifier when hashing messages.
func MessageFullnameIdentifier() Option {
return func(h *hasher) {
h.messageFullnameIdentifier = true
}
}

// FieldNamesAsKeys is an option that uses field names for key hashing rather
// than the field number.
func FieldNamesAsKeys() Option {
return func(h *hasher) {
h.fieldNamesAsKeys = true
}
Expand All @@ -31,6 +51,9 @@ type hasher struct {
// Whether to use the proto field name as its key, as opposed to using the
// tag number as the key.
fieldNamesAsKeys bool
// Whether to use the fullname of the message descriptor rather than 'm'
// (mapIdentifier) for proto messages.
messageFullnameIdentifier bool
}

type fieldHashEntry struct {
Expand Down Expand Up @@ -91,9 +114,10 @@ func (h *hasher) hashMessage(msg protoreflect.Message) ([]byte, error) {
}

identifier := mapIdentifier
// if hasher.messageIdentifier != "" {
// identifier = hasher.messageIdentifier
// }
if h.messageFullnameIdentifier {
identifier = string(md.FullName())
}

return hash(identifier, buf.Bytes())
}

Expand Down Expand Up @@ -328,7 +352,7 @@ func (h *hasher) hashGoogleProtobufAny(md protoreflect.MessageDescriptor, msg pr
// files := protoregistry.GlobalFiles - create option to set files explictly?
typeUrl := msg.Get(md.Fields().ByName("type_url")).String()
// TODO: lookup at a type server?
return nil, fmt.Errorf("unsupported type: " + typeUrl)
return nil, fmt.Errorf("protoreflecthash does not support hashing of Any type: " + typeUrl)
}

func (h *hasher) hashGoogleProtobufDuration(md protoreflect.MessageDescriptor, msg protoreflect.Message) ([]byte, error) {
Expand Down

0 comments on commit 246a60f

Please sign in to comment.